Часы с будильником на STM32.

Всем доброго времени суток! И сегодня мы снова поговорим о модуле RTC микроконтроллеров STM32. А точнее мы разберем его функции более подробно. Напомню, что в предыдущей статье мы начали знакомиться с часами реального времени и написали программу для их инициализации и запуска (статья тут).

Так вот, сегодня мы сделаем часы с будильником 😉 Я для этого по традиции буду использовать плату STM32F4 Discovery. Но для начала давайте разберемся с теоретической частью. И для начала поковыряемся в файлах библиотеки Standard Peripheral Library, относящихся к модулю RTC. Как обычно, в SPL мы находим два нужных нам файла – stm32f4xx_rtc.c и stm32f4xx_rtc.h. В первой реализованы функции на все случаи жизни, но мы поговорим о них немного позже. А сначала рассмотрим определения структур из заголовочного файла библиотеки. И первая из них:

typedef struct
{
  uint32_t RTC_HourFormat;     
  uint32_t RTC_AsynchPrediv;   
  uint32_t RTC_SynchPrediv;  
}RTC_InitTypeDef;

Эта структура, как понятно из ее названия, необходима при инициализации часов реального времени. И более того мы ее уже использовали в предыдущей статье про RTC )

Далее идет структура, которая является по большому счету базовой для всех функций, реализованных в SPL:

typedef struct
{
  uint8_t RTC_Hours;    
  uint8_t RTC_Minutes;  
  uint8_t RTC_Seconds
  uint8_t RTC_H12;      
}RTC_TimeTypeDef;

Эта структура собственно позволяет задать время – то есть часы, минуты и секунды. Так же тут можно задать формат отображения времени – 12 или 24 часовой. В принципе в SPL снабжено соответствующими комментариями, кроме того название любой переменной однозначно позволяет понять для чего она будет использоваться, поэтому тут и пояснять особо нечего. Тем не менее продолжим =)

typedef struct
{
  uint8_t RTC_WeekDay;   
  uint8_t RTC_Month;
  uint8_t RTC_Date;   
  uint8_t RTC_Year;
}RTC_DateTypeDef;

Структура RTC_DateTypeDef аналогична предыдущей структуре RTC_TimeTypeDef, разница лишь в том, что она позволяет задать день недели, месяц, дату и год. И последнее:

typedef struct
{
  RTC_TimeTypeDef RTC_AlarmTime;     
  uint32_t RTC_AlarmMask;            
  uint32_t RTC_AlarmDateWeekDaySel;  
  uint8_t RTC_AlarmDateWeekDay;      
}RTC_AlarmTypeDef;

Эту структуру мы будем использовать для настройки будильника. И, пожалуй, стоит обсудить ее поля поподробнее.

Первое поле — RTC_AlarmTime — представляет из себя уже упомянутую структуру RTC_TimeTypeDef, предназначенную для установки времени пробуждения, то есть времени срабатывания нашего будильника.

Поле RTC_AlarmDateWeekDaySel может иметь следующие значения:

RTC_AlarmDateWeekDaySel_Date 
RTC_AlarmDateWeekDaySel_WeekDay

При выборе первого из этих значений будильник будет срабатывать в определенный день месяца, то есть при наступлении даты, которую мы передадим в другое поле этой структуры, а именно в поле RTC_AlarmDateWeekDay.

Если же мы выбрали значение RTC_AlarmDateWeekDaySel_WeekDay, то будильник будет срабатывать в определенный день недели. Нужный день недели мы записываем все в то же поле RTC_AlarmDateWeekDay.

И, наконец, осталось рассмотреть RTC_AlarmMask. Назначение этого поля следующее. Если мы хотим настроить будильник так, чтобы он срабатывал в определенное время, независимо от даты и дня недели, то необходимо задать:

RTC_AlarmMask = RTC_AlarmMask_DateWeekDay

Все оказалось довольно-таки просто =) Можно двигаться дальше. Не будем отдельно обсуждать каждую из функций, реализованных в файле stm32f4xx_rtc.c, лучше напишем программу, в которой будем использовать некоторые из них. Соответственно в процессе написания кода разберемся как и для чего нужно использовать ту или иную функцию. А пример будет такой – установим определенное время и запустим часы, а кроме того установим будильник и создадим обработчик прерывания для него. В общем, пора бы уже перейти к делу 😉

Для начала, как обычно, подключаем все файлы, которые нам понадобятся :

/*************************************************************************************/
#include "stm32f4xx.h"
#include "stm32f4xx_exti.h"
#include "stm32f4xx_rtc.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_pwr.h"
#include "stm32f4xx_conf.h"
#include "system_stm32f4xx.h"
#include "misc.h"
 
/*************************************************************************************/

И сразу же объявим переменные:

/*************************************************************************************/
RTC_InitTypeDef rtc;
RTC_TimeTypeDef time;
RTC_TimeTypeDef alarmTime;
RTC_AlarmTypeDef alarm;
EXTI_InitTypeDef exti;
NVIC_InitTypeDef NVIC_InitStructure;
int i;
 
/*************************************************************************************/

Для начала работы с часами реального времени необходимо произвести их инициализацию:

/*************************************************************************************/
void initRTC()
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
 
    PWR_BackupAccessCmd(ENABLE);
 
    RCC_BackupResetCmd(ENABLE);
    RCC_BackupResetCmd(DISABLE);
 
    RCC_LSICmd(ENABLE);
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
    RCC_RTCCLKCmd(ENABLE);
 
    RTC_StructInit(&rtc);
    rtc.RTC_HourFormat = RTC_HourFormat_24;
    rtc.RTC_SynchPrediv = 0x7FFF;
    RTC_Init(&rtc);
}
 
/*************************************************************************************/

В принципе тут все то же самое, что и в предыдущей статье про модуль RTC, поэтому останавливаться на этом не будем )

Итак, функция main()…Сразу же вызываем функцию глобального разрешения прерываний и вызываем нашу функцию инициализации:

__enable_irq();
initRTC();

Готово! Идем дальше. Установим время – пусть это будет например 20:00:00 (то есть 8 вечера, ноль часов, ноль минут 😉 ):

time.RTC_H12 = RTC_HourFormat_24;
time.RTC_Hours = 20;
time.RTC_Minutes = 00;
time.RTC_Seconds = 00;
 
RTC_SetTime(RTC_Format_BIN, &time);

Для установки времени используем функцию RTC_SetTime(), куда в качестве аргументов передаем уже упомянутую структуру с нашим значением времени и используемый формат (возможно два варианта — RTC_Format_BIN и RTC_Format_BCD).

Далее необходимо настроить 17 линию внешних прерываний, поскольку именно она подключена к событию срабатывания будильника. Кроме того сразу же настроим и соответствующее прерывание RTC_Alarm_IRQn:

EXTI_ClearITPendingBit(EXTI_Line17);
exti.EXTI_Line = EXTI_Line17;
exti.EXTI_Mode = EXTI_Mode_Interrupt;
exti.EXTI_Trigger = EXTI_Trigger_Rising;
exti.EXTI_LineCmd = ENABLE;
EXTI_Init(&exti);
 
NVIC_InitStructure.NVIC_IRQChannel = RTC_Alarm_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

Теперь пришло время установить время включения будильника, пусть будет 20:00:15. То есть через 15 секунд после включения контроллера должен сработать будильник, и программа улетит в обработчик прерывания:

RTC_AlarmStructInit(&alarm);
alarmTime.RTC_H12 = RTC_HourFormat_24;
alarmTime.RTC_Hours = 20;
alarmTime.RTC_Minutes = 00;
alarmTime.RTC_Seconds = 15;
 
alarm.RTC_AlarmTime = alarmTime;
alarm.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay;
 
RTC_SetAlarm(RTC_Format_BIN, RTC_Alarm_A, &alarm);

У STM32F4 есть Alarm_A и Alarm_B, мы остановим свой выбор на первом из будильников ) В общем то теперь нам осталось немного:

RTC_OutputConfig(RTC_Output_AlarmA, RTC_OutputPolarity_High);
RTC_ITConfig(RTC_IT_ALRA, ENABLE);
RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
RTC_ClearFlag(RTC_FLAG_ALRAF);

Настраиваем выход, включаем прерывание, запускаем сам будильник, ну и чистим флаг на всякий случай. В основном цикле программы ничего делать не будем:

while(1)
{				
}

Казалось бы все, но нет! Мы совсем забыли про обработчик прерывания:

void RTC_Alarm_IRQHandler()
{
    if(RTC_GetITStatus(RTC_IT_ALRA) != RESET)
    {   
        RTC_ClearITPendingBit(RTC_IT_ALRA);
        EXTI_ClearITPendingBit(EXTI_Line17);
    } 
}

Здесь мы проверим, вызвано ли прерывание срабатыванием будильника Alarm_A и очистим соответствующие флаги, вот и все )

Зашиваем программу в микроконтроллер и теперь в режиме отладки можем проверить, что же вышло. Запускаем, ждем заветные 15 секунд, и вот он результат:
RTC

Как видите программа ушла на обработку прерывания как раз через 15 секунд после начала выполнения. Таким образом, мы с вами добились того, чего и хотели! Собственно, на этом все на сегодня, с установкой времени и будильника мы полностью разобрались ) Если возникнут какие-нибудь вопросы – обязательно пишите в комментариях к статье.

Понравилась статья? Поделись с друзьями!

Часы с будильником на STM32.: 35 комментариев
  1. Огромное Вам спасибо за статьи по stm! Подскажите пожалуйста куда посмотреть для поднятия полного RS-232? На stm32f3discovery

      • Подскажите как это вывести на lcd 1602 В келе у меня все работает часы идут. в while прописываю
        hd44780_clear();
        hd44780_printf(«%0.2d:%0.2d:%0.2d»,time.RTC_Hours, time.RTC_Minutes, time.RTC_Seconds );
        delay_ms(5000);

        По идее каждые 20 сек информация на дисплее должна обновляться но там постоянно 20:00:00 и ничего не меняется.

        • Для тех, кто прочитает это когда-нибудь: time.RTC_Hours etc.. используется для установки времени в RTC! Она не показывает текущее время!

  2. и снова здравствуйте )
    столкнулся со следующей проблемой:
    работал в кейле с STM32 Discovery все было хорошо, прошивалось, работала отладка.
    купил STM32F4 Discovery, поключил, кейл пишет: «No ST-LINK detected»
    При чем «STM32 ST-LINK Utility» с платкой прекрасно работает, а кейл не видит.
    я так понимаю дело в драйверах ?

  3. в настройках ничего не трогаю, подключаю STM32 Discovery, кейл его видит:
    http://s019.radikal.ru/i602/1401/5f/e2a5d201129d.png
    потом, опять же, ничего не меняя в настройках подключаю STM32F4 Discovery, кейл его уже не видит:
    http://i018.radikal.ru/1401/c3/400089da3dfb.png
    если потом снова подключить STM32 Discovery, то все работает.
    сначала думал что плата отладочная неисправна, но «STM32 ST-LINK Utility» с ней работает, уже прошивал.
    но вот еще какая особенность, вот так выглядит диспетчер устройств если подключить F1:
    http://i047.radikal.ru/1401/05/4c0d12323228.jpg
    а вот так если подключить F4:
    http://s003.radikal.ru/i202/1401/7b/527e65e07a65.jpg

    Как я понял, встроенные программаторы на платах разные, и Кейл надо настраивать по-разному для них, но как настроить для F4 не знаю.

    • Надо в Кейле в меню Flash-Configure flash tools выбрать st-link (как и для STM32Discovery), затем кнопочка Settings, откроется новое окно, там жмем Add и надо выбрать STM32F4xx Flash. Ну и в этом же окне вкладка Debug и там SW.

  4. Спасибо за Ваши статьи. По ним изучаю STM32. Вопрос по этой статье — как получить данные по времени и дате? Я так понимаю RTC_GetTime() ? Можно какой-нибудь пример — не врубаюсь в структуру процедуру.

    • Ну вот, например:
      объявляем переменную:
      RTC_TimeTypeDef currrentTime;
      В коде вызываем функцию:
      RTC_GetTime(RTC_Format_BIN, &currentTime);
      В итоге получаем:
      currentTime.RTC_Hours — часы
      currentTime.RTC_Minutes — минуты
      currentTime.RTC_Seconds — секунды

  5. Здравствуйте, уважаемые. В общем собрал я проект с RTC под stm32f103, и ничего, как водится не работает. При инициализации виснет, тактирование не начинается. Подскажите, а может сначала нужно альтернативную функцию ножек OSC32, ну вроде вот этого что-то прописать: GPIO_Mode_AF_PP. В общем, подскажите, люди добрые

  6. Разве кейл поддерживает эмуляцию переферии stm32? Я так понимаю вы пользуете кейл 5… как вы прсматриваете регистры ?

    • У меня Кейл 4. В принципе, я почти всегда в железе отлаживаю, только когда начинал с STM32 (давным-давно) пользовался Кейловским симулятором. А так спектр возможностей симулятора довольно широкий )

  7. Да это я знаю… когда я пользовал LPC2478 то тоже отлаживал в симуляторе многие вещи… кейл прекрасно симулирует этот камень.. и всю почти периферию. а вот stm32f4 не хочет симулировать. Нет у него таких возможностей и в планах тоже.

    • SPL в принципе не особо сильно отличается для разных семейств, так что примерно также как и для F4.

  8. Попробовал, определений типов таких же не нашел, буду штудировать reference manual

  9. как-то глупо каждый раз выставлять часы после выключения. там же я надеюсь есть своя батарейка для rtc

  10. тоесть получается если использовать прерывание от будильника то нужно пожертвовать одной ножкой контроллера и не использовать ее?

      • ну если при срабатывании будильник выставляет высокий/низкий уровень на ногу контроллера которая настроена на вход с прерыванием. то нога стает недоступна для нормального использования

          • цытата: «17 линию внешних прерываний»
            хотя и правда. я не подумал, что в каждом порту по 16 линий. с толку сбило «внешнее прерывание»
            тогда получается что все круто и без костылей) спасибо

        • Не за что )
          Там этих EXTI больше 30 прерываний (для STM32F3 — 36), а к физическим ножкам подключены первые 16, остальные — на определенные события.

  11. Добрый день! Подскажите
    Работаю с f103ret6
    Мне необходимо подключить 10 внешних EXTI прерывания
    у меня есть
    EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, EXTI3_IRQn, EXTI4_IRQn, EXTI9_5_IRQn, EXTI15_10_IRQn
    Могу ли я использовать к примеру EXTI9_5_IRQn так
    EXTIInit(EXTI_Line6, ENABLE, EXTI_Mode_Interrupt, EXTI_Trigger_Falling, EXTI9_5_IRQn, GPIO_PortSourceGPIOB, GPIO_PinSource6);
    EXTIInit(EXTI_Line7, ENABLE, EXTI_Mode_Interrupt, EXTI_Trigger_Falling, EXTI9_5_IRQn, GPIO_PortSourceGPIOB, GPIO_PinSource7);

    EXTIInit // полная настройка внешнего прерывания

    При использовании к примеру EXTI3_IRQn, EXTI4_IRQn всё работает
    EXTIInit(EXTI_Line3, ENABLE, EXTI_Mode_Interrupt, EXTI_Trigger_Falling, EXTI3_IRQn, GPIO_PortSourceGPIOA, GPIO_PinSource3);
    EXTIInit(EXTI_Line4, ENABLE, EXTI_Mode_Interrupt, EXTI_Trigger_Falling, EXTI4_IRQn, GPIO_PortSourceGPIOC, GPIO_PinSource4);

    А в случае с EXTI начиная с 5-го нет

    • Если всё таки нужна расшифровка EXTIInit

      /* Инициализация внешних прерываний
      */
      #define EXTIInit(line, linecmd, mode, trigger, IRQn, port, pin) \
      GPIO_EXTILineConfig(port, pin); \
      EXTI_InitStructure.EXTI_Line = line; \
      EXTI_InitStructure.EXTI_Mode = mode; \
      EXTI_InitStructure.EXTI_Trigger = trigger; \
      EXTI_InitStructure.EXTI_LineCmd = linecmd; \
      EXTI_Init( & EXTI_InitStructure); \
      NVIC_InitStructure.NVIC_IRQChannel = IRQn; \
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F; \
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F; \
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; \
      NVIC_Init( & NVIC_InitStructure);

    • Так нашёл ошибку там нужно что бы прерывание то же что бы называлось EXTI9_5_IRQHandler(void)

      Но от этого не легче.
      так как в регистрах при сработке того или другого прерывания взводятся оба EXTI->PR->PR6 = 1 и EXTI->PR->PR7 = 1
      Следовательно я ни как не смогу их разделить?

      • Нужно еще посмотреть по всем регистрам/функциям — по идее должен быть способ, чтобы определить какое именно событие его вызвало.

  12. Странное дело. Используя HAL_RTC_GetTime() НЕ работает при обычном запуске (RESET от кнопки на плате), но работает если включить отладку Debug в Keil. Причем просто — Start Debug Session and Run.
    А без отладки — не считает, хотя прошивку не меняешь вообще.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *