> 技术文档 > 【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc

【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc


🕒HAL库中的实现:RTC(Real-Time Clock)实时时钟

RTC 是 STM32 的低功耗实时时钟模块,常用于:

  • 实时时间维护(年月日时分秒)
  • 定时唤醒
  • 日志时间戳
  • 闹钟功能

RTC(实时时钟)模块 提供了 三个中断源用于在不同时间条件下触发事件。这些中断在 低功耗唤醒、定时任务、系统监控等场景下使用。

🕒 RTC 的三个中断源
中断类型 含义 用途 HAL支持情况 秒中断(Second Interrupt) 每秒触发一次 定时刷新、节拍事件 ❌ HAL 默认未封装,需裸机配置 闹钟中断(Alarm Interrupt) 到指定时间触发一次 定时唤醒、事件提醒 ✅ HAL 封装完整 溢出中断(Overflow Interrupt) RTC 计数器溢出时触发 长时间周期事件(如每86400秒) ❌ HAL 默认未封装,需裸机配置

CubeMX配置:
【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc
【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc

RTC时钟秒更新中断流程:
【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc

🧾 业务代码配置( RTC 秒中断功能)

配置流程:

步骤 状态 说明 RTC 初始化 ✅ 使用 MX_RTC_Init() 初始化 启用秒中断 ✅ __HAL_RTC_SECOND_ENABLE_IT(&hrtc, RTC_IT_SEC); 中断向量启用 ✅ RTC_IRQn 已在中断向量表中启用 回调函数实现 ✅ HAL_RTCEx_RTCEventCallback() 已实现 中断函数转发 ✅ RTC_IRQHandler() 中调用了 HAL_RTCEx_RTCIRQHandler() 时钟源配置 ✅ 使用 LSI,稳定性较好 获取时间 ✅ 每秒打印当前时间
+------------------+| RTC 每秒更新 |+--------+---------+ | v+-------------------------+| RTC_IRQn 中断触发 |+-------------------------+ | v+-------------------------+| HAL_RTCEx_RTCIRQHandler |+-------------------------+ | v+-----------------------------+| HAL_RTCEx_RTCEventCallback || (打印函数)  |+-----------------------------+
完整代码(中断的方式获取时间)

📄 stm32f1xx_it.c

/* USER CODE BEGIN Header *//** ****************************************************************************** * @file stm32f1xx_it.c * @brief Interrupt Service Routines. ****************************************************************************** * @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\"#include \"stm32f1xx_it.h\"/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include \"usart.h\"#include \"iwdg.h\"#include \"rtc.h\"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN TD *//* USER CODE END TD *//* Private define ------------------------------------------------------------*//* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*//* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*//* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 *//* USER CODE END 0 *//* External variables --------------------------------------------------------*/extern RTC_HandleTypeDef hrtc;extern TIM_HandleTypeDef htim3;extern UART_HandleTypeDef huart1;/* USER CODE BEGIN EV *//* USER CODE END EV *//******************************************************************************//*  Cortex-M3 Processor Interruption and Exception Handlers *//******************************************************************************//** * @brief This function handles Non maskable interrupt. */void NMI_Handler(void){ /* USER CODE BEGIN NonMaskableInt_IRQn 0 */ /* USER CODE END NonMaskableInt_IRQn 0 */ /* USER CODE BEGIN NonMaskableInt_IRQn 1 */ while (1) { } /* USER CODE END NonMaskableInt_IRQn 1 */}/** * @brief This function handles Hard fault interrupt. */void HardFault_Handler(void){ /* USER CODE BEGIN HardFault_IRQn 0 */ /* USER CODE END HardFault_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_HardFault_IRQn 0 */ /* USER CODE END W1_HardFault_IRQn 0 */ }}/** * @brief This function handles Memory management fault. */void MemManage_Handler(void){ /* USER CODE BEGIN MemoryManagement_IRQn 0 */ /* USER CODE END MemoryManagement_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */ /* USER CODE END W1_MemoryManagement_IRQn 0 */ }}/** * @brief This function handles Prefetch fault, memory access fault. */void BusFault_Handler(void){ /* USER CODE BEGIN BusFault_IRQn 0 */ /* USER CODE END BusFault_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_BusFault_IRQn 0 */ /* USER CODE END W1_BusFault_IRQn 0 */ }}/** * @brief This function handles Undefined instruction or illegal state. */void UsageFault_Handler(void){ /* USER CODE BEGIN UsageFault_IRQn 0 */ /* USER CODE END UsageFault_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_UsageFault_IRQn 0 */ /* USER CODE END W1_UsageFault_IRQn 0 */ }}/** * @brief This function handles System service call via SWI instruction. */void SVC_Handler(void){ /* USER CODE BEGIN SVCall_IRQn 0 */ /* USER CODE END SVCall_IRQn 0 */ /* USER CODE BEGIN SVCall_IRQn 1 */ /* USER CODE END SVCall_IRQn 1 */}/** * @brief This function handles Debug monitor. */void DebugMon_Handler(void){ /* USER CODE BEGIN DebugMonitor_IRQn 0 */ /* USER CODE END DebugMonitor_IRQn 0 */ /* USER CODE BEGIN DebugMonitor_IRQn 1 */ /* USER CODE END DebugMonitor_IRQn 1 */}/** * @brief This function handles Pendable request for system service. */void PendSV_Handler(void){ /* USER CODE BEGIN PendSV_IRQn 0 */ /* USER CODE END PendSV_IRQn 0 */ /* USER CODE BEGIN PendSV_IRQn 1 */ /* USER CODE END PendSV_IRQn 1 */}/** * @brief This function handles System tick timer. */void SysTick_Handler(void){ /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */}/******************************************************************************//* STM32F1xx Peripheral Interrupt Handlers  *//* Add here the Interrupt Handlers for the used peripherals.  *//* For the available peripheral interrupt handler names,*//* please refer to the startup file (startup_stm32f1xx.s).  *//******************************************************************************//** * @brief This function handles RTC global interrupt. */void RTC_IRQHandler(void){ /* USER CODE BEGIN RTC_IRQn 0 */ /* USER CODE END RTC_IRQn 0 */ HAL_RTCEx_RTCIRQHandler(&hrtc); /* USER CODE BEGIN RTC_IRQn 1 */ /* USER CODE END RTC_IRQn 1 */}/** * @brief This function handles EXTI line0 interrupt. */void EXTI0_IRQHandler(void){ /* USER CODE BEGIN EXTI0_IRQn 0 */ /* USER CODE END EXTI0_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(PA0_Key_Pin); /* USER CODE BEGIN EXTI0_IRQn 1 */ /* USER CODE END EXTI0_IRQn 1 */}/** * @brief This function handles TIM3 global interrupt. */void TIM3_IRQHandler(void){ /* USER CODE BEGIN TIM3_IRQn 0 */ /* USER CODE END TIM3_IRQn 0 */ HAL_TIM_IRQHandler(&htim3); /* USER CODE BEGIN TIM3_IRQn 1 */ /* USER CODE END TIM3_IRQn 1 */}/** * @brief This function handles USART1 global interrupt. */void USART1_IRQHandler(void){ /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */}/* USER CODE BEGIN 1 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){if(GPIO_Pin == PA0_Key_Pin){if( HAL_GPIO_ReadPin(PA0_Key_GPIO_Port, PA0_Key_Pin) == GPIO_PIN_RESET){HAL_GPIO_TogglePin(GPIOC, LED_G_Pin);//红灯的状态翻转//HAL_IWDG_Refresh(&hiwdg);//进行喂狗//printf(\"Iwdg Count = %d \\r\\n\", Count++);}}}void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){U1RxLen = Size;HAL_UARTEx_ReceiveToIdle_IT(&huart1, U1RxData, U1RxDataSize);//启动串口空闲中断的接收U1RxFlag = 1;}//TIM的中断回调函数 控制LED灯的闪烁void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){//static uint32_t Count = 0;if(htim->Instance == TIM3)//判断产生中断的哪一个中断回调函数{HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);//绿灯的状态翻转HAL_IWDG_Refresh(&hiwdg);//进行喂狗//printf(\"Iwdg Count = %d \\r\\n\", Count++);}}RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc){HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BCD);HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BCD);printf(\"TimeDate -> %d:%d:%d[%d] | %d:%d:%d\\r\\n\", sDate.Year,sDate.Month,sDate.Date,sDate.WeekDay,sTime.Hours,sTime.Minutes,sTime.Seconds);HAL_Delay(100);}/* USER CODE END 1 */

📄 main.c

/* 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\"#include \"iwdg.h\"#include \"rtc.h\"#include \"tim.h\"#include \"usart.h\"#include \"gpio.h\"/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include \"string.h\"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD */uint8_t U1SendData[] = {\"hello world!\"};/* 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 ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/void SystemClock_Config(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 */uint16_t pwmVal = 0;//占空比 /* 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_USART1_UART_Init(); MX_IWDG_Init(); MX_TIM3_Init(); MX_RTC_Init(); /* USER CODE BEGIN 2 */HAL_UARTEx_ReceiveToIdle_IT(&huart1, U1RxData, U1RxDataSize);//使能空闲中断HAL_TIM_Base_Start_IT(&htim3);// 启动定时器HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);//PWM初始化完毕之后,找到对应PWM的启动函数__HAL_RTC_SECOND_ENABLE_IT(&hrtc, RTC_IT_SEC);//开启秒更新中断 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */HAL_UART_Transmit(&huart1, U1SendData, sizeof(U1SendData), 0xff);//启动串口空闲中断的发送 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_LSI|RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.LSIState = RCC_LSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; 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_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC; PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); }}/* USER CODE BEGIN 4 *//* 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 */

🧩 代码中的核心细节说明
  1. __HAL_RTC_SECOND_ENABLE_IT() 说明

这个宏开启了 秒中断(SECIE)

#define __HAL_RTC_SECOND_ENABLE_IT(__HANDLE__, __INTERRUPT__) \\ ((__HANDLE__)->Instance->CRH |= (__INTERRUPT__))

配合:

#define RTC_IT_SEC ((uint32_t)RTC_CRH_SECIE)

它控制的是 RTC->CRH.SECIE 位,每秒触发一次 SECF 标志位,由 HAL 库内部清除。

  1. HAL_RTCEx_RTCEventCallback() 是 HAL 提供的专用 RTC 秒中断回调函数:
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc){ // 每秒触发}

它在 HAL_RTCEx_RTCIRQHandler() 中被调用:

if(__HAL_RTC_GET_IT(hrtc, RTC_IT_SEC) != RESET){ __HAL_RTC_CLEAR_FLAG(hrtc, RTC_FLAG_SEC); HAL_RTCEx_RTCEventCallback(hrtc);}

打印时钟:

HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BCD);HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BCD);printf(\"TimeDate -> %d:%d:%d[%d] | %d:%d:%d\\r\\n\",  sDate.Year, sDate.Month, sDate.Date, sDate.WeekDay,  sTime.Hours, sTime.Minutes, sTime.Seconds);

每秒中断触发后,会打印一次当前时间,非常适合作为 RTC 秒事件验证。

另外,代码中可以去除 HAL_Delay(100):这是因为在中断中使用 HAL_Delay() 不太安全,可能引发系统异常或丢中断。也可以替代为:

// 替代 HAL_Delay(100) 或者不加延时,使用定时器节奏控制打印频率。for (volatile uint32_t i = 0; i < 100000; ++i);

还有,使用 RTC_FORMAT_BIN 更直观,这是因为BCD 格式读取后要自己解码,而使用 BIN 格式更便于直接打印和逻辑判断。

HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);

最后,RTC 时钟源也可以用 LSE(32.768kHz),目前这里使用的是:

PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;

LSI 精度较低(±10%,温漂大),建议若有外部 32.768kHz 晶振,则改为:

PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;

打印中也可以添加LED闪烁:

HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 每秒闪烁一次LED

串口输出验证:(使用串口工具(如 SSCOM、PuTTY)查看打印内容是否每秒刷新。如图所示,是我打印出来的时钟:)

【STM32】HAL库中的实现(四):RTC (实时时钟)_stm32f1 hal rtc

以上。 这便是 STM32 HAL库 + RTC 每秒中断功能 的实现。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!