Определение точного значения времени играет важную роль в жизни современного человечества, поэтому в современном мире получили широкое распространение часы различных форматов и назначения – начиная от простых настольных будильников и заканчивая "умными" смарт-часами. В данной статье мы рассмотрим подключение модуля часов реального времени (RTC) DS3231 к микроконтроллеру PIC. Определяемое с помощью модуля DS3231 время мы будем выводить на экран ЖК дисплея 16х2. Таким образом, наш проект может использоваться в роли цифровых часов на основе микроконтроллера PIC.
Также на нашем сайты вы можете посмотреть проекты часов на основе других микроконтроллеров (плат):
- GPS часы;
- часы реального времени на ESP32 и модуле DS3231;
- часы реального времени с будильником на основе Arduino;
- бинарные часы на светодиодах с использованием Arduino;
- часы на Arduino и 4-х разрядном семисегментном индикаторе;
- умные часы (Smart Watch) на основе Arduino, OLED дисплея и смартфона.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Модуль часов реального времени DS3231 (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 33 пФ (2 шт.) (купить на AliExpress).
- Резистор 1 кОм, 5,1 кОм и 10 кОм (купить на AliExpress).
- Потенциометр 10 кОм (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Стабилизированное напряжение питания 5V.
- Макетная плата.
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Модуль часов реального времени DS3231
Модуль часов реального времени (Real Time Clock, RTC) позволяет хранить информацию о реальном времени и дате и делиться этой информацией с другими устройствами. Одним из самых популярных подобных модулей является DS3231. Дрейф времени в данном модуле составляет всего несколько секунд за год, поэтому его показания времени можно считать достаточно надежными. Модуль запитывается от батарейки-таблетки 3V и хранит показания времени до тех пор пока батарейка не разрядится. Внешний вид модуля DS3231 показан на следующем рисунке.
С другими устройствами модуль DS3231 взаимодействует по протоколу I2C, использование данного протокола в микроконтроллерах PIC мы рассматривали в этой статье. Также мы создадим библиотеку для работы с модулем DS3231 и будем подключать ее заголовочный файл ниже в программе. Саму библиотеку можно скачать ниже в статье. Поскольку для вывода информации в нашем проекте используется ЖК дисплей рекомендуем ознакомиться с его подключением к микроконтроллеру PIC.
Схема проекта
Схема подключения модуля часов реального времени DS3231 к микроконтроллеру PIC представлена на следующем рисунке.
Модуль DS3231 работает по интерфейсу I2C, поэтому его линии SCL и SDA подключены к контактам 18 (SCL) и 23 (SDA) микроконтроллера PIC. Подтягивающие резисторы 4,7 кОм используются для поддержания шины в состоянии high в то время когда она не занята (по ней не передается никакая информация). ЖК дисплей подключен к Port D микроконтроллера в 4-битном режиме.
Модуль I2C, показанный на схеме, используется только для отладки взаимодействия по интерфейсу I2C, его не следует учитывать в общей схеме соединений. RTC модуль DS3231 запитывается от +5V через свои контакты Vcc и Ground.
Внешний вид собранной на макетной плате конструкции проекта показан на следующем рисунке.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. Также полный код программы и необходимыми библиотеками к ней в виде ZIP файла можно скачать по этой ссылке.
В программе мы используем три библиотеки, заголовочные файлы которых мы подключаем в начале программы: lcd.h для работы с ЖК дисплеем, PIC16F877a_I2C.h для работы с интерфейсом I2C и PIC16F877a_DS3231.h для взаимодействия с модулем реального времени.
Первым делом в программе настроим биты конфигурации микроконтроллера и установим для него частоту работы 20 MHz.
1 2 3 4 5 6 7 8 9 10 |
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 |
Далее укажем контакты микроконтроллера, к которым подключен ЖК дисплей.
1 2 3 4 5 6 |
#define RS RD2 #define EN RD3 #define D4 RD4 #define D5 RD5 #define D6 RD6 #define D7 RD7 |
При покупке модуля часов реального времени в нем, скорее всего, не будут установлены правильные дата и время, поэтому нам будет необходимо установить их в программе. Поэтому объявим в программе соответствующие переменные для их установки. Мы на момент запуска программы использовали время 10:55 и дату 6-5-2018. Вам необходимо изменить эти переменные под то время и дату, когда вы будете загружать программу в микроконтроллер.
1 2 3 4 5 6 7 8 |
/*Set the current value of date and time below*/ int sec = 00; int min = 55; int hour = 10; int date = 06; int month = 05; int year = 18; /*Time and Date Set*/ |
Затем мы подключим все необходимые заголовочные файлы в программе.
1 2 3 4 |
#include <xc.h> #include "lcd.h" #include "PIC16F877a_I2C.h" #include "PIC16F877a_DS3231.h" |
ЖК дисплей подключен у нас к PORT D, поэтому контакты данного порта сконфигурируем для работы на вывод данных. Затем инициализируем ЖК дисплей.
1 2 |
TRISD = 0x00; //Make Port D pins as outptu for LCD interfacing Lcd_Start(); // Initialize LCD module |
Затем инициализируем связь по протоколу I2C поскольку наш RTC модуль работает по данному протоколу. Модуль DS3231 использует частоту шины интерфейса I2C равную 100 кГц (как и большинство других устройств, работающих по данному протоколу), поэтому мы инициализируем интерфейс I2C с частотой 100 кГц.
1 |
I2C_Initialize(100); //Initialize I2C Master with 100KHz clock |
Далее нам необходимо установить в модуле DS3231 текущие время и дату – мы это сделаем с помощью вызова функции Set_Time_Date(). Как только время и дата будут установлены модуль будет непрерывно поддерживать их в актуальном состоянии.
1 |
Set_Time_Date(); //set time and date on the RTC module |
Вначале работы программы мы покажем на экране ЖК дисплея 16х2 приветственное сообщение на 2 секунды.
1 2 3 4 5 6 |
Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String(" RTC with PIC"); Lcd_Set_Cursor(2,1); Lcd_Print_String(" -Circuit Digest"); __delay_ms(1500); |
Далее внутри бесконечного цикла мы будем считывать с RTC модуля дату и время с помощью функции Update_Current_Date_Time() и затем отображать их на экране ЖК дисплея.
1 |
Update_Current_Date_Time(); //Read the current date and time from RTC module |
Поскольку значения даты и времени представлены у нас переменными целого типа, то для отображения их на ЖК дисплея необходимо разделить их на отдельные символы.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Split the into char to display on lcd char sec_0 = sec%10; char sec_1 = (sec/10); char min_0 = min%10; char min_1 = min/10; char hour_0 = hour%10; char hour_1 = hour/10; char date_0 = date%10; char date_1 = date/10; char month_0 = month%10; char month_1 = month/10; char year_0 = year%10; char year_1 = year/10; |
Далее отображаем эту информацию на экране ЖК дисплея, используя для этого функции из подключенной нами библиотеки для работы с ЖК дисплеем.
1 2 3 4 5 6 7 8 9 10 11 |
Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String("TIME: "); Lcd_Print_Char(hour_1+'0'); Lcd_Print_Char(hour_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(min_1+'0'); Lcd_Print_Char(min_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(sec_1+'0'); Lcd_Print_Char(sec_0+'0'); |
Краткое объяснение заголовочного файла PIC16F877a_DS3231.h
Для того, чтобы максимально полно разобраться с тем, как работает модуль DS3231, целесообразно изучить даташит на него. Если говорить кратко, то в нашем проекте модуль DS3231 работает в качестве ведомого (slave) по отношению к микроконтроллеру PIC в протоколе I2C и адрес модуля равен D0. То есть, чтобы записать данные в модуль мы должны передать адрес D0, а чтобы считать данные с модуля – мы должны передать адрес D1. Если мы передаем адрес записи модулю DS3231, то он подготавливается к приему данных от микроконтроллера PIC, поэтому в дальнейшем данные, которые мы будем передавать с микроконтроллера, будут приниматься и сохраняться в модуле реального времени. Аналогичным образом, если мы передаем модулю адрес для считывания, то микроконтроллер PIC должен быть готов к считыванию данных от RTC модуля. Последовательности битов для адресов D0 и D1 приведены в даташите на модуль DS3231: последовательность 0b11010000 означает адрес D0 (запись), а последовательность 0b11010001 – адрес D1 (чтение).
Когда микроконтроллер PIC передает адрес D0 или D1 (запись или чтение), по последующие данные должны считываться или записываться в определенном формате (порядке). Эта последовательность приведена на рисунке ниже. То есть первыми данными должны быть секунды (00h), затем минуты (01h) , затем часы (02h) и затем день (03h) и так далее вплоть до значений температуры.
Модуль часов реального времени не работает с десятичными значениями, он понимает только двоично-[кодированный] десятичный код (binary-coded decimal, BCD). Поэтому прежде чем передавать какие либо данные в модуль часов реального времени их необходимо преобразовать в двоично-десятичный код, а когда мы принимаем данные от модуля их необходимо преобразовать из формата BCD в десятичный формат. Поэтому мы запрограммировали специальные функции для этих преобразований.
Функции BCD_2_DEC и DEC_2_BCD
Эти функции используются для преобразования данных из двоично-десятичного формата в десятичный и обратно. Формулы для этих преобразований выглядят следующим образом.
1 2 3 |
Decimal = (BCD >> 4) * 10 + (BCD & 0x0F) BCD = ((Decimal / 10) << 4) + (Decimal % 10) |
Поэтому код функций BCD_2_DEC и DEC_2_BCD будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 |
int BCD_2_DEC(int to_convert) { return (to_convert >> 4) * 10 + (to_convert & 0x0F); } int DEC_2_BCD (int to_convert) { return ((to_convert / 10) << 4) + (to_convert % 10); } |
Функция Set_Time_Date()
Эта функция будет записывать данные времени и даты из микроконтроллера PIC в модуль часов реального времени. Эти данные будут задаваться с помощью соответствующих переменных sec, min, hour, date, month и year. Эти данные будут конвертироваться в формат BCD и записываться в модуль RTC.
Как мы уже обсуждали ранее, чтобы передать значение модулю RTC мы должны передать ему адрес D0 и чтобы инициировать процесс записи мы должны передать бит 0. После этого мы можем передавать данные в последовательности, приведенной в таблице на рисунке выше.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void Set_Time_Date() { I2C_Begin(); I2C_Write(0xD0); I2C_Write(0); I2C_Write(DEC_2_BCD(sec)); //update sec I2C_Write(DEC_2_BCD(min)); //update min I2C_Write(DEC_2_BCD(hour)); //update hour I2C_Write(1); //ignore updating day I2C_Write(DEC_2_BCD(date)); //update date I2C_Write(DEC_2_BCD(month)); //update month I2C_Write(DEC_2_BCD(year)); //update year I2C_End(); } |
Функция Update_Current_Date_Time()
Данная функция используется для считывания данных времени и даты с модуля часов реального времени и передачи их в микроконтроллер PIC. Данная функция разделена на три фрагмента, в первом из которых инициируется процесс считывания, во втором происходит считывание данных и сохранение их в глобальные переменные sec, min, hour, date, month и year, а в третьем – передается подтверждение о том, что считывание данных было успешным.
При этом следует отметить то, что каждое действие в протоколе I2C должно начинаться и заканчиваться.
Чтобы считать данные с модуля часов реального времени мы должны передать ему адрес D0, а за ним – бит 0. После этого модуль RTC будет передавать данные в последовательности, приведенной на рисунке выше. Мы будем считывать эти данные, преобразовывать их в десятичный формат и сохранять их в соответствующие переменные в том же самом порядке.
И, наконец, после того как процесс считывания данных будет завершен, модуль RTC будет передавать бит подтверждения (acknowledgment bit), который также будет считываться и подтверждаться.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void Update_Current_Date_Time() { //START to Read I2C_Begin(); I2C_Write(0xD0); I2C_Write(0); I2C_End(); //READ I2C_Begin(); I2C_Write(0xD1); // Initialize data read sec = BCD_2_DEC(I2C_Read(1)); min = BCD_2_DEC(I2C_Read(1)); hour = BCD_2_DEC(I2C_Read(1)); I2C_Read(1); date = BCD_2_DEC(I2C_Read(1)); month = BCD_2_DEC(I2C_Read(1)); year = BCD_2_DEC(I2C_Read(1)); I2C_End(); //END Reading I2C_Begin(); I2C_Write(0xD1); // Initialize data read I2C_Read(1); I2C_End(); } |
Моделирование работы проекта
Работу проекта мы смоделировали в симуляторе Proteus. Нарисуйте в нем схему, приведенную на рисунке, и загрузите в микроконтроллер PIC hex файл программы. Когда вы запустите в Proteus процесс симуляции вы увидите два всплывающих окна и выводимые на экран ЖК дисплея данные времени и даты как показано на следующем рисунке.
В первом всплывающем окне будут выводиться данные времени и даты, а второе всплывающее окно представляет собой I2C debugger (отладчик) – отличный инструмент для мониторинга данных, передаваемых по шине I2C.
Тестирование работы проекта
После того как аппаратная часть проекта будет готова и вы скачали ZIP файл со всеми необходимыми файлами проекта, откройте программу проекта с помощью MPLABX IDE. Вам необходимо сначала запустить IDE, затем использовать опцию открытия проекта (open project), после чего открыть содержимое ZIP файла и в нем открыть каталог .X.
Проверьте успешно ли компилируется программа и затем загрузите ее в микроконтроллер с помощью программатора PicKit3. После того как программа успешно загрузится в микроконтроллер вы увидите соответствующее сообщение об этом и после этого на экране ЖК дисплея начнут показываться данные времени и даты.
Если ничего не происходит, проверьте соединения в схеме, а также отрегулируйте уровень контрастности экрана ЖК дисплея с помощью потенциометра. Фактически, этот проект вы можете использовать в качестве цифровых часов на основе микроконтроллера PIC.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/* * File: RTC_with_PIC_main.c * Author: Aswinth * More info: www.circuitdigest.com * Created on 5 May, 2018, 6:42 PM */ #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 //We are running on 20MHz crystal //Define the LCD pins #define RS RD2 #define EN RD3 #define D4 RD4 #define D5 RD5 #define D6 RD6 #define D7 RD7 /*устанавливаем текущие значения времени и даты (измените их на свои)*/ int sec = 00; int min = 55; int hour = 10; int date = 06; int month = 05; int year = 18; /*Time and Date Set*/ #include <xc.h> #include "lcd.h" //библиотека для работы с ЖК дисплеем #include "PIC16F877a_I2C.h" // библиотека для работы с протоколом I2C #include "PIC16F877a_DS3231.h" //библиотека для работы с модулем DS3231 int main() { TRISD = 0x00; //контакты Port D конфигурируем для работы на вывод данных – к ним подключен ЖК дисплей Lcd_Start(); // инициализируем ЖК дисплей I2C_Initialize(100); //инициализируем I2C Master с частотой синхронизации 100KHz Set_Time_Date(); //устанавливаем дату и время в модуле RTC // показываем приветственное сообщение на экране ЖК дисплея Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String(" RTC with PIC"); Lcd_Set_Cursor(2,1); Lcd_Print_String(" -Circuit Digest"); __delay_ms(1500); //display for 1.5sec while(1) { Update_Current_Date_Time(); //считываем текущие дату и время из модуля RTC //разделяем считанные данные на отдельные символы чтобы затем отобразить их на ЖК дисплее char sec_0 = sec%10; char sec_1 = (sec/10); char min_0 = min%10; char min_1 = min/10; char hour_0 = hour%10; char hour_1 = hour/10; char date_0 = date%10; char date_1 = date/10; char month_0 = month%10; char month_1 = month/10; char year_0 = year%10; char year_1 = year/10; // отображаем время на ЖК дисплее Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String("TIME: "); Lcd_Print_Char(hour_1+'0'); Lcd_Print_Char(hour_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(min_1+'0'); Lcd_Print_Char(min_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(sec_1+'0'); Lcd_Print_Char(sec_0+'0'); // отображаем дату на ЖК дисплее Lcd_Set_Cursor(2,1); Lcd_Print_String("DATE: "); Lcd_Print_Char(date_1+'0'); Lcd_Print_Char(date_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(month_1+'0'); Lcd_Print_Char(month_0+'0'); Lcd_Print_Char(':'); Lcd_Print_Char(year_1+'0'); Lcd_Print_Char(year_0+'0'); __delay_ms(500); //обновляем каждые 0.5 sec } return 0; } |
Все файлы проекта можно скачать по этой ссылке.