# STM32F4 系列 LSE 停振后基于 RTC 备份域寄存器的无痕恢复策略 ## 1. 问题背景与原理 在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(32.768kHz 外部晶振)提供精准时基。然而,LSE 可能因晶振老化、外部干扰或 PCB 布局不当而停振。一旦 LSE 失效,RTC 将失去时钟源,时间数据无法更新,甚至可能导致系统复位后时间归零。 STM32F4 系列提供了 **RTC 备份域寄存器(BKP)**,这是一块由 VBAT 供电的独立存储区域,即使在主电源断电或系统复位时,只要 VBAT 有电,数据就能保留。利用这一特性,我们可以在 LSE 停振前将关键状态(如时间戳、校准值、标志位)存入备份域,并在复位后读取,实现“无痕恢复”。 ## 2. 硬件与软件准备 - 硬件:STM32F4 开发板(如 STM32F407)、外部 32.768kHz 晶振、备用电池(连接 VBAT)。 - 软件:STM32CubeIDE 或 Keil MDK,HAL 库或标准外设库。本文以 HAL 库为例。 ## 3. 核心原理:备份域寄存器与 RTC 配置 ### 3.1 备份域寄存器(BKP) 备份域包含 20 个 32 位数据寄存器(BKP0R~BKP19R),以及控制寄存器。它们由 VBAT 供电,复位后数据不丢失(除非 VBAT 掉电)。访问前需使能 PWR 和 BKP 时钟,并设置 PWR_CR 的 DBP 位(备份域写保护)。 ### 3.2 LSE 停振检测 RTC 提供 **RTC_ISR 寄存器的 LSERDY 标志** 来指示 LSE 是否就绪。当 LSERDY 为 0 时,表示 LSE 停振。我们可以在主循环中周期性检查该标志,一旦发现停振,立即将当前时间戳和状态写入备份域。 ### 3.3 恢复流程 1. 系统上电或复位后,先检查备份域中的标志位(如 0xA5A5)是否有效。 2. 若有效,则读取备份的时间戳,并重新配置 RTC(使用 LSI 或 HSE 作为备用时钟源),将时间写入 RTC。 3. 清除标志位,完成恢复。 ## 4. 配置步骤 ### 4.1 使能备份域访问 ```c __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_BKP_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 设置 DBP 位 ``` ### 4.2 配置 RTC(正常模式,使用 LSE) ```c RTC_HandleTypeDef hrtc; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; hrtc.Init.SynchPrediv = 255; 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 __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) != SET); // 等待就绪 ``` ### 4.3 检测 LSE 停振并保存状态 在主循环中,定期检查 LSERDY 标志: ```c if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // LSE 停振,保存当前时间到备份域 uint32_t timestamp = HAL_RTC_GetTimestamp(&hrtc); // 自定义函数获取时间戳 HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, timestamp); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0xA5A5); // 标志位 // 可切换至 LSI 继续运行,或进入安全模式 } ``` ### 4.4 复位后恢复 在系统初始化时,检查备份域标志: ```c uint32_t flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2); if (flag == 0xA5A5) { uint32_t saved_time = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); // 重新初始化 RTC,使用 LSI 作为时钟源 __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); __HAL_RCC_LSI_ENABLE(); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) != SET); // 配置 RTC 使用 LSI,并设置时间 hrtc.Init.SynchPrediv = 0x7A; // 根据 LSI 频率调整 HAL_RTC_Init(&hrtc); HAL_RTC_SetTime(&hrtc, &saved_time, RTC_FORMAT_BIN); // 清除标志 HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0); } ``` ## 5. 完整代码示例 以下是一个简化的完整示例,包含初始化、检测和恢复逻辑: ```c #include "stm32f4xx_hal.h" RTC_HandleTypeDef hrtc; void RTC_Init(void) { __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_BKP_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); 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 __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); uint32_t timeout = 1000; while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) != SET) { if (--timeout == 0) break; } // 检查备份域标志,决定是否恢复 if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2) == 0xA5A5) { // 恢复时间 RTC_TimeTypeDef sTime; sTime.Hours = (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) >> 16) & 0xFF; sTime.Minutes = (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) >> 8) & 0xFF; sTime.Seconds = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) & 0xFF; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0); // 清除标志 } else { // 正常初始化时间 RTC_TimeTypeDef sTime = {0}; sTime.Hours = 12; sTime.Minutes = 0; sTime.Seconds = 0; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); } } void Check_LSE_And_Save(void) { if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { // LSE 停振,保存当前时间 RTC_TimeTypeDef sTime; HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); uint32_t packed = (sTime.Hours << 16) | (sTime.Minutes << 8) | sTime.Seconds; HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, packed); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0xA5A5); // 可切换至 LSI 继续运行 __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); __HAL_RCC_LSI_ENABLE(); // 重新配置 RTC 使用 LSI(略) } } int main(void) { HAL_Init(); RTC_Init(); while (1) { Check_LSE_And_Save(); HAL_Delay(1000); } } ``` ## 6. 注意事项 - **备份域写保护**:访问 BKP 寄存器前必须使能 PWR 和 BKP 时钟,并设置 DBP 位,否则写入无效。 - **VBAT 供电**:确保 VBAT 引脚连接电池或大电容,否则备份域数据在断电后丢失。 - **时间戳格式**:示例中简单打包了时分秒,实际应用可扩展为完整日期时间(使用 BKP 多个寄存器)。 - **时钟切换**:LSE 停振后,RTC 时钟源需切换为 LSI,但 LSI 精度较低,可能影响时间准确性,需在恢复后重新校准。 - **标志位唯一性**:使用 0xA5A5 作为有效标志,避免随机数据误判。 - **中断处理**:可配置 LSE 中断(RTC_LSECSS)来立即响应停振,而不是轮询。 ## 7. 总结 通过利用 RTC 备份域寄存器,我们可以在 LSE 停振时保存关键时间信息,并在复位后无痕恢复,显著提升系统的可靠性。该方法实现简单,成本低,适用于对时间连续性要求高的嵌入式应用。开发者可根据实际需求扩展,例如保存更多状态或实现多级备份。