В данной статье я хочу рассказать о том, как работать с модулем RTC (Часы Реального Времени) в микроконтроллере STM8L152C6T6. В работе использую отладочную плату STM8L-Discovery, на ней как раз такой камушек. Как обычно, прошивка переносима на другие камни восьмибитной серии (правда тут необходимо наличие драйвера ЖК индикатора), процесс описан в интернете многократно, а так же в моей статье «Термометр на STM8L-Discovery«. Как работать с ЖК-стекляшкой, в интернете довольно много статей, ( я использую стандартную библиотеку, она прикреплена к статье ). Но этого не скажешь о RTC.
RTC ( Real Time Clock — Часы Реального Времени ) — модуль микроконтроллера, по сути настоящие часы, текущее время хранится в особых регистрах. Начнем как раз с рассмотрения регистров, которые связаны с часами. В первую очередь это CLK_PCKENR2, его второй бит отвечает за включение тактирования часов. Далее с тактированием связан ещё и CLK_CRTCR, где определяется источник тактирования часов реального времени. Мы поставим третий бит, тем самым выбрав внешний часовой кварц. ( Вообще биты источника тактирования стоят стандартно, HSI, LSI, HSE, LSE от 0 до 3 соответственно. ) Теперь можно осуществить инициализацию таймера, а также контроллера LCD:
CLK_CKDIVR = 0; //отключаем основной предделитель
CLK_PCKENR2_bit.PCKEN22 = 1; //такт часов
CLK_CRTCR_bit.RTCSEL3 = 1; //выбран часовой кварц
LCD_Init(); //запущен дисплей
LCD_Contrast( 7 ); //контрастность дисплея на максимум
PC_DDR_bit.DDR7 = 1;
PC_CR1_bit.C17 = 1;
PE_DDR_bit.DDR7 = 1;
PE_CR1_bit.C17 = 1;
Продолжим дальше изучать регистры RTC. Для чтения и установки времени есть 6 регистров:
- TRC_TR1 — секунды
- RTC_TR2 — минуты
- RTC_TR3 — часы
- RTC_DR1 — день
- RTC_DR2 — месяц и день недели
- RTC_DR3 — год
С этими регистрами связана одна тонкость: надо смотреть время в шестнадцатиричной системе, тогда оно будет выглядеть правильно как в десятеричной системе. Поясню. Пусть текущее время 17:42. Тогда RTC_TR3 == 0x17, RTC_TR2 == 0x42; соответственно в десятичной системе RTC_TR3 == 23, RTC_TR2 == 66. Для работы с регистрами нам требуются функции для преобразования чисел в обе стороны. Вот так:
int GetNum( int i )
{
if ( i == 0x00 ) return 0;
if ( i == 0x01 ) return 1;
if ( i == 0x02 ) return 2;
if ( i == 0x03 ) return 3;
if ( i == 0x04 ) return 4;
if ( i == 0x05 ) return 5;
if ( i == 0x06 ) return 6;
if ( i == 0x07 ) return 7;
if ( i == 0x08 ) return 8;
if ( i == 0x09 ) return 9;
if ( i == 0x10 ) return 10;
if ( i == 0x11 ) return 11;
if ( i == 0x12 ) return 12;
if ( i == 0x13 ) return 13;
if ( i == 0x14 ) return 14;
if ( i == 0x15 ) return 15;
if ( i == 0x16 ) return 16;
if ( i == 0x17 ) return 17;
if ( i == 0x18 ) return 18;
if ( i == 0x19 ) return 19;
if ( i == 0x20 ) return 20;
if ( i == 0x21 ) return 21;
if ( i == 0x22 ) return 22;
if ( i == 0x23 ) return 23;
if ( i == 0x24 ) return 24;
if ( i == 0x25 ) return 25;
if ( i == 0x26 ) return 26;
if ( i == 0x27 ) return 27;
if ( i == 0x28 ) return 28;
if ( i == 0x29 ) return 29;
if ( i == 0x30 ) return 30;
if ( i == 0x31 ) return 31;
if ( i == 0x32 ) return 32;
if ( i == 0x33 ) return 33;
if ( i == 0x34 ) return 34;
if ( i == 0x35 ) return 35;
if ( i == 0x36 ) return 36;
if ( i == 0x37 ) return 37;
if ( i == 0x38 ) return 38;
if ( i == 0x39 ) return 39;
if ( i == 0x40 ) return 40;
if ( i == 0x41 ) return 41;
if ( i == 0x42 ) return 42;
if ( i == 0x43 ) return 43;
if ( i == 0x44 ) return 44;
if ( i == 0x45 ) return 45;
if ( i == 0x46 ) return 46;
if ( i == 0x47 ) return 47;
if ( i == 0x48 ) return 48;
if ( i == 0x49 ) return 49;
if ( i == 0x50 ) return 50;
if ( i == 0x51 ) return 51;
if ( i == 0x52 ) return 52;
if ( i == 0x53 ) return 53;
if ( i == 0x54 ) return 54;
if ( i == 0x55 ) return 55;
if ( i == 0x56 ) return 56;
if ( i == 0x57 ) return 57;
if ( i == 0x58 ) return 58;
if ( i == 0x59 ) return 59;
return 60;
}
int CodNum( int i )
{
if ( i == 0 ) return 0x00;
if ( i == 1 ) return 0x01;
if ( i == 2 ) return 0x02;
if ( i == 3 ) return 0x03;
if ( i == 4 ) return 0x04;
if ( i == 5 ) return 0x05;
if ( i == 6 ) return 0x06;
if ( i == 7 ) return 0x07;
if ( i == 8 ) return 0x08;
if ( i == 9 ) return 0x09;
if ( i == 10 ) return 0x10;
if ( i == 11 ) return 0x11;
if ( i == 12 ) return 0x12;
if ( i == 13 ) return 0x13;
if ( i == 14 ) return 0x14;
if ( i == 15 ) return 0x15;
if ( i == 16 ) return 0x16;
if ( i == 17 ) return 0x17;
if ( i == 18 ) return 0x18;
if ( i == 19 ) return 0x19;
if ( i == 20 ) return 0x20;
if ( i == 21 ) return 0x21;
if ( i == 22 ) return 0x22;
if ( i == 23 ) return 0x23;
if ( i == 24 ) return 0x24;
if ( i == 25 ) return 0x25;
if ( i == 26 ) return 0x26;
if ( i == 27 ) return 0x27;
if ( i == 28 ) return 0x28;
if ( i == 29 ) return 0x29;
if ( i == 30 ) return 0x30;
if ( i == 31 ) return 0x31;
if ( i == 32 ) return 0x32;
if ( i == 33 ) return 0x33;
if ( i == 34 ) return 0x34;
if ( i == 35 ) return 0x35;
if ( i == 36 ) return 0x36;
if ( i == 37 ) return 0x37;
if ( i == 38 ) return 0x38;
if ( i == 39 ) return 0x39;
if ( i == 40 ) return 0x40;
if ( i == 41 ) return 0x41;
if ( i == 42 ) return 0x42;
if ( i == 43 ) return 0x43;
if ( i == 44 ) return 0x44;
if ( i == 45 ) return 0x45;
if ( i == 46 ) return 0x46;
if ( i == 47 ) return 0x47;
if ( i == 48 ) return 0x48;
if ( i == 49 ) return 0x49;
if ( i == 50 ) return 0x50;
if ( i == 51 ) return 0x51;
if ( i == 52 ) return 0x52;
if ( i == 53 ) return 0x53;
if ( i == 54 ) return 0x54;
if ( i == 55 ) return 0x55;
if ( i == 56 ) return 0x56;
if ( i == 57 ) return 0x57;
if ( i == 58 ) return 0x58;
if ( i == 59 ) return 0x59;
return 0x60;
}
Думаю, все ясно без комментариев. Но, по верным замечанием читателей, выше приведённый алгоритм неоправданно громоздкий. Цель этого алгоритма наглядно показать преобразование чисел. Но проект можно оптимизировать, переписав короче две показанные выше функции. Тогда поучится вот так:
int GetNum( int i )
{
return (int)(i/16*10 + i%16);
}
int CodNum( int i )
{
return (int)(i/10*16 + i%10);
}
Коротко и красиво, правда, для кого-то может стать менее понятно. В данном случае мы разбиваем на разряды число в исходной системе счисления и заново собираем по разрядам в целевой системе счисления.
Есть ещё нюансы работы с регистрами времени и даты. Дело в том, что это буферные регистры, при чтении данных из них обновление этих регистров приостанавливается. Потому необходимо читать все регистры от начала до конца ( после этого обновление регистров возобновится), иначе фактически произойдет зависание часов реального времени. Перед чтением регистров ещё надо удостовериться, что они обновлены, то есть в них загружены данные из фактических регистров RTC, об этом символизирует поднятый бит RSF в RTC_ISR1. Вот таким образом мы читаем регистры часов:
long int data[6];
while ( !RTC_ISR1_bit.RSF ); // ожидаем загрузку данных
//читаем все регистры
data[0] = GetNum( RTC_TR1 );
data[1] = GetNum( RTC_TR2 );
data[2] = GetNum( RTC_TR3 );
data[3] = GetNum( RTC_DR1 );
data[4] = GetNum( RTC_DR2 );
data[5] = GetNum( RTC_DR3 );
num_t = data[2]*10000 + data[1]*100 + data[0]; // собираем строчку времени
LCD_Write_Int( &num_t, -1 ); // пишем ее на экран
LCD_RAM7 = 0x88; // ставим разделительные двоеточия
С чтением разобрались. Но если мы не настроим время, то читать и смысла не будет. Скажу честно, 2/3 времени работы над проектом у меня заняло именно создание простого алгоритма настройки часов. Как видно из алгоритма чтения, я вывожу на экран лишь время, дату не трогаю, поэтому и настраивать буду лишь время. Настройка будет производиться в прерывании от кнопки, поэтому все настроим соответственно:
PC_DDR_bit.DDR1 = 0; //пин с кнопкой на вход
PC_CR1_bit.C11 = 1; //в режиме push-pull
PC_CR2_bit.C21 = 1; //разрешены прерывания
EXTI_CR1_bit.P1IS = 2; //прерывания по спаду
asm( «RIM» ); //глобальное разрешение прерываний
Также нам потребуются светодиоды, вот их настройка:
PC_DDR_bit.DDR7 = 1;
PC_CR1_bit.C17 = 1;
PE_DDR_bit.DDR7 = 1;
PE_CR1_bit.C17 = 1;
Думаю понятно.
Теперь алгоритм настройки. Зажимаем кнопку — загорается синий светодиод — это значит будем настраивать минуты. Не отпускаем кнопку, пока не загорится нужное число минут, после этого отпускаем кнопку. при повторном зажимание кнопки загорится зелёный светодиод и точно также будут настраиваться часы. При повторном зажимании снова минуты и так далее. Кратковременное нажатие приводит к сбросу секунд в 0.
Вся настройка происходит в прерывании:
#define KEYPRESSED !PC_IDR_bit.IDR1
long int data[6];
enum category { MINUTE,
HOUR };
category choice = MINUTE;
void delay( void )
{
for ( long int i = 0; i < 200000; i++ )
;
return;
}
#pragma vector=EXTI1_vector
__interrupt void Pin1_interrupt(void)
{
delay(); //чуть-чуть подождем
if ( !KEYPRESSED ) //если кнопку отпустили, настроим секунды
{
PC_ODR_bit.ODR7 = 1; //зажжём синий…
PE_ODR_bit.ODR7 = 1; //…и зелёный светодиоды
delay(); //подождем
PC_ODR_bit.ODR7 = 0; //потушим…
PE_ODR_bit.ODR7 = 0; //…светодиоды
RTC_WPR = 0xCA; //снимем защиту
RTC_WPR = 0x53;
RTC_ISR1_bit.INIT = 1; // входим в режим настройки
while ( RTC_ISR1_bit.INITF == 0 ); //подождем, пока нам разрешат войти
RTC_TR1 = CodNum( 0 ); //запишем в секунды 0
RTC_ISR1_bit.INIT = 0; //выйдем из режима настройки
RTC_WPR = 0x00; //введем блокировку
EXTI_SR1_bit.P1F = 1; //сброс флага прерывания, чтобы вернуться в код
return; // окончательно выйдем из прерывания PC_DDR_bit.DDR7 = 1;
PC_CR1_bit.C17 = 1;
PE_DDR_bit.DDR7 = 1;
PE_CR1_bit.C17 = 1;
}
if ( choice == MINUTE ) // настраиваем минуты
{
long int i = 0;
PC_ODR_bit.ODR7 = 1; // зеленый свет
while ( KEYPRESSED ) //пока нажата кнопка
{
LCD_Write_Int( &i, -1 ); //пишем время
delay(); //ждем
i++; //инкременитируем счетчик
LCD_Write_Int( &i, -1 );//пишем время
if ( i == 60 ) i = 0; // если дошли до 60 — сброс в начало
}
PC_ODR_bit.ODR7 = 0; //погасим светодиод
//запишем занчение минут в соответствующий регистр
RTC_WPR = 0xCA;
RTC_WPR = 0x53;
RTC_ISR1_bit.INIT = 1;
while ( RTC_ISR1_bit.INITF == 0 );
RTC_TR2 = CodNum( i — 1 );
RTC_ISR1_bit.INIT = 0;
RTC_WPR = 0x00;
choice = HOUR; // в следующий раз будем настраивать часы
EXTI_SR1_bit.P1F = 1;
return;
}
if ( choice == HOUR ) // аналогично для настройки часов
{
long int i = 0;
PE_ODR_bit.ODR7 = 1;
while ( KEYPRESSED )
{
LCD_Write_Int( &i, -1 );
delay();
i++;
LCD_Write_Int( &i, -1 );
if ( i == 24 ) i = 0; // теперь вернемся в 0 при достижении значения 24
}
PE_ODR_bit.ODR7 = 0;
RTC_WPR = 0xCA;
RTC_WPR = 0x53;
RTC_ISR1_bit.INIT = 1;
while ( RTC_ISR1_bit.INITF == 0 );
RTC_TR3 = CodNum( i — 1 );
RTC_ISR1_bit.INIT = 0;
RTC_WPR = 0x00;
choice = MINUTE; //и в следующий раз будем настраивать минуты
EXTI_SR1_bit.P1F = 1;
return;
}
}
Да, как видим регистры защищены от записи, поэтому нужно ее снять, как показано в исходном коде. В регистр RTC_WPR вводится указанный ключ. После этого заходим в режим редактирования. После редактирования обратно вводим блокировку ( на всякий случай ) записью в RTC_WPR любого числа, отличного от ключа.
Если кто захочет собрать на отдельной плате — вот блок — схема, как все соединено на отладочной плате. Конкретные ножки не пишу — вы можете взять свой камушек и дисплей. В конце список деталей тоже есть.
Вот как это выглядит:
Ниже прикреплены архивы с проектом и библиотекой дисплея, а также собранная прошивка «RTC.HEX».
Список радиоэлементовОбозначение
Тип
Номинал
Количество
ПримечаниеМагазинМой блокнот
МК STM8STM8L152C61
Конденсатор10 нФ1
Резистор1 — 10 кОм1
КнопкаЗамыкающая1
Кварц32768 Гц1
ЖК-дисплей6 знакомест1
Добавить все
Скачать список элементов (PDF)
Прикрепленные файлы:
- RTC.zip (4045 Кб)
- LCD_library.zip (5 Кб)
- RTC.hex (12 Кб)