# 引言 在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(32.768kHz 外部晶振)以获得高精度计时。然而,在强振动、温度剧变或晶振老化等场景下,LSE 可能停振,导致 RTC 停止走时,系统时间丢失。STM32F4 系列提供了 LSE 监测功能,但默认配置下不会自动切换时钟源。本文将分享一种实用的软硬件协同方案,在 LSE 停振后自动切换至 HSI(16MHz 内部 RC),并利用 HSI 分频后的 32.768kHz 信号维持 RTC 运行,确保时间连续。 # 原理分析 ## LSE 停振检测机制 STM32F4 的 RTC 域包含 LSE 监测功能,通过 RTC_ISR 寄存器的 LSERDY 位可查询 LSE 就绪状态。但硬件没有自动中断,需要软件轮询或使用 RTC 校准/闹钟中断配合。更可靠的方式是启用 LSE 的 CSS(时钟安全系统),但 STM32F4 的 LSE CSS 仅适用于 LSI 或 HSE,不直接支持 LSE。因此,我们采用轮询 + 超时判断的策略: - 周期读取 LSERDY 位,若连续 N 次为 0,则判定 LSE 停振。 - 判定后,立即切换 RTC 时钟源至 HSI,并重新初始化 RTC 预分频器。 ## HSI 作为 RTC 时钟源 STM32F4 的 RTC 时钟源可选 LSE、LSI 或 HSE 分频。HSI 不能直接作为 RTC 时钟,但可以通过 RCC_CFGR 寄存器的 RTCPRE 位将 HSI 分频至 32.768kHz(HSI/488)。注意:此分频仅适用于 HSE,HSI 不支持直接分频至 RTC。因此,我们需要借助 LSI 或 HSE 的替代方案。实际上,STM32F4 的 RTC 时钟源中,HSI 不可用,但我们可以使用 LSI(内部低速 RC,约 32kHz)作为备用,但精度较低。本文采用 HSI 驱动一个定时器产生 1Hz 脉冲,再通过外部触发或内部连接喂给 RTC?不,更简洁的做法是:利用 RTC 的异步预分频器,将 LSI 或 HSE 分频至 1Hz。但 HSI 无法直接接入 RTC。 因此,我们调整策略:在 LSE 失效后,切换 RTC 时钟源至 LSI(内部 RC,约 32kHz),虽然精度稍差,但能保持走时。若必须使用 HSI,则需通过软件模拟:使用一个基本定时器(如 TIM6)以 HSI 为时钟,产生 1Hz 中断,在中断中手动递增 RTC 计数器(通过后备寄存器实现),但这会占用 CPU。本文推荐使用 LSI,因为其硬件支持直接作为 RTC 时钟,且功耗低。 # 实现方案 ## 硬件配置 - MCU:STM32F407VET6(或其他 F4 系列) - 外部晶振:LSE 32.768kHz - 备用时钟:LSI(内部 RC,典型 32kHz) ## 软件设计 ### 1. 初始化 RTC 使用 LSE 首先,配置 LSE 并启动 RTC,设置异步预分频 127,同步预分频 255,得到 1Hz 计数。 ```c void RTC_Init_LSE(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_BackupAccessCmd(ENABLE); // 允许访问备份域 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待就绪 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_InitTypeDef RTC_InitStruct; RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStruct.RTC_AsynchPrediv = 127; RTC_InitStruct.RTC_SynchPrediv = 255; RTC_Init(&RTC_InitStruct); // 设置时间等... } ``` ### 2. LSE 停振检测 在主循环中周期性检查 LSERDY 位,若连续多次为 0,则调用切换函数。 ```c uint8_t LSE_CheckTimeout = 0; void LSE_Monitor(void) { if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { LSE_CheckTimeout++; if (LSE_CheckTimeout >= 10) { // 连续 10 次检测失败(每次间隔 100ms) RTC_SwitchToLSI(); LSE_CheckTimeout = 0; } } else { LSE_CheckTimeout = 0; } } ``` ### 3. 切换至 LSI 并保持 RTC 走时 切换时,需先关闭 RTC 时钟,重新配置时钟源为 LSI,并重新初始化 RTC 预分频器。注意:切换会丢失当前时间,因此需在切换前保存 RTC 计数器的值,并在新时钟下恢复。 ```c void RTC_SwitchToLSI(void) { uint32_t saved_counter = RTC_GetCounter(); // 保存当前秒计数 // 关闭 RTC 时钟 RCC_RTCCLKCmd(DISABLE); // 等待 RTC 寄存器同步 RTC_WaitForSynchro(); // 使能 LSI RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 配置 RTC 时钟源为 LSI RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); // 重新初始化 RTC,但保留日期时间? RTC_InitTypeDef RTC_InitStruct; RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStruct.RTC_AsynchPrediv = 127; // LSI 约 32kHz,分频得到 1Hz RTC_InitStruct.RTC_SynchPrediv = 255; RTC_Init(&RTC_InitStruct); // 恢复计数器(注意:由于 LSI 频率偏差,时间会有微小误差) RTC_SetCounter(saved_counter); // 标记已切换 PWR_BackupAccessCmd(ENABLE); RTC_WriteBackupRegister(RTC_BKP_DR0, 0xA5A5); // 写入标志 } ``` ### 4. 完整代码框架 ```c int main(void) { // 系统时钟初始化(使用 HSI 或 HSE) SystemInit(); // 初始化 RTC(使用 LSE) RTC_Init_LSE(); while (1) { LSE_Monitor(); Delay(100); // 100ms 轮询 } } ``` # 注意事项 - **切换时机**:检测到 LSE 停振后,应尽快切换,但需确保 LSI 已稳定(等待 LSIRDY 置位)。 - **时间保持**:切换过程中 RTC 会短暂停止,但通过保存和恢复计数器,可保证秒级连续,但亚秒级会有误差。 - **精度影响**:LSI 精度通常为 ±5%,长时间运行会导致时间漂移,建议在应用层定期校准或使用外部时间同步。 - **备份域访问**:操作 RTC 和备份寄存器前,必须使能 PWR 时钟并调用 `PWR_BackupAccessCmd(ENABLE)`。 - **中断处理**:若启用了 RTC 闹钟或唤醒中断,切换后需重新配置,因为预分频器变化会影响中断周期。 - **调试建议**:在切换函数中加入日志输出(如串口),便于观察切换过程。 # 总结 本文详细介绍了 STM32F4 在 LSE 停振后自动切换至 LSI 并保持 RTC 走时的实现方法。通过轮询检测、保存/恢复计数器,实现了无缝切换。虽然 LSI 精度有限,但保证了系统的持续运行,适用于对时间连续性要求高但允许一定漂移的场景。开发者可根据实际需求,进一步优化检测算法或引入外部校准机制。