# 引言 在工业控制、物联网设备等嵌入式应用中,RTC(实时时钟)是时间戳和定时任务的核心。STM32F4 系列默认使用 LSE(32.768 kHz 外部晶振)驱动 RTC,其精度可达 ±20 ppm。然而,LSE 可能因晶振老化、焊接缺陷或强电磁干扰而停振。一旦 LSE 失效,RTC 将停止计时,系统时间归零,造成数据错乱。本文提出一种嵌入式软件容错方案:当检测到 LSE 停振时,自动切换至 HSI(16 MHz 内部 RC 振荡器),并通过软件校准补偿 HSI 的频率误差,使 RTC 在无外部晶振下仍能保持较高精度。 # 原理分析 ## 1. STM32F4 时钟树与 RTC 时钟源 STM32F4 的 RTC 时钟源可选:LSE、LSI(约 32 kHz 内部低速时钟)或 HSE/128。LSE 精度最高,但依赖外部硬件;LSI 精度差(±50%),不适合长时间计时;HSE 精度高但通常被系统主时钟占用。HSI 虽为高速时钟,但可通过分频得到 32.768 kHz(16 MHz / 488.28125 非整数,需特殊处理)。 ## 2. LSE 停振检测机制 STM32F4 的 RTC 模块提供时钟安全系统(CSS)功能,可监测 LSE 信号。当 LSE 振幅低于阈值时,CSS 产生中断,置位 RTC_ISR 中的 LSECSSF 标志。软件可读取该标志或使能中断来感知停振。 ## 3. HSI 校准原理 HSI 出厂精度约 ±1%,在 25°C 下典型误差 ±0.5%。通过软件校准,可将其误差降至 ±0.1% 以内。校准方法:利用 RTC 的同步预分频器(PREDIV_S)和异步预分频器(PREDIV_A),将 HSI 分频至 1 Hz。由于 HSI 频率不精确,实际秒脉冲会偏移,通过测量偏移量并调整预分频值,可实现频率补偿。 # 硬件与软件准备 - 硬件:STM32F407 开发板(或其他 F4 系列) - 软件:STM32CubeIDE 或 Keil MDK,HAL 库 - 关键外设:RTC、EXTI、NVIC、系统时钟 # 配置步骤 ## 1. 初始化系统时钟与 RTC 首先,配置系统时钟为 HSI(或 PLL),并启用 RTC 时钟。以下代码初始化 RTC 使用 LSE,并开启 CSS 中断。 ```c void RTC_Init(void) { RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); __HAL_RCC_RTC_ENABLE(); RTC_HandleTypeDef hrtc; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; // 异步分频 hrtc.Init.SynchPrediv = 255; // 同步分频,得到 1 Hz hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; HAL_RTC_Init(&hrtc); // 使能 LSE CSS 中断 HAL_RTCEx_EnableLSECSS(&hrtc); HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn); } ``` 注意:`HAL_RTC_Init` 内部会尝试启动 LSE,若 LSE 未就绪,则返回错误。我们需在初始化时检测 LSE 状态,若失败则直接切换到 HSI 模式。 ## 2. LSE 停振中断处理 当 LSE 停振时,CSS 中断触发。在中断回调中,我们关闭 LSE,切换 RTC 时钟源至 HSI,并启动校准流程。 ```c void HAL_RTCEx_LSECSSEventCallback(RTC_HandleTypeDef *hrtc) { // 关闭 LSE 和 CSS __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); HAL_RTCEx_DisableLSECSS(hrtc); // 切换 RTC 时钟源到 HSI(通过 RCC_BDCR 寄存器) __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV8); // 16MHz/8=2MHz,再分频得到 32.768kHz?实际需调整 // 注意:HSI 分频后需配合预分频器,见下文校准 // 重新初始化 RTC 预分频器 hrtc->Instance->PRER = (127 << 16) | 255; // 默认值,后续校准会调整 // 设置校准标志 lse_failure = 1; } ``` 注意:`RCC_RTCCLKSOURCE_HSI_DIV8` 在 F4 系列中并不存在,实际需使用 `RCC_RTCCLKSOURCE_HSI_DIV2` 或直接使用 HSI 经分频。查阅参考手册,RTC 时钟源可选 HSI/2(8 MHz)或 HSI/8(2 MHz)。我们选择 HSI/8 得到 2 MHz,然后通过预分频器分频至 1 Hz。 ## 3. HSI 校准算法 HSI 频率误差导致秒脉冲偏移。校准思路:利用一个已知精度的参考时钟(如系统主时钟或外部信号)测量 RTC 秒脉冲的实际周期,然后调整同步预分频值。 具体实现: - 使用定时器(如 TIM2)在 1 秒内计数,测量 RTC 秒中断的间隔。 - 假设理想周期为 32768 个 HSI/8 时钟(即 2 MHz / 32768 = 61.035 Hz,非整数,实际需用小数分频)。 - 由于 HSI 频率偏差,实际计数会偏离理论值。通过多次测量取平均,计算偏差比例,然后调整 PREDIV_S 值。 ```c #define HSI_FREQ 16000000 #define HSI_DIV 8 #define RTC_CLK (HSI_FREQ / HSI_DIV) // 2 MHz #define TARGET_HZ 1 uint32_t measure_rtc_interval(void) { // 配置 TIM2 为外部时钟模式,计数 RTC 秒脉冲?或使用内部时钟捕获 // 简化:使用 SysTick 或 DWT 计数器 DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0; // 等待 RTC 秒中断标志 while (!(RTC->ISR & RTC_ISR_SECF)); RTC->ISR &= ~RTC_ISR_SECF; uint32_t start = DWT->CYCCNT; while (!(RTC->ISR & RTC_ISR_SECF)); RTC->ISR &= ~RTC_ISR_SECF; uint32_t end = DWT->CYCCNT; return (end - start) / (SystemCoreClock / 1000000); // 微秒 } void calibrate_hsi(void) { uint32_t measured_us = measure_rtc_interval(); uint32_t expected_us = 1000000; // 1 秒 int32_t error_ppm = ((int32_t)measured_us - expected_us) * 1000000 / expected_us; // 调整 PREDIV_S:假设当前 PREDIV_S = 255,实际频率 = RTC_CLK / (PREDIV_A+1) / (PREDIV_S+1) // 若误差为正,说明 RTC 慢了,需减小 PREDIV_S;反之增大 uint32_t new_prediv_s = 255 - (error_ppm * 255 / 1000000); // 限制范围 if (new_prediv_s > 511) new_prediv_s = 511; if (new_prediv_s < 0) new_prediv_s = 0; // 写入预分频器(需在 RTC 写保护关闭后) HAL_RTC_InitTypeDef init; // 重新初始化 RTC 预分频 RTC->PRER = (127 << 16) | new_prediv_s; } ``` 注意:上述代码仅为示意,实际需处理 RTC 写保护(`HAL_RTC_Unlock`)和同步等待。 ## 4. 完整代码示例 以下是一个集成上述功能的简化示例,包含初始化、中断处理和校准调用。 ```c volatile uint8_t lse_failure = 0; void SystemClock_Config(void); void RTC_Init(void); void RTC_Calibration_Handler(void); int main(void) { HAL_Init(); SystemClock_Config(); RTC_Init(); while (1) { if (lse_failure) { RTC_Calibration_Handler(); lse_failure = 0; } // 其他任务 } } void RTC_Calibration_Handler(void) { // 进行多次测量校准 for (int i = 0; i < 10; i++) { calibrate_hsi(); HAL_Delay(100); // 等待稳定 } } void HAL_RTCEx_LSECSSEventCallback(RTC_HandleTypeDef *hrtc) { // 切换时钟源 __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV8); // 重新初始化 RTC 预分频(默认值) hrtc->Instance->PRER = (127 << 16) | 255; lse_failure = 1; } ``` # 注意事项 - **RTC 写保护**:修改 RTC 寄存器前必须解锁(`HAL_RTC_Unlock`),修改后重新锁定。 - **时钟切换时机**:切换 RTC 时钟源时,应确保 RTC 处于初始化模式(`HAL_RTC_DeInit` 或设置 INIT 位),否则可能产生毛刺。 - **校准精度**:HSI 受温度影响较大,建议在系统运行时周期性校准,或使用温度传感器补偿。 - **备份域电源**:LSE 和 RTC 位于备份域,切换时钟源时注意 VBAT 供电,避免数据丢失。 - **中断优先级**:CSS 中断应设为高优先级,确保及时响应。 - **测试验证**:在实际硬件上模拟 LSE 停振(如断开晶振),观察 RTC 是否继续走时,并测量精度。 # 总结 本文详细介绍了 STM32F4 在 LSE 停振后利用 HSI 校准 RTC 的嵌入式方案。通过 CSS 中断检测、时钟源切换和软件频率补偿,系统能够在失去外部晶振时维持时间精度,显著提高了可靠性。开发者可根据实际需求调整校准算法,如使用更精确的参考源或增加温度补偿。该方案适用于对成本敏感且环境恶劣的嵌入式应用。