Ранее в посте Микроконтроллеры AVR: пример работы с часами реального времени DS1302 отмечалось, что DS1302 было бы довольно глупо использовать с микроконтроллерами STM32, так как у них есть встроенные часы реального времени. Давайте же попробуем разобраться, как происходит работа с этими встроенными RTC, и что они умеют.
Как обычно, для экспериментов я использовал плату Nucleo-F411RE , но описанные далее шаги можно повторить и для любой другой отладочной платы. Обратите внимание, что в мире STM32 встречаются микроконтроллеры с RTC, но без функции календаря . Их RTC умеет работать со временем, но не с датами. В качестве примера можно привести используемый в Blue Pill микроконтроллер STM32F103C8T6. Чтобы определить, есть ли в микроконтроллере хардварный календарь, внимательно читайте даташит.
Итак, для включения RTC следует открыть STM32CubeMX и произвести следующие изменения в проекте:
- Во вкладке Pinout перейдите в Configuration → Peripherals → RCC. В выпадающем списке Low Speed Clock (LSE) выберите «Crystal/Ceramic Resonator». Этим мы говорим микроконтроллеру использовать внешний часовой кварц на 32768 Гц. Разумеется, предполагается, что он есть на вашей плате. Можно обойтись и внутренним генератором (LSI). Однако точность часов в этом случае будет оставлять желать лучшего.
- В той же вкладке Pinout перейдите в Configuration → Peripherals → RTC, поставьте галочки «Activate Clock Source» и «Activate Calendar». Этим мы, собственно, включаем RTC.
- Во вкладке Configuration появится кнопка RTC. Нажмите на нее и проверьте, что используется 24-х часовое время (Hour Format) и бинарный формат (Data Format).
Важно! При проектировании собственной платы нужно серьезно отнестись к выбору конденсаторов рядом с часовым кварцем. Если переборщить с емкостью этих конденсаторов, часы будут идти слишком медленно. На практике неплохо работают конденсаторы на 10 пФ.
Наконец, во вкладке Clock Configuration будет не лишним перепроверить, что LSE действительно используется:
В Makefile понадобится добавить пару файлов к списку C_SOURCES:
$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rtc_ex.c
Теперь можно спокойно использовать RTC в коде. Например, получение даты и времени осуществляется так:
RTC_DateTypeDef date ;
HAL_StatusTypeDef res ;
res = HAL_RTC_GetTime ( & hrtc , & time , RTC_FORMAT_BIN ) ;
if ( res != HAL_OK ) {
UART_Printf ( «HAL_RTC_GetTime failed: %d r n » , res ) ;
return ;
}
res = HAL_RTC_GetDate ( & hrtc , & date , RTC_FORMAT_BIN ) ;
if ( res != HAL_OK ) {
UART_Printf ( «HAL_RTC_GetDate failed: %d r n » , res ) ;
return ;
}
А так происходит их изменение:
uint8_t year , uint8_t month , uint8_t day ,
uint8_t hour , uint8_t min , uint8_t sec ,
uint8_t dow ) {
HAL_StatusTypeDef res ;
RTC_TimeTypeDef time ;
RTC_DateTypeDef date ;
memset ( & time , 0 , sizeof ( time ) ) ;
memset ( & date , 0 , sizeof ( date ) ) ;
date. WeekDay = dow ;
date. Year = year ;
date. Month = month ;
date. Date = day ;
res = HAL_RTC_SetDate ( & hrtc , & date , RTC_FORMAT_BIN ) ;
if ( res != HAL_OK ) {
UART_Printf ( «HAL_RTC_SetDate failed: %d r n » , res ) ;
return — 1 ;
}
time . Hours = hour ;
time . Minutes = min ;
time . Seconds = sec ;
res = HAL_RTC_SetTime ( & hrtc , & time , RTC_FORMAT_BIN ) ;
if ( res != HAL_OK ) {
UART_Printf ( «HAL_RTC_SetTime failed: %d r n » , res ) ;
return — 2 ;
}
return 0 ;
}
Заметьте, что структура RTC_TimeTypeDef
помимо самого времени хранит ряд дополнительных полей. Они могут не очень аккуратно заполняться процедурой HAL_RTC_GetTime
. Поэтому при изменении времени структуру лучше заполнить самостоятельно. Иначе можно увидеть, как часы показывают 27 часов, и налететь на прочие потрясающие баги.
Следует также иметь в виду, что в RTC не предусмотрено защиты от дурака, что позволяет выставить дату вроде 31 февраля. Если пользователь вашего устройства может изменять дату, вы должны предусмотреть в коде валидацию вводимых данных, знающую про високосные года и всякое такое. Эта тема уже поднималась в данном блоге, поэтому снова на ней я останавливаться не буду. Пример валидации вы найдете в полной версии исходников к данному посту (ищите в конце текста).
Для питания RTC нужно подать от 1.6 В до 3.6 В на пины микроконтроллера VBAT и GND. Я использовал батарейку CR2032:
Для вывода времени, даты, дня недели и выбранных цветов интерфейса я использовал TFT-экранчик на базе ST7735 . Для изменения даты, времени, и всего остального используются четыре кнопки. Действия кнопок сверху вниз: переход назад, перех вперед, уменьшить значение, увеличить значение.
Интересно, что при отсутствии основного питания батарейка питает не только часы, но и небольшое количество SRAM, так называемую backup memory (она же backup registers). Для доступа к ней при инициализации устройства нужно сказать:
/* … (пропущено) … */
HAL_PWR_EnableBkUpAccess ( ) ;
}
После чего можно осуществлять чтение и запись памяти. Я решил хранить в ней выбранный пользователем цвет интерфейса:
// … (пропущено) …
if ( /* … */ ) { // color changed
HAL_RTCEx_BKUPWrite ( & hrtc , RTC_BKP_DR1 , ( uint32_t ) chosen_color ) ;
}
Как видите, backup memory поделена на 32-х битные регистры. Количество регистров зависит от микроконтроллера, их точное число можно узнать из даташита. Например, использованный мной STM32F411RE имеет 20 регистров. То есть, суммарно в backup memory он может хранить до 80 байт данных. Учитывая, что большинство микроконтроллеров STM32 не имеют встроенного EEPROM , наличие у них backup memory оказывается весьма кстати.
В целом, я не нашел серьезных дефектов во встроенных RTC. Время они показывают весьма точно, дату и день недели обновляют правильно, отключение основного питания переживают. RTC имеют и другие возможности, которые не были рассмотрены выше. Например, есть настройки для дополнительной калибровки часов. А еще RTC могут генерировать прерывания , выводящие микроконтроллер из энергосберегающего режима по расписанию. Однако эти моменты уже выходят за рамки данного поста. В качестве источника дополнительной информации можно порекомендовать книгу Mastering STM32 .
Полную версию исходников к этому посту вы найдете на GitHub .