PROGCONT.RU

Форма входа







Регистрация Вход/

Пример Пример Пример


STM32F101-107 HAL RTC встроенные часы настройка, использование, калибровка и применение прерываний.

RTC-часы реального времени, таймер который используется как часы.

NO

В контролерах моделях STM32F101-107 подсчёт времени осуществляется регистром RTC_CNT, для определения единицы подсчёта в таймере предусмотрен входной предделитель частоты RTC prescaler обычно он настраивается так, что бы счётчик вел подсчёт секунды. Серым на рисунке выделена зона таймера которая не сбрасывается после отключения от питания контроллера и может сохранять свою работоспособность если только будет запитан вывод контроллера VBAT например от батарейки.

В STM32F101-107 контроллерах нет регистров для хранения даты, в библиотеке HAL дата реализована в виде структуры которую вы должны сами изменять, после сброса или отключения питания(если даже запитан VBAT) структура даты обнуляется как и вся память контроллера.

NO

Источников тактирования у таймера может быть 3, это внутренний источник LSI 40KHz, внешний LSE 32.768KHz и тактирование от HSE через делитель 128. Обычно для часов используется LSE кварцевый резонатор 32.768KHz именно с ним и будем работать так как он точный с ним RTC таймер будут работать даже после отключения платы от питания конечно если запитан вывод VBAT, что нам и нужно.

Использовать будем отладочную плату Blue Pill.

NO

Красным выделен LSE резонатор, если его нет то плата не пригодна для использования для часов. Внешне эти резонаторы могут отличатся так, что изучаем описание или datasheet на плату.

И не множко о особенностях для работы с этими резонаторами, часовые кварцевые резонаторы очень капризны к конденсаторам с которыми работают, их номиналы должны быть в пределах от 6-12.5pF в зависимости от модели, если превысить или уменьшить номиналы конденсаторов то кварцевый резонатор просто не будет запускаться. Так же ошибки при разведении платы скажутся на работе резонатора так длинные дорожки или дополнительные контакты прикрепленные к выводам конденсаторов резонатора сильно увеличат их ёмкость или добавят индуктивную помеху, что опять скажется на работе кварца а именно будет не точность отсчёта времени.

NO

Пример с не правильной разводкой для часового резонатора 32.768KHz, отладочная плата Blue Pill это дорожки от конденсаторов к контактам платы PC14 и PC15, исправляем аккуратно перерезаем максимально близко к контакту конденсатора как на картинке.

Пример настройки RTC с установкой прерывания от секунд и будильника.

Ниже программа где настраиваем RTC для работы с LSE резонатором также в коде показано как использовать прерывание от секунд и будильника. В программе настраивается прерывание от секунд и в обработчике прерывания каждую секунду будет изменятся состояние вывода GPIOB GPIO_PIN_11.

Настройка прерывания и его обработчика написана мной для упрощения кода программы и экономии памяти контроллера.

Включение прерывание от секунд и закомментированное включение для будильника.

/* USER CODE BEGIN 2 */ //Включаем прерывание от RTC NVIC_EnableIRQ(RTC_IRQn); //Включаем прерывание от секунд RTC RTC->CRH |= RTC_IT_SEC; RTC->CRL &= ~RTC_FLAG_SEC; //Включаем прерывание от будильника RTC, раскомментируйте если будете использовать // RTC->CRH |= RTC_IT_ALRA; // RTC->CRL &= ~RTC_FLAG_ALRAF; /* USER CODE END 2 */

Обработчик прерывания для RTC от секунд и будильника.

/* USER CODE BEGIN 4 */ //Обработчик прерываний от RTC void RTC_IRQHandler(void){ //Проверяем прерывание от секунд if(RTC->CRL & RTC_FLAG_SEC){ //Начало вашего кода HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); //Конец вашего кода RTC->CRL &= ~RTC_FLAG_SEC; } //Проверяем прерывание от будильника if (RTC->CRL & RTC_FLAG_ALRAF){ //Начало вашего кода //Конец вашего кода RTC->CRL &= ~RTC_FLAG_ALRAF;} } /* USER CODE END 4 */

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

Код программы./* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ RTC_HandleTypeDef hrtc; /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_RTC_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_RTC_Init(); /* USER CODE BEGIN 2 */ //Включаем прерывание от RTC NVIC_EnableIRQ(RTC_IRQn); //Включаем прерывание от секунд RTC RTC->CRH |= RTC_IT_SEC; RTC->CRL &= ~RTC_FLAG_SEC; //Включаем прерывание от будильника RTC // RTC->CRH |= RTC_IT_ALRA; // RTC->CRL &= ~RTC_FLAG_ALRAF; /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.LSEState = RCC_LSE_ON; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC; PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } } /** * @brief RTC Initialization Function * @param None * @retval None */ static void MX_RTC_Init(void) { /* USER CODE BEGIN RTC_Init 0 */ /* USER CODE END RTC_Init 0 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef DateToUpdate = {0}; /* USER CODE BEGIN RTC_Init 1 */ /* USER CODE END RTC_Init 1 */ /** Initialize RTC Only */ hrtc.Instance = RTC; hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND; hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN Check_RTC_BKUP */ /* USER CODE END Check_RTC_BKUP */ /** Initialize RTC and set the Time and Date */ sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY; DateToUpdate.Month = RTC_MONTH_JANUARY; DateToUpdate.Date = 1; DateToUpdate.Year = 0; if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN RTC_Init 2 */ /* USER CODE END RTC_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* USER CODE BEGIN MX_GPIO_Init_1 */ /* USER CODE END MX_GPIO_Init_1 */ /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); /*Configure GPIO pin : PB11 */ GPIO_InitStruct.Pin = GPIO_PIN_11; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* USER CODE BEGIN MX_GPIO_Init_2 */ /* USER CODE END MX_GPIO_Init_2 */ } /* USER CODE BEGIN 4 */ //Обработчик прерываний от RTC void RTC_IRQHandler(void){ //Проверяем прерывание от секунд if(RTC->CRL & RTC_FLAG_SEC){ HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); RTC->CRL &= ~RTC_FLAG_SEC; } //Проверяем прерывание от будильника if (RTC->CRL & RTC_FLAG_ALRAF){ RTC->CRL &= ~RTC_FLAG_ALRAF;} } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */

Корректировка отсчёта времени.

Используя RTC вы наверняка столкнётесь с такой проблемой как отсутствие точности отсчёта времени которое может проявится через некоторое время. Подкорректировать данную проблему можно изменяя значение в регистре предделителя, что приведет к замедлению или ускорению отсчёта времени.

Корректировка с помощью предделителя RTC.

Например если используется резонатор 32.768 kHz то увеличивая значение предделителя на одну единицу мы замедляем на 1/32768 = 0,000030517578125 секунды наш таймер, если уменьшаем то на оборот таймер ускоряется. В итоге изменяя на одну единицу мы получаем за сутки 1 / 32768 *24*60*60 = 2,63671875 секунд плюс или минус.

Изменять значение предделителя можно следующим методом.

/*Структура и функция описана ниже в статье*/ /*Устанавливаем 0x7FFF для 32.768 kHz резонатора отсчёт одной секунды*/ hrtc.Init.AsynchPrediv = 0x7FFF; HAL_RTC_Init(&hrtc); /*Замедляем 0x7FFF + 1 = 0x8000*/ hrtc.Init.AsynchPrediv = 0x8000; HAL_RTC_Init(&hrtc); /*Ускоряем 0x7FFF - 1 = 0x7FFE*/ hrtc.Init.AsynchPrediv = 0x7FFE; HAL_RTC_Init(&hrtc);

Другой вариант корректировки это с помощью специального регистра BKP_RTCCR в котором имеются биты(6-0) для калибровки, данная калибровка работает только с LSE резонатором.

NO

Схема калибровки.

NO

Смысл работы этого регистра это пропуск одного такта тактирования RTC через определённый период времени, минимальная задержка которую можно сделать это 0 секунд за 30 дней и максимальная задержка 314 секунды за 30 дней. Регистр калибровки может только увеличивать время задержки, что бы время корректировалось как на прибавку так и на уменьшение задержки нужно установить предварительный делитель RTC на 0x7FFD(32765) вместо 0x7FFF(32767) а в регистр калибровки записать его среднее значение 0x40(64) и мы получаем от 0 до 63 ускоряем таймер с 65 до 127 замедляем.

В библиотеке HAL нет функций которыми можно корректировать регистр калибровки и поэтому ниже мной написана функция с помощью которой можно это сделать.

void RTC_set_calibration( uint16_t calib); /*Код функции*/ /*Функция принимает калибровочное значение от 0 до 127*/ void RTC_set_calibration( uint16_t calib){ calib &= 127; uint16_t cal = BKP->RTCCR; cal &= 0x380; cal |= calib; BKP->RTCCR = cal; } /*Пример применения*/ hrtc.Init.AsynchPrediv = 32765; HAL_RTC_Init(&hrtc); RTC_set_calibration( 64);

В таблице ниже показаны калибровочные данные:

Calibration value - значение которое устанавливаем в регистр калибровки или передаём моей функции:

Value in seconds per month (30 days) rounded to the nearest second - значение в секундах которое накопится в месяц (30 дней) округлено до ближайшей секунды.

Калибровочная таблица.NO

Структуры и функции библиотеки.

RTC_HandleTypeDef-основная структура настройки таймера;

RTC_TimeTypeDef-структура для настройки времени;

RTC_DateTypeDef-структура для настройки даты;

HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)-функция устанавливает время RTC;

HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)-функция получает время из RTC;

HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)-функция извлекает из структуры таймера дату;

HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)-функция записывает в структуру таймера дату;

В функциях используется такой параметр как Format который может быть RTC_FORMAT_BIN или RTC_FORMAT_BCD, BCD это старый формат который использовался для упрощения индикации чисел, BIN это обычный двоичный код с которым мы работаем и по этому используем RTC_FORMAT_BIN.

Внизу пример как хранится в байте число 42 в BCD и BIN форматах.

NO

Основная структура настройки RTC, включает в себя базовый адрес таймера, еще три структуры и переменную состояния таймера.

RTC_HandleTypeDef; Настраиваемые параметры структуры: /*Выбор нужного таймера для настройки*/ .Instance = RTC /*Настройка предделителя*/ .Init.AsynchPrediv = принимает значение от 0x00 и до 0xFFFFF RTC_AUTO_1_SECOND-для автоматической настройки отсчёта 1 секунды, если используется внешний резонатор 32.768 kHz то это равносильно установки значения 0x7FFF /*Настройка вывода Tamper pin*/ .Init.OutPut = RTC_OUTPUTSOURCE_NONE RTC_OUTPUTSOURCE_CALIBCLOCK RTC_OUTPUTSOURCE_ALARM RTC_OUTPUTSOURCE_SECOND /*Установка дня недели устанавливается автоматически функцией*/ .DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY RTC_WEEKDAY_TUESDAY RTC_WEEKDAY_WEDNESDAY RTC_WEEKDAY_THURSDAY RTC_WEEKDAY_FRIDAY RTC_WEEKDAY_SATURDAY RTC_WEEKDAY_SUNDAY /*Установка месяца*/ .DateToUpdate.Month = RTC_MONTH_JANUARY RTC_MONTH_FEBRUARY RTC_MONTH_MARCH RTC_MONTH_APRIL RTC_MONTH_MAY RTC_MONTH_JUNE RTC_MONTH_JULY RTC_MONTH_AUGUST RTC_MONTH_SEPTEMBER RTC_MONTH_OCTOBER RTC_MONTH_NOVEMBER RTC_MONTH_DECEMBER /*Установка дня*/ .DateToUpdate.Date = значение от 1 до 31 /*Установка года*/ .DateToUpdate.Year = значение от 0 до 99 /*Состояние блокировки таймера*/ .Lock = HAL_UNLOCKED HAL_LOCKED /*Состояние таймера*/ .State = HAL_RTC_STATE_RESET HAL_RTC_STATE_READY HAL_RTC_STATE_BUSY HAL_RTC_STATE_TIMEOUT HAL_RTC_STATE_ERROR /*Пример использования*/ RTC_HandleTypeDef hrtc; hrtc.Instance = RTC; hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND; hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE; HAL_RTC_Init(&hrtc);

Структура настройки времени содержит часы, минуты и секунды.

RTC_TimeTypeDef; Настраиваемые параметры структуры: /*Установка часов*/ .Hours = значение от 0 до 23 /*Установка минут*/ .Minutes = значение от 0 до 59 /*Установка секунд*/ .Seconds = значение от 0 до 59 /*Пример использования*/ RTC_TimeTypeDef sTime = {0}; sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0;

Структура даты, содержит день недели, месяц, день и год.

RTC_DateTypeDef; Настраиваемые параметры структуры: /*Установка дня недели устанавливается автоматически функцией*/ .WeekDay = RTC_WEEKDAY_MONDAY //1 RTC_WEEKDAY_TUESDAY //2 RTC_WEEKDAY_WEDNESDAY //3 RTC_WEEKDAY_THURSDAY //4 RTC_WEEKDAY_FRIDAY //5 RTC_WEEKDAY_SATURDAY //6 RTC_WEEKDAY_SUNDAY //0 /*Установка месяца*/ .Month = RTC_MONTH_JANUARY RTC_MONTH_FEBRUARY RTC_MONTH_MARCH RTC_MONTH_APRIL RTC_MONTH_MAY RTC_MONTH_JUNE RTC_MONTH_JULY RTC_MONTH_AUGUST RTC_MONTH_SEPTEMBER RTC_MONTH_OCTOBER RTC_MONTH_NOVEMBER RTC_MONTH_DECEMBER /*Установка дня*/ .Date = значение от 1 до 31 /*Установка года*/ .Year = значение от 0 до 99 /*Пример использования*/ RTC_DateTypeDef DateToUpdate = {0}; DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY; DateToUpdate.Month = RTC_MONTH_JANUARY; DateToUpdate.Date = 1; DateToUpdate.Year = 0;

Функция устанавливает время в счётчик RTC.

HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); Функция принимает: /*Структуру настройки таймера*/ *hrtc /*Структуру времени*/ *sTime /*Формат времени*/ Format = RTC_FORMAT_BIN RTC_FORMAT_BCD /*Пример использования*/ sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);

Функция извлекает время из счётчика RTC.

HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); Функция принимает: /*Структуру настройки таймера*/ *hrtc /*Структуру времени*/ *sTime /*Формат времени*/ Format = RTC_FORMAT_BIN RTC_FORMAT_BCD /*Пример использования*/ HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); uint8_t hour = sTime.Hours; uint8_t minut = sTime.Minutes; uint8_t second = sTime.Seconds;

Функция извлекает из принимаемой структуры RTC день недели, месяц, день, год и переписывает в другую принимаемую структуру с нужным форматом.

HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); Функция принимает: /*Структура в которую будем вставлять данные*/ *hrtc /*Структура из которой перепишем данные*/ *sDate /*Формат данных*/ Format = RTC_FORMAT_BIN RTC_FORMAT_BCD /*Пример использования*/ DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY; DateToUpdate.Month = RTC_MONTH_JANUARY; DateToUpdate.Date = 1; DateToUpdate.Year = 0; HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);

Функция извлекает из структуры настройки таймера дату и записывает в структуру из которой будем извлекать данные.

HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); Функция принимает: /*Структура из которой извлекаем данные*/ *hrtc /*Структура в которую перепишем извлекаемые данные*/ *sDate /*Формат данных*/ Format = RTC_FORMAT_BIN RTC_FORMAT_BCD /*Пример использования*/ HAL_RTC_GetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN); uint8_t week = DateToUpdate.WeekDay; uint8_t mont = DateToUpdate.Month; uint8_t date = DateToUpdate.Date; uint8_t year = DateToUpdate.Year;


Комментариев нет  Только зарегистрированные пользователи могут оставлять комментарии!