# 引言 在工业控制、物联网设备等嵌入式场景中,RTC(实时时钟)是记录事件时间戳的核心外设。STM32F4 系列通常使用 32.768kHz 的 LSE 晶振为 RTC 提供低功耗、高精度的时钟源。然而,LSE 晶振可能因焊接不良、温漂、振动或 EMI 干扰而停振,一旦停振,RTC 将失去时钟基准,系统时间立即失效。 本文提出一种嵌入式解决方案:当检测到 LSE 停振时,自动切换至内部 HSI(高速内部振荡器,16MHz)作为 RTC 时钟源,并通过软件校准算法补偿 HSI 的频率偏差,使 RTC 在无外部晶振情况下仍能保持较高精度(典型误差 < 50ppm)。 # 原理剖析 ## 1. RTC 时钟源架构 STM32F4 的 RTC 时钟源可选: - LSE:外部低速晶振,精度高(±20ppm),但依赖外部硬件。 - LSI:内部低速 RC 振荡器,约 32kHz,精度差(±50%),不适合计时。 - HSE/HSI 分频:通过 RTCSEL[1:0] 位选择,HSI 经 32 分频后得到 500kHz,再经异步预分频器得到 1Hz 计数时钟。 关键点:RTC 时钟源必须在 RTC 使能前选定,切换时钟源需复位 RTC 域。 ## 2. HSI 频率误差与校准原理 HSI 出厂精度约 ±1%,温度漂移可达 ±2%。直接使用会导致 RTC 日误差达数百秒。校准思路: - 利用 STM32F4 内部的 RTC 校准寄存器(RTC_CALR)进行数字校准,可调整 ±0.95 ppm 步进。 - 但 HSI 误差较大,需先通过软件测量 HSI 与已知参考(如系统主时钟 HSE 或外部信号)的偏差,然后计算校准值。 本文采用更实用的方法:利用定时器输入捕获测量 HSI 分频后的 500kHz 信号周期,与理想值比较,得到误差比例,再动态调整 RTC 的异步预分频因子(ASYNCHPREDIV)或使用 RTC 平滑校准寄存器。 ## 3. 检测 LSE 停振 STM32F4 的 RTC 提供 LSEDRV 和 LSEMOD 位,但无硬件停振检测。常用方法: - 周期性读取 RTC 秒值,若在超时时间内未更新,则判定 LSE 失效。 - 或使用 LSE 就绪标志(LSERDY)轮询,若长时间未置位则停振。 # 配置步骤 ## 1. 初始化 RTC 使用 LSE - 使能 PWR 和备份域时钟。 - 解锁备份域写保护。 - 配置 LSE 并等待就绪。 - 选择 LSE 作为 RTC 时钟源。 - 配置预分频器(异步 127,同步 255)得到 1Hz。 ## 2. 检测 LSE 停振并切换至 HSI - 在主循环中监测 RTC 秒更新标志。 - 若超时(如 2 秒)未更新,则执行切换流程。 ## 3. HSI 校准流程 - 使能 HSI 并等待稳定。 - 配置 TIM5 输入捕获,测量 HSI/32 信号(即 500kHz)的周期。 - 计算实际频率与理想值的偏差。 - 根据偏差调整 RTC 的同步预分频器(或使用校准寄存器)。 # 完整代码示例 以下代码基于 STM32F407,使用 HAL 库。 ```c // 全局变量 volatile uint8_t rtc_updated = 0; volatile uint32_t last_rtc_sec = 0; // 初始化 RTC 使用 LSE void RTC_Init_LSE(void) { __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_BKP_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // 超时处理 } __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); __HAL_RCC_RTC_ENABLE(); RTC_HandleTypeDef hrtc = {0}; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; hrtc.Init.SynchPrediv = 255; hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; HAL_RTC_Init(&hrtc); } // 检测 LSE 停振 uint8_t LSE_Stopped(void) { uint32_t current_sec = HAL_RTC_GetTime(&hrtc, RTC_FORMAT_BIN); if (current_sec == last_rtc_sec) { return 1; // 未更新,可能停振 } last_rtc_sec = current_sec; return 0; } // 切换至 HSI 并校准 void RTC_SwitchToHSI_Calibrate(void) { // 1. 使能 HSI __HAL_RCC_HSI_ENABLE(); while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET); // 2. 配置 TIM5 输入捕获测量 HSI/32 频率 // 假设 TIM5_CH1 连接到 MCO1 输出 HSI/32(需配置 GPIO 和 MCO) // 此处省略具体配置,假设测得实际频率为 freq_hz uint32_t measured_freq = TIM5_MeasureFrequency(); // 返回 Hz // 3. 计算误差比例 float error_ratio = (float)measured_freq / 500000.0f; // 理想 500kHz // 4. 调整同步预分频器(以 1Hz 输出为例) // 同步预分频器值 = (500000 / error_ratio) - 1,但需取整 uint32_t sync_prediv = (uint32_t)((500000.0f / error_ratio) - 1); if (sync_prediv > 0x3FFF) sync_prediv = 0x3FFF; // 限制范围 // 5. 重新配置 RTC 时钟源为 HSI __HAL_RCC_RTC_DISABLE(); __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV32); __HAL_RCC_RTC_ENABLE(); // 6. 设置新的预分频值 hrtc.Init.AsynchPrediv = 127; // 保持不变 hrtc.Init.SynchPrediv = sync_prediv; HAL_RTC_Init(&hrtc); // 7. 重新设置时间(可选,从上次保存的时间恢复) // HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); } // 主循环 int main(void) { HAL_Init(); RTC_Init_LSE(); while (1) { if (LSE_Stopped()) { RTC_SwitchToHSI_Calibrate(); // 记录错误日志,并继续运行 } HAL_Delay(1000); } } ``` # 注意事项 - **切换时机**:必须在 RTC 使能前切换时钟源,否则需复位 RTC 域,导致时间丢失。建议在检测到停振后立即保存当前时间到备份寄存器,再执行切换。 - **校准精度**:HSI 受温度影响较大,建议在系统空闲时周期性重新校准(如每 10 分钟)。 - **测量频率**:使用 TIM 输入捕获时,需确保参考时钟(如 HSE)稳定,否则测量误差会传递到校准结果。 - **功耗考量**:HSI 功耗高于 LSE,若系统对功耗敏感,可考虑在 LSE 恢复后自动切回。 - **备份域写保护**:操作 RTC 和备份寄存器前务必解锁备份域,否则写操作无效。 - **中断处理**:建议使用 RTC 闹钟或唤醒定时器定期检测秒更新,避免主循环阻塞。 # 总结 通过上述方案,STM32F4 在 LSE 停振后仍能依靠 HSI 维持 RTC 运行,并通过软件校准将精度控制在可接受范围。该方法无需额外硬件,适用于对成本敏感的嵌入式产品。开发者可根据实际需求调整校准周期和预分频计算,实现更优的时间保持能力。