RTC HAL stm32

Речь пойдёт о программировании RTC (встроенных часов) в микроконтроллере stm32.
Описание сделано для F103 и F303, но так как у них много общих функций, то читать нужно всё.
Первым делом нужно указать источник тактирования для часов. Указываем внешний кварцевый резонатор, он есть на многих платах…

В CubeMX выбираем

В мультиплексоре RTC Clock Mux нужно указать источник LSE…

Если внешнего кварца нет, тогда в мультиплексоре укажите LSI, а Low Speed External (LSE) ⇨ Disable.
Переходим в раздел
Для F103

Со временем и календарём всё понятно. Формат 24-х часовой.
Для F303

Здесь два предделителя для настройки частоты —
Если у Вас LSI 40kHz, тогда во втором предделителе укажите 311. Если частота другая, тогда смотрите ниже.
Настройка предделителей для разных источников тактирования…

У нас тактируется от LSE = 32.768кГц, тогда исходя из формулы получается следующее: 128 * 256 = 32768 / 32.768кГц = 1Гц (один тик в секунду). Если уменьшить или увеличить какое-нибудь значение, то часы пойдут быстрее или медленнее.
Пишем код. В функции
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
char trans_str[64] = {0,};
/* USER CODE END PV */
И заодно объявим массив для вывода данных в UART.
У F303 структура даты называется sDate.
В бесконечном цикле будем читать дату и время:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // RTC_FORMAT_BIN , RTC_FORMAT_BCD
snprintf(trans_str, 63, "Time %d:%d:%d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Date %d-%d-20%d\n", DateToUpdate.Date, DateToUpdate.Month, DateToUpdate.Year);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...

Если на пин
/**Initialize RTC and set the Time and Date */
sTime.Hours = 10;
sTime.Minutes = 34;
sTime.Seconds = 0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
//if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
//{
// Error_Handler();
//}
DateToUpdate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
DateToUpdate.Month = RTC_MONTH_FEBRUARY;
DateToUpdate.Date = 20;
DateToUpdate.Year = 19;
//if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
//{
// Error_Handler();
//}
Эти же функции можно использовать где-нибудь в программе для изменения времени/даты на лету.
Теперь можно прошить ещё раз и понажимать ресет. Данные должны сохраняться.
У микроконтроллера F103, дата не сохраняется. Это связано с тем, что F103 всего один 32-х битный регистр, см. спойлер…
спойлер
Вот так выглядит схема RTC в F103…

От батарейки работает только регистр со временем и будильником, ну и ещё предделитель. То есть дату сохранить нельзя.
Но, дату сохранить нельзя только если пользоваться HAL, если же написать свой костыль, тогда в RTC_CNT можно сохранять дату/время в UNIX-формате, то есть количество секунд прошедшее с 1970 года. А потом средствами СИ вытаскивать из этого числа дату и время.

От батарейки работает только регистр со временем и будильником, ну и ещё предделитель. То есть дату сохранить нельзя.
Но, дату сохранить нельзя только если пользоваться HAL, если же написать свой костыль, тогда в RTC_CNT можно сохранять дату/время в UNIX-формате, то есть количество секунд прошедшее с 1970 года. А потом средствами СИ вытаскивать из этого числа дату и время.
У более «жирных» камней сохраняется и дата и время.
Если используется LSI, то данные сохранятся, но время идти не будет.
Если у вас микроконтроллер F4xx или F7xx, и вы хотите считывать только время (без даты), то в любом случае нужно после функции
Будильник (Alarm) для F103
Настраиваем…

Будильник сработает через пять секунд после старта.
Можно сгенерировать проект, прошить и посмотреть как мигнёт светодиод.
После того как попробуете эти режимы верните всё как на картинке.
Включите прерывание от будильника…

Будильник соединён с линией EXTI17 – RTC Alert event.
Добавьте колбек будильника:
/* USER CODE BEGIN 0 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "ALARM\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Прошивайте и смотрите результат.
Чтобы посмотреть настройки будильника из программы, нужно вызвать функцию
пример
Структуру будильника объявим как глобальную:
Для программной установки будильника нужно воспользоваться функциейHAL_RTC_SetAlarm_IT() , она есть в функции инициализации — static void MX_RTC_Init(void) .
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
RTC_AlarmTypeDef sAlarm = {0}; // структура будильника
char trans_str[64] = {0,};
/* USER CODE END PV */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // RTC_FORMAT_BIN , RTC_FORMAT_BCD
snprintf(trans_str, 63, "Time %d:%d:%d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Date %d-%d-20%d\n", DateToUpdate.Date, DateToUpdate.Month, DateToUpdate.Year);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Alarm %d:%d:%d\n", sAlarm.AlarmTime.Hours, sAlarm.AlarmTime.Minutes, sAlarm.AlarmTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...
Для программной установки будильника нужно воспользоваться функцией
Помимо прерывания от будильника, есть ещё прерывание, которое может вызываться раз в секунду.
Включите глобальное прерывание…

Добавьте ещё один колбек:
/* USER CODE BEGIN 0 */
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "One sec\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Перед бесконечным циклом добавьте функцию:
/* USER CODE BEGIN 2 */
HAL_RTCEx_SetSecond_IT(&hrtc);
/* USER CODE END 2 */
Прошейте и смотрите.
Как использовать этот функционал — например можно сделать так: в колбеке будильника запускаем это прерывание, после чего оно вызовется 10 раз и выключится. В прерывании можно делать что угодно, например подавать импульс на какой-нибудь пин с подключённой пищалкой.
пример
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
RTC_AlarmTypeDef sAlarm = {0};
char trans_str[64] = {0,};
volatile uint8_t count = 0; // добавить счётчик
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc)
{
// тут подаём импульс на какой-нибудь пин
count++;
if(count > 9) HAL_RTCEx_DeactivateSecond(hrtc); // отключаем ежесекундное прерывание
snprintf(trans_str, 63, "One sec\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "ALARM\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTCEx_SetSecond_IT(hrtc); // запускаем ежесекундное прерывание
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
//HAL_RTCEx_SetSecond_IT(&hrtc);
/* USER CODE END 2 */
Если установить

… то режимы
Tamper для F103 и F303
Активируйте Tamper…

В RTC OUT указывайте что хотите. Calendar можно включить, а можно и не включать.
До этого мы использовали tamper-пин для вывода сигнала, а сейчас он будет выполнять обратную функцию.
У F103 есть десять 16-ти битных регистров для хранения пользовательских данных (backup registers). Если подключена батарейка, то данные в этих регистрах не обнуляются ни при нажатии Reset, ни при выходе из спящего режима, ни при отключении основного питания.
Если в эти регистры записать какие-то данные, то их можно будет стереть подав на tamper-пин кратковременный импульс.
Какой именно сигнал послужит триггером, настраивается в пункте
Укажите
Внутренняя подтяжка такая слабенькая, что срабатывает от прикосновения пальца, поэтому желательно подтянуть пин к «плюсу» резистором (10КОм).
Запишем в первые два регистра данные:
/* USER CODE BEGIN 2 */
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 1234); // в первый регистр запишем число 1234
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 5678); // во второй регистр запишем число 5678
/* USER CODE END 2 */
В бесконечном цикле читаем эти данные:
/* USER CODE BEGIN WHILE */
while (1)
{
uint16_t reg_1 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
uint16_t reg_2 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
snprintf(trans_str, 63, "Reg_1 %d Reg_2 %d\n", reg_1, reg_2);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...
Прошейте эту программу и коротните пин на «землю» — данные обнулятся.
Теперь если нажать Reset, то по идее данные должны будут записаться заново, но этого не случится. Дело в том, что после подачи сигнала запись в эти регистры будет запрещена. Чтобы восстановить возможность записи нужно полностью обесточить плату (батарейку тоже нужно отключить).
Помимо обнуления регистров этот сигнал может вызывать прерывание…

В программе нужно добавить только колбек:
/* USER CODE BEGIN 0 */
void HAL_RTCEx_Tamper1EventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "Tamper\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Если включено прерывание, то при нажатии Reset регистры с данными
Для работы без прерываний можно воспользоваться функцией ожидания сигнала.
пример
Структуру тампера объявляем глобально:
В бесконечном цикле делаем так:
Опять же, при нажатии Reset регистры с данными будут перезаписываться.
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
RTC_AlarmTypeDef sAlarm = {0};
RTC_TamperTypeDef sTamper = {0}; // структура тампера
char trans_str[64] = {0,};
/* USER CODE END PV */
В бесконечном цикле делаем так:
/* USER CODE BEGIN WHILE */
while (1)
{
static uint16_t count = 0; // просто счётчик для наглядности
uint16_t reg_1 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
uint16_t reg_2 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
snprintf(trans_str, 63, "Before Reg_1 %d Reg_2 %d\n", reg_1, reg_2);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
snprintf(trans_str, 63, "Start %d Wait 10 sec...\n", count++);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTCEx_SetTamper(&hrtc, &sTamper);
HAL_RTCEx_PollForTamper1Event(&hrtc, 10000); // ждём сигнала 10 сек.
reg_1 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
reg_2 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
snprintf(trans_str, 63, "After Reg_1 %d Reg_2 %d\n\n", reg_1, reg_2);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...
Опять же, при нажатии Reset регистры с данными будут перезаписываться.
У F303 15 пользовательских регистров и три tamper-пина.
Прерывания включаются так…

У каждого пина свой колбек:
void HAL_RTCEx_Tamper1EventCallback(RTC_HandleTypeDef *hrtc)
void HAL_RTCEx_Tamper2EventCallback(RTC_HandleTypeDef *hrtc)
void HAL_RTCEx_Tamper3EventCallback(RTC_HandleTypeDef *hrtc)
Всё остальное как у F103.
Я когда ковырялся с этим функционалом, время от времени возникало ощущение что что-то «глючит» (регистры то записываются, то не записываются, то обнуляются, то не обнуляются), но потом стало понятно что это всего лишь следствие неправильных действий. Это к тому, что нужно проявить терпение и разобраться.
Будильник (Alarm) для F303
У этого микроконтроллера два будильника А и В…

У них есть два варианта настроек:
Сейчас укажите
Настройте всё как на картинке…

Время у нас установлено 10:34:00, а будильник сработает в 10:34:10.
Следующие пункты, это различные комбинации настроек времени/даты срабатывания будильника. Например пункт
Если активировать пункт
Если активировать пункт
Этих комбинаций достаточно много, поэтому надеюсь что вы разберётесь самостоятельно.
Alarm combinations
Активируйте прерывание от будильника…

В код добавьте колбек:
/* USER CODE BEGIN 0 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "Alarm_A\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Alarm B
У Alarm B свой колбек:
HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
В бесконечном цикле будем выводить инфу:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // RTC_FORMAT_BIN , RTC_FORMAT_BCD
snprintf(trans_str, 63, "Time %d:%d:%d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);
//snprintf(trans_str, 63, "Time %d:%d:%d:%lu\n", sTime.Hours, sTime.Minutes, sTime.Seconds, sTime.SubSeconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Date %d-%d-20%d\n", DateToUpdate.Date, DateToUpdate.Month, DateToUpdate.Year);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Alarm %d:%d:%d\n", sAlarm.AlarmTime.Hours, sAlarm.AlarmTime.Minutes, sAlarm.AlarmTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...
Чтобы будильник подавал импульс на пин РС13, надо указать

В настройках появятся два дополнительных пункта…

Делайте как на картинке и не забудьте будильник настроить, он сбивается при изменении режима.
Подключите светодиод к пину РС13 и смотрите как он мигнёт во время срабатывания будильника.
WakeUp
WakeUp может выводить МК из спящего режима, вызывать прерывание и подавать сигнал на пин РС13.

WakeUp это простой 16-ти битный (от 0 до 65535) счётчик. Тактирование можно настроить с помощью предделителей часового генератора.
Например если сделать так…

Тогда счётчик будет увеличиваться со скоростью 2048 единиц в секунду и достигнет 10000 примерно через 5 секунд (32.768кГц / 16 = 2048, 10000 / 2048 = 4.88 сек). То есть WakeUp будет срабатывать каждые ~5 секунд.
Можно не заморачиваться с предделителями, а просто указать 1Hz…

Счётчик будет увеличиваться со скоростью 1 единица в секунду и срабатывать через каждые 5 сек.
Таким образом можно настроить пробуждение МК на достаточно большой интервал. Например если указать 65000, то WakeUp будет срабатывать каждые 18 часов.
Активируйте прерывание…

Если включено прерывание, то на пин РС13 подаётся кратковременный импульс, а если отключено, то подаётся постоянный сигнал.
И добавьте соответствующий колбек:
/* USER CODE BEGIN 0 */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "WakeUp\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Прошивайте и смотрите что получилось.
Посмотреть значение счётчика можно так:
uint32_t wu = HAL_RTCEx_GetWakeUpTimer(&hrtc);
snprintf(trans_str, 63, "WakeUp %lu\n\n", wu);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
Отключить счётчик:
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
TimeStamp

Прикольная функция. Если на пин РС13 подать внешний импульс, тогда в специальные регистры будут записаны текущие время и дата. Настройка только одна и она ничем не отличается от того, что написано в главе «Tamper для F103 и F303». То есть нужно указать фронт сигнала и подтянуть пин.
В бесконечном цикле сделайте так:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_RTCEx_GetTimeStamp(&hrtc, &sTime, &DateToUpdate, RTC_FORMAT_BIN); // читаем Time/Date Stamp
snprintf(trans_str, 63, "TimeStamp %d:%d:%d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
snprintf(trans_str, 63, "DateStamp %d-%d-%d\n", DateToUpdate.Date, DateToUpdate.Month, DateToUpdate.Year);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Time %d:%d:%d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);
snprintf(trans_str, 63, "Date %d-%d-%d\n\n", DateToUpdate.Date, DateToUpdate.Month, DateToUpdate.Year);
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
HAL_Delay(1000);
...
Прошейте МК и коротните РС13 на «землю».
Данные из регистров записываются в структуры времени и даты (чтоб не создавать дополнительные структуры).
Обратите внимание на то, что регистры обнуляются после чтения.
Прерывание то же что и у Tamper…

А колбек свой:
/* USER CODE BEGIN 0 */
void HAL_RTCEx_TimeStampEventCallback(RTC_HandleTypeDef *hrtc)
{
snprintf(trans_str, 63, "TimeStamp\n");
HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 1000);
}
Calibration
В мануале про этот выход сказано так:
«Выход RTC_CALIB используется для генерации сигнала переменной частоты. В зависимости от пожелания пользователя этот сигнал может играть роль опорной частоты для внешнего устройства или его можно подключить к зуммеру для генерации звука.»
Есть два варианты частоты — 1Гц и 512Гц…

Запускается и останавливается этот сигнал функциями…
HAL_RTCEx_SetCalibrationOutPut(&hrtc, RTC_CALIBOUTPUT_1HZ); // или RTC_CALIBOUTPUT_512HZ
HAL_RTCEx_DeactivateCalibrationOutPut(&hrtc);
Можно помигать или попищать при срабатывании будильника.
Для калибровки часов этот выход нужно подключить к осциллографу и добиваться необходимой частоты двумя способами:
1. Манипулировать предделителями — грубая калибровка.
2. С помощью пропусков (маскировки) или добавления тактов — мягкая калибровка.
Осциллографа у меня нет поэтому я особо в этом не разбирался. За мягкую калибровку отвечает функция
Reference clock detection — на это пин можно подать опорную частоту (50 Гц) из розетки. Проводить эксперименты я не решился

В мануале есть такая картика…

В статье не описаны некоторые функции — посмотреть их можно в файлах stm32f3xx_hal_rtc.c и stm32f3xx_hal_rtc_ex.c.
На этом всё.
Всем спасибо

мануал AN3371
Телеграм-чат istarik
Телеграм-чат STM32

- 0
- stD
81576
Поддержать автора
Комментарии (0)