# 一、问题背景与硬件特性 STM32F4 系列(如 STM32F407)的 RTC 时钟源可选 LSE、LSI 或 HSE 分频。LSE 精度高(典型 ±20ppm),但依赖外部晶振,易受振动、湿度或焊接应力影响而停振。一旦 LSE 失效,RTC 将停止计数,若系统依赖 RTC 唤醒或时间戳,则可能引发逻辑错乱。 STM32F4 的 RTC 模块内置 **RTC_ISR 寄存器的 LSERDY 标志位** 用于指示 LSE 就绪状态,同时 **RTC_TAFCR 寄存器(TAMPFLT 等)** 可配置入侵检测,但最直接的方式是轮询或中断。更关键的是,**RTC 备份域寄存器(BKP)** 在 VDD 或 VBAT 供电下均能保持数据,且与 RTC 共享复位域,非常适合存储故障快照。 # 二、无损切换原理 当检测到 LSE 停振(LSERDY=0),我们需要立即将 RTC 时钟源切换到 LSI(约 32kHz,精度较低但可靠)。切换过程必须保证 RTC 计数器不丢失,因此不能直接复位 RTC。 STM32F4 的 RTC 时钟源切换流程如下: 1. 进入初始化模式(INIT=1),此时 RTC 停止计数。 2. 修改 RTC_CR 寄存器的时钟源选择位(SEL),从 LSE 改为 LSI。 3. 退出初始化模式(INIT=0),RTC 恢复计数。 由于 RTC 计数器(RTC_TR/RTC_DR)在初始化模式下保持原值,切换后时间连续,实现“无损”。同时,我们利用备份寄存器记录切换前的 LSE 状态、切换时间戳和故障原因。 # 三、硬件与软件准备 - 硬件:STM32F407 开发板,外部 32.768kHz 晶振,备用电池连接 VBAT。 - 软件:STM32CubeIDE(或 Keil),HAL 库。 # 四、CubeMX 配置步骤 1. **RTC 配置**: - 激活 RTC,选择时钟源为 LSE,开启 Calendar。 - 在 NVIC 设置中使能 RTC 全局中断(可选,用于秒中断或闹钟)。 2. **备份寄存器**:无需额外配置,直接通过 HAL 函数访问。 3. **LSI 时钟**:默认开启,无需手动配置。 4. **调试串口**:用于输出日志,配置 USART2 异步模式。 # 五、代码实现 ## 5.1 定义故障记录结构 ```c // 备份寄存器地址映射(每个 32 位) #define BKP_REG_FAULT_FLAG 1 // 故障标志,0x5A5A 表示有记录 #define BKP_REG_FAULT_TIME 2 // 故障发生时的 Unix 时间戳(简化) #define BKP_REG_FAULT_SRC 3 // 故障源:1=LSE停振,2=切换成功 // 故障记录结构体(从备份寄存器读取) typedef struct { uint16_t flag; uint32_t timestamp; uint8_t source; } FaultRecord; ``` ## 5.2 LSE 停振检测与切换函数 ```c void RTC_LSE_FaultHandler(void) { // 1. 读取当前 RTC 时间(切换前) RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate; HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 2. 构造简单时间戳(示例:仅保存分钟计数) uint32_t fault_time = (sDate.Year * 365 + sDate.Month * 30 + sDate.Date) * 1440 + sTime.Hours * 60 + sTime.Minutes; // 3. 进入 RTC 初始化模式 HAL_RTC_InitTypeDef rtc_init = {0}; __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); hrtc.Instance->ISR |= RTC_ISR_INIT; while ((hrtc.Instance->ISR & RTC_ISR_INITF) == 0); // 4. 切换时钟源到 LSI hrtc.Instance->CR &= ~RTC_CR_SEL; // 清零 SEL 位 hrtc.Instance->CR |= RTC_CR_SEL_0; // 选择 LSI(参考手册:SEL=1 时用 LSI) // 5. 退出初始化模式 hrtc.Instance->ISR &= ~RTC_ISR_INIT; __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); // 6. 写入备份寄存器(先写标志,再写时间,最后写源) HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_FAULT_FLAG, 0x5A5A); HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_FAULT_TIME, fault_time); HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_FAULT_SRC, 1); // 7. 重新配置 RTC 中断(可选) printf("[FAULT] LSE stopped, switched to LSI at %lu\n", fault_time); } ``` ## 5.3 主循环中的轮询检测 ```c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_RTC_Init(); MX_USART2_UART_Init(); // 启动时检查备份寄存器是否有历史故障 uint16_t flag = HAL_RTCEx_BKUPRead(&hrtc, BKP_REG_FAULT_FLAG); if (flag == 0x5A5A) { FaultRecord rec; rec.flag = flag; rec.timestamp = HAL_RTCEx_BKUPRead(&hrtc, BKP_REG_FAULT_TIME); rec.source = HAL_RTCEx_BKUPRead(&hrtc, BKP_REG_FAULT_SRC); printf("Previous fault: time=%lu, source=%d\n", rec.timestamp, rec.source); // 清除标志,避免重复报告 HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_FAULT_FLAG, 0); } while (1) { // 轮询 LSE 就绪位(也可用中断,但轮询简单可靠) if (!__HAL_RTC_LSE_READY(&hrtc)) { RTC_LSE_FaultHandler(); // 等待切换完成,然后继续运行 HAL_Delay(100); } HAL_Delay(1000); // 正常业务代码... } } ``` # 六、注意事项 - **备份寄存器写保护**:在写入前必须关闭 PWR 备份域写保护(`__HAL_RTC_WRITEPROTECTION_DISABLE`),否则写入无效。 - **时钟源切换后的校准**:LSI 精度差,若系统对时间精度有要求,建议在切换后定期用外部参考(如 GPS 或网络)校准 RTC。 - **中断优先级**:如果使用 RTC 闹钟中断来检测 LSE,务必确保中断优先级高于其他可能阻塞的任务,避免延迟切换。 - **备份寄存器数量**:STM32F4 有 20 个 32 位备份寄存器,可扩展记录更多信息(如多次故障)。 - **测试方法**:实际断开 LSE 晶振引脚(或软件强制清除 LSERDY 位)模拟停振,观察切换是否成功。 # 七、总结 通过 RTC 备份域寄存器,我们不仅实现了 LSE 停振时的无损时钟切换,还保留了故障现场,极大提升了系统的可维护性。该方案适用于对时间连续性要求高的工业设备、数据采集终端等场景。开发者可根据实际需求扩展故障记录内容,例如记录切换后的累计运行时间、环境温度等。