Реклама ⓘ
Главная » Микроконтроллеры
Призовой фонд
на апрель 2024 г.
1. 100 руб.
От пользователей

Похожие статьи:


Реклама ⓘ

RTC на STM8L-Discovery

В данной статье я хочу рассказать о том, как работать с модулем 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".

Список радиоэлементов

Обозначение Тип Номинал Количество ПримечаниеМагазинМой блокнот
МК STM8
STM8L152C6
1 Поиск в магазине ОтронВ блокнот
Конденсатор10 нФ1 Поиск в магазине ОтронВ блокнот
Резистор1 - 10 кОм1 Поиск в магазине ОтронВ блокнот
КнопкаЗамыкающая1 Поиск в магазине ОтронВ блокнот
Кварц32768 Гц1 Поиск в магазине ОтронВ блокнот
ЖК-дисплей6 знакомест1 Поиск в магазине ОтронВ блокнот
Добавить все

Скачать список элементов (PDF)

Прикрепленные файлы:

Теги:

Опубликована: Изменена: 14.04.2015 0 0
Я собрал 0 2
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 2.4 Проголосовало: 2 чел.

Комментарии (9) | Я собрал (0) | Подписаться

0
Zlodey #
Какие то непонятные простыни кода в функциях

int GetNum( int i )
int CodNum( int i )

Пробелы лучше ставить вот так


int GetNum (int i)
int CodNum (int i)
Ответить
0

[Автор]
Стальной #
А мне по другому нравится.
Дело в том, что по этим проектам я друзьям объяснял программирование, и именно такая реализация оказалась им понятной
Ответить
0
boots #
Как же много условных операторов, неужели не нашлось других реализаций?
Ответить
-1

[Автор]
Стальной #
Я выбрал самый понятный метод, конечно нашлось. Чуть позже добавлю.
Ответить
+1
boots #
Этот метод претендует на первичный запуск, чтобы вы могли убедиться что ваш код работает. Но перед выкладыванием нужно было немного все же оптимизировать его. Условные операторы можно заменить циклом это бы было не так громоздко.
Ответить
0
Pavel #
Добрый день.
На STM8151L сделал по Вашему примеру.
Инициализация проходит, но при считывании из RTC секунды не обновляются.
То ли RTC не запускаются, то ли он медленно обновляется.
В режиме отладке, загрузка проходит успешно т.е. это условие выполнятся while ( !RTC_ISR1_bit.RSF ); // ожидаем загрузку данных

В Вашем примере процессор работает на HSI ?

long int data[6];

CLK_CKDIVR = 0; //отключаем основной предделитель
CLK_PCKENR2_bit.PCKEN22 = 1; //такт часов
CLK_CRTCR_bit.RTCSEL3 = 1; //выбран часовой кварц

//инициализация часов
RTC_WPR = 0xCA; //снимем защиту
RTC_WPR = 0x53;
RTC_ISR1_bit.INIT = 1; // входим в режим настройки
while ( RTC_ISR1_bit.INITF == 0 ); //подождем, пока нам разрешат войти
RTC_TR1 = 0x10; //запишем секунды
RTC_TR2 = 1; //минуты
RTC_TR3 = 0; //часы
RTC_ISR1_bit.INIT = 0; //выйдем из режима настройки
RTC_WPR = 0x00; //введем блокировку


while (1)
{
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 );
}
}
Ответить
0

[Автор]
Стальной #
Вроде бы на HSI.
А минуты обновляются? Или часы просто стоят? Часовой кварц тикает?
Ответить
0
Pavel #
В прошлом году забросил этот проект. Решил вернуться. Часовой кварц не тикает!
Я в отладчике мониторю переменные и они одни и те же.
Ответить
0

[Автор]
Стальной #
Гексу зашить не пробовали?
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется сила тока?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

Pickit 2 - USB-программатор PIC-микроконтроллеров
Pickit 2 - USB-программатор PIC-микроконтроллеров
Конструктор регулируемого преобразователя напряжения LM317 Печатная плата для усилителя "LM3886 + AD825"
вверх