STM32F4 系列 LSE 停振后自动切换 HSI 并保持 RTC 走时的实现细节
👁 4 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列 MCU 中,LSE(低速外部晶振)为 RTC 提供精确时钟,但在恶劣环境下可能停振,导致 RTC 失效。本文深入探讨 LSE 停振的检测机制,并给出一种在 LSE 失效后自动切换至 HSI(内部高速 RC 振荡器)并保持 RTC 走时的完整实现方案,涵盖原理、配置步骤、代码示例及注意事项,帮助开发者构建高可靠的嵌入式时钟系统。
# 引言
在嵌入式系统中,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 精度有限,但保证了系统的持续运行,适用于对时间连续性要求高但允许一定漂移的场景。开发者可根据实际需求,进一步优化检测算法或引入外部校准机制。