# 引言 STM32F4 系列内置的 RTC(实时时钟)通常依赖外部 32.768kHz 晶振(LSE)获得高精度。但在恶劣环境(如振动、温漂)或晶振老化下,LSE 可能停振。一旦停振,若未做处理,RTC 时间将冻结或丢失,影响日志、通信协议等。传统方案依赖外部 RTC 芯片或频繁校准,成本高且复杂。本文利用 STM32F4 的备份域(Backup Domain)特性,实现 LSE 停振后的无感时钟恢复:时间数据保存在 VBAT 供电的备份寄存器中,当检测到 LSE 故障时,自动切换至内部低速时钟(LSI)并恢复时间,整个过程对用户透明。 # 1. 备份域与 RTC 架构原理 STM32F4 的备份域由 VBAT 引脚供电(通常接 3V 电池),包含: - **RTC 计数器**:记录秒、分、时、日期,由 LSE 或 LSI 驱动。 - **备份寄存器(BKP)**:20 个 32 位寄存器,可保存任意数据,复位后内容保留(只要 VBAT 有电)。 - **RTC 校准寄存器**:用于频率补偿。 关键点:当 LSE 停振时,RTC 时钟源可自动切换至 LSI(约 32kHz,精度较差)。但 LSI 频率随温度漂移,直接使用会导致时间误差。因此,我们采用策略: - 正常工作时,RTC 使用 LSE,并定期将当前时间戳和 LSE 校准值写入备份寄存器。 - 检测到 LSE 故障(通过 RTC_ISR 的 LSERDY 位或时钟安全系统 CSS)后,立即切换至 LSI,并从备份寄存器恢复时间戳,同时应用基于 LSI 的补偿系数(可预先测量)。 # 2. 硬件设计要点 - VBAT 引脚必须接电池或超级电容,容量需满足断电保持时间。 - LSE 晶振负载电容匹配(典型 6-12pF),并加反馈电阻。 - 若使用 CSS(时钟安全系统),需在 NVIC 中使能 RTC 全局中断。 # 3. 软件配置步骤 ## 3.1 初始化备份域与 RTC ```c void RTC_Init(void) { // 1. 使能 PWR 时钟,并允许访问备份域 __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 2. 选择 LSE 作为 RTC 时钟源(若 LSE 可用) __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // 等待 LSE 就绪,超时则标记故障 } __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); __HAL_RCC_RTC_ENABLE(); // 3. 配置 RTC 参数(24小时制,秒中断等) RTC_HandleTypeDef hrtc; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; // 异步分频 hrtc.Init.SynchPrediv = 255; // 同步分频,得到 1Hz hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; HAL_RTC_Init(&hrtc); } ``` ## 3.2 定期保存时间戳到备份寄存器 ```c void Save_TimeToBackup(void) { RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate; HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 将时间转换为 Unix 时间戳(简化示例) uint32_t timestamp = ConvertToUnix(sDate.Year, sDate.Month, sDate.Date, sTime.Hours, sTime.Minutes, sTime.Seconds); // 写入备份寄存器(例如 BKP0) HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, timestamp); // 同时保存 LSE 校准值(假设有) HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, lse_calibration_value); } ``` ## 3.3 检测 LSE 故障并切换时钟源 ```c void Check_LSE_Failure(void) { // 检查 LSE 就绪标志 if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // LSE 停振! // 1. 从备份寄存器读取时间戳 uint32_t saved_ts = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); // 2. 切换 RTC 时钟源到 LSI __HAL_RCC_RTC_DISABLE(); __HAL_RCC_LSI_ENABLE(); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET); __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI); __HAL_RCC_RTC_ENABLE(); // 3. 重新初始化 RTC 并设置时间(基于 LSI) RTC_Init(); // 注意:此函数会重新配置,需调整 Set_RTC_Time(saved_ts); // 4. 应用 LSI 补偿(例如每秒加 30ppm 修正) // 可在秒中断中动态调整 } } ``` ## 3.4 完整代码示例(主循环) ```c int main(void) { HAL_Init(); SystemClock_Config(); RTC_Init(); // 启动时检查是否有备份时间 if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0xFFFFFFFF) { // 有备份,恢复时间(例如上电时) uint32_t ts = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); Set_RTC_Time(ts); } while (1) { // 每 10 秒保存一次时间 if (tick - last_save > 10000) { Save_TimeToBackup(); last_save = tick; } Check_LSE_Failure(); // 其他任务 } } ``` # 4. 注意事项 - **备份寄存器写入次数**:无限制,但建议定时写入(如每 10 秒)以平衡功耗和可靠性。 - **LSI 精度补偿**:LSI 频率在 25°C 时约 32kHz,但偏差可达 ±3%。建议在初始化时测量 LSI 实际频率(通过定时器比较),并存储补偿系数。 - **切换时序**:切换时钟源时,RTC 会短暂停止(约 2 个时钟周期),需确保时间误差可接受。 - **中断处理**:若使用 CSS,在 RTC_IRQHandler 中检测 LSERDY 下降沿,并执行切换。 - **备份域复位**:若 VBAT 掉电,备份寄存器内容丢失,需重新初始化时间。 - **调试建议**:使用逻辑分析仪观察 LSE 波形,模拟停振(断开晶振)验证恢复逻辑。 # 5. 总结 通过备份域和备份寄存器,STM32F4 可以在 LSE 停振后实现无感时钟恢复,无需外部硬件。该方法利用了芯片内置的 VBAT 供电特性,成本低且响应快。实际项目中,建议结合看门狗和错误日志,进一步提升系统鲁棒性。希望本文能为你设计高可靠时钟系统提供参考。