# 引言 在工业控制、物联网设备中,RTC(实时时钟)是时间戳、定时唤醒的核心。STM32F4 系列默认使用 LSE(32.768kHz 外部晶振)驱动 RTC,其精度高、功耗低。但 LSE 起振慢、抗振性差,在强电磁干扰或晶振老化时易停振。一旦 LSE 失效,RTC 若未配置备用时钟,将停止计时,造成系统时间错乱。本文介绍一种实用方案:利用内部 HSI(16MHz)作为备份时钟,并通过软件校准补偿频率偏差,确保时间连续且精度可控。 # 1. 原理剖析:RTC 时钟树与 LSE 故障机制 STM32F4 的 RTC 时钟源可选 LSE、LSI(约 32kHz)或 HSE 分频。RTC 内核使用 1Hz 的计数时钟,由异步预分频器(ASYNC_PRE)和同步预分频器(SYNC_PRE)对时钟源分频得到。典型配置:LSE 32.768kHz,ASYNC=127,SYNC=255,输出 1Hz。 LSE 停振时,硬件会置位 RTC_ISR 中的 LSERDY 位(若使能),但 RTC 不会自动切换时钟源。若继续使用 LSE,RTC 将停止更新。因此,需在软件中检测 LSE 故障,并切换至 HSI。HSI 为 16MHz,经 PWR 域的 RTC 时钟选择器(RTCSEL)可配置为 HSI/32(即 500kHz),再经预分频器得到 1Hz。但 HSI 精度(±1%)远低于 LSE(±20ppm),直接使用会导致日误差达 864 秒,必须校准。 # 2. 校准策略:基于 HSI 的软件补偿 HSI 频率固定,但受温度影响漂移。校准思路:在 LSE 正常时,测量 HSI 的实际频率(通过定时器捕获或 RTC 校准寄存器),计算偏差系数。当切换至 HSI 后,利用 RTC 的同步预分频器(可动态调整)或软件累加器进行补偿。 具体实现: - 在 LSE 运行期间,使用 TIM5 的捕获通道测量 HSI 周期,得到实际频率 f_HSI_actual。 - 计算理想分频值:理想情况下,HSI/32=500kHz,预分频后 1Hz。实际频率偏差导致每秒计数偏差 Δ = f_HSI_actual/32 - 500000。 - 切换后,每秒软件增加或减少相应计数,或调整 SYNC_PRE 值(但 SYNC_PRE 只能在 RTC 初始化时设置,运行中修改需重新初始化,故采用软件补偿更灵活)。 # 3. 配置步骤 1. **使能 PWR 和 RTC 时钟**: - 开启 PWR 时钟,进入备份域访问。 - 使能 RTC 时钟(通过 RCC_BDCR 寄存器)。 2. **检测 LSE 状态**: - 读取 RTC_ISR 的 LSERDY 位,若为 0 表示 LSE 未就绪。 - 也可使能 LSE 中断(LSIE)以异步通知。 3. **切换时钟源至 HSI**: - 修改 RCC_BDCR 的 RTCSEL 位为 10(HSI/32)。 - 注意:切换前需确保 RTC 已停止(通过 RTC_CR 的 BCE 位控制备份域写保护)。 4. **配置 RTC 预分频器**: - 设置 ASYNC_PRE=127,SYNC_PRE=255(针对 500kHz 输入)。 - 若 HSI 实际频率有偏差,可微调 SYNC_PRE,但需重新初始化 RTC。 5. **软件补偿**: - 在秒中断中,根据校准系数调整 RTC 计数(通过 RTC_SSR 或软件变量)。 # 4. 完整代码示例 以下代码基于 STM32F407,使用 HAL 库,实现 LSE 故障检测、切换 HSI 及软件补偿。 ```c // 全局变量 volatile uint32_t g_rtc_second_count = 0; volatile uint8_t g_lse_failure = 0; float g_hsi_calibration = 1.0f; // 校准系数,实际频率/理想频率 // LSE 故障中断回调(在 RTC_IRQHandler 中调用) void HAL_RTC_LSE_ErrorCallback(RTC_HandleTypeDef *hrtc) { g_lse_failure = 1; // 切换时钟源至 HSI __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV32); // 重新初始化 RTC 预分频器 RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN); // 停止 RTC HAL_RTC_DeInit(hrtc); // 重新配置 RTC_InitTypeDef init = {0}; init.HourFormat = RTC_HOURFORMAT_24; init.AsynchPrediv = 127; init.SynchPrediv = 255; init.OutPut = RTC_OUTPUT_DISABLE; HAL_RTC_Init(hrtc); // 设置时间 HAL_RTC_SetTime(hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_SetDate(hrtc, &sDate, RTC_FORMAT_BIN); } // 秒中断回调(用于软件补偿) void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) { g_rtc_second_count++; if (g_lse_failure) { // 补偿:每秒根据校准系数调整计数 // 例如,若实际频率偏高,则每秒多计数 0.001 秒,通过软件累加 static float error_accum = 0.0f; error_accum += (g_hsi_calibration - 1.0f); if (error_accum >= 1.0f) { // 增加 1 秒(通过设置 RTC 的 SSR 或直接修改时间) RTC_TimeTypeDef sTime; HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN); sTime.Seconds++; if (sTime.Seconds >= 60) sTime.Seconds = 0; HAL_RTC_SetTime(hrtc, &sTime, RTC_FORMAT_BIN); error_accum -= 1.0f; } } } // 初始化时测量 HSI 频率(示例:使用 TIM5 捕获) void Measure_HSI_Frequency(void) { // 配置 TIM5 输入捕获,测量 HSI/32 信号(需外部连接或内部路由) // 此处省略具体配置,假设得到实际频率 f_actual float f_actual = 500123.0f; // 示例值 g_hsi_calibration = f_actual / 500000.0f; } // 主程序初始化 int main(void) { HAL_Init(); // 使能 PWR 时钟,允许访问备份域 __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 使能 LSE 并等待就绪 __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // 超时处理 } // 配置 RTC 时钟源为 LSE __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); __HAL_RCC_RTC_ENABLE(); // 初始化 RTC(略) // 使能 LSE 错误中断 HAL_RTCEx_SetLSECallback(HAL_RTC_LSE_ErrorCallback); // 测量 HSI 校准系数 Measure_HSI_Frequency(); // 主循环 while (1) { // 应用逻辑 } } ``` # 5. 注意事项 - **备份域写保护**:修改 RTC 时钟源前,必须关闭备份域写保护(通过 PWR_CR 的 DBP 位),否则操作无效。 - **切换时机**:LSE 停振后,RTC 可能已停止,切换前需保存当前时间,避免丢失。 - **校准精度**:HSI 受温度影响,建议定期在 LSE 正常时重新测量校准系数,并存储到备份寄存器。 - **中断优先级**:LSE 错误中断应设为高优先级,确保及时响应。 - **测试验证**:在强干扰环境下测试,观察时间误差,必要时调整补偿算法(如使用 PID)。 # 结语 通过上述方案,STM32F4 在 LSE 失效时能自动切换至 HSI,并借助软件校准保持时间精度,极大提升了系统可靠性。该方法适用于对成本敏感、无法使用外部高精度晶振的场景。开发者可根据实际需求优化补偿算法,实现更精准的计时。