# 引言 在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(32.768kHz 晶振)保持时间基准。然而,晶振可能因环境干扰、老化或焊接问题而停振。一旦 LSE 失效,RTC 时间会丢失,若系统随后复位,默认时间将导致日志、通信协议或调度逻辑错乱。STM32F4 系列提供了备份寄存器(BKP)和 RTC 校准机制,我们可以利用它们实现“无感恢复”——即使 LSE 停振,系统重启后也能恢复到停振前的状态。 # 原理分析 ## 1. LSE 停振检测 STM32F4 的 RTC 模块包含一个时钟安全系统(CSS)用于 LSE,但默认未启用。启用后,若 LSE 失效,RTC 会触发 CSS 中断,并将 RTC 时钟自动切换到 LSI(内部低速时钟)。但 LSI 精度远低于 LSE,时间会漂移。更关键的是,如果系统复位,RTC 会尝试重新使用 LSE,若 LSE 仍故障,则可能进入死循环或使用默认时间。 ## 2. 备份寄存器(BKP)的作用 BKP 是 20 个 32 位寄存器(位于备份域),由 VBAT 供电,在系统复位或电源掉电时数据不丢失(前提是 VBAT 有电)。我们可以利用 BKP 存储: - RTC 在 LSE 停振前的秒数(或完整时间戳) - 停振标志(如 0xA5A5) - 其他系统状态(如任务计数、校准值) 这样,系统重启后,固件检查 BKP 中的标志,若有效,则用保存的时间初始化 RTC,并切换到 LSI 继续运行,实现无感恢复。 ## 3. 恢复流程 - 正常运行时,定期(如每秒)将 RTC 时间写入 BKP。 - 检测到 LSE 停振(通过 CSS 中断或轮询 RTC_ISR 的 LSERDY 位),立即将当前时间写入 BKP,并设置标志。 - 系统复位后,启动代码检查 BKP 标志,若有效,则用 BKP 中的时间初始化 RTC,并强制使用 LSI,跳过 LSE 启动等待。 # 配置步骤 ## 1. 使能备份域访问和 BKP 时钟 ```c RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 允许访问备份域 BKP_DeInit(); // 可选,但会清除所有备份数据 ``` ## 2. 配置 RTC 时钟源(LSE 或 LSI) ```c RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 优先使用 LSE RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); // 等待同步 ``` ## 3. 启用 LSE CSS 中断(可选) ```c RTC_ITConfig(RTC_IT_CSS, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_Init(&NVIC_InitStructure); ``` ## 4. 定期保存时间到 BKP 在 RTC 秒中断或主循环中调用 `SaveTimeToBKP()`。 ## 5. 启动时检查并恢复 在 `main()` 初始化 RTC 之前,先检查 BKP 标志。 # 完整代码示例 以下代码基于 STM32F4xx 标准外设库,演示核心逻辑。 ```c #include "stm32f4xx.h" #include #define BKP_FLAG_VALUE 0xA5A5 #define BKP_FLAG_REG BKP_DR1 #define BKP_TIME_REG BKP_DR2 // 保存当前时间到 BKP void SaveTimeToBKP(void) { uint32_t seconds = RTC_GetCounter(); // 假设 RTC 计数为秒 BKP_WriteBackupRegister(BKP_TIME_REG, seconds); BKP_WriteBackupRegister(BKP_FLAG_REG, BKP_FLAG_VALUE); } // 检查 BKP 标志是否有效 uint8_t IsBKPValid(void) { return (BKP_ReadBackupRegister(BKP_FLAG_REG) == BKP_FLAG_VALUE); } // 从 BKP 恢复时间 uint32_t RestoreTimeFromBKP(void) { return BKP_ReadBackupRegister(BKP_TIME_REG); } // 初始化 RTC 使用 LSE 或 LSI void RTC_Init(uint8_t useLSE) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); if (useLSE) { RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待 LSE 就绪 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); } else { RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); } RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); // 配置 RTC 为 1 秒中断(略) } int main(void) { // 初始化系统时钟等 // 检查 BKP 标志 if (IsBKPValid()) { // 有备份,使用 LSI 快速启动 RTC_Init(0); // 使用 LSI uint32_t savedTime = RestoreTimeFromBKP(); RTC_SetCounter(savedTime); // 恢复时间 // 清除标志,避免下次重复恢复 BKP_WriteBackupRegister(BKP_FLAG_REG, 0); printf("恢复时间: %lu\n", savedTime); } else { // 无备份,正常使用 LSE RTC_Init(1); // 使用 LSE // 启动后立即保存一次 SaveTimeToBKP(); } // 主循环中每秒保存时间 while (1) { // 假设有秒中断或轮询 // SaveTimeToBKP(); } } // RTC 中断处理(CSS 检测) void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_CSS) != RESET) { RTC_ClearITPendingBit(RTC_IT_CSS); // LSE 停振,立即保存时间,并切换到 LSI SaveTimeToBKP(); // 重新配置 RTC 使用 LSI(需先关闭 RTC 时钟) RCC_RTCCLKCmd(DISABLE); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); // 注意:切换后 RTC 计数器会保留,但时间基准变化,需重新校准 } } ``` # 注意事项 - **备份域电源**:BKP 依赖 VBAT 供电,若 VBAT 也掉电,数据会丢失。确保 VBAT 接电池或大电容。 - **LSE 启动超时**:在等待 LSE 就绪时,应加入超时机制,避免死循环。若超时,直接切换到 LSI。 - **时间精度**:LSI 精度差(约 ±1%),恢复后时间会漂移,建议在 LSE 恢复后重新校准或同步。 - **BKP 写入次数**:BKP 寄存器没有写入次数限制,但频繁写入会增加功耗,建议每秒或每 10 秒一次。 - **清除标志**:恢复后清除 BKP 标志,防止下次复位重复恢复旧时间。 - **中断优先级**:CSS 中断应设为高优先级,确保及时处理。 # 总结 通过 RTC 备份寄存器,STM32F4 可以在 LSE 停振后保存关键时间信息,并在系统重启时快速恢复,实现无感衔接。此方法简单可靠,适用于对时间连续性要求高的场景。结合 LSI 切换和超时处理,可构建健壮的时钟系统。