STM32F4 无痕热切换:LSE 停振后硬件 RTC 备份域接管策略
👁 2 阅读 · 2026-08-27 · 嵌入式
在工业与物联网场景中,LSE 晶振受温度或老化影响可能停振,导致 RTC 时间丢失或系统紊乱。本文深入剖析 STM32F4 备份域结构,提出一种基于硬件 RTC 的 LSE 无痕热切换方案:利用备份寄存器保存时间戳与切换标志,在检测到 LSE 故障后自动切换至 LSI,并借助 RTC 校准与补偿实现时间连续性。文章涵盖原理分析、配置步骤、完整代码示例及工程注意事项,帮助开发者构建高可靠时钟系统。
# 引言
STM32F4 系列的 RTC 通常依赖外部低速晶振(LSE,32.768kHz)以保持低功耗高精度计时。然而,LSE 在恶劣环境(如高低温冲击、振动)或晶振老化下可能停振,导致 RTC 时间停滞,进而影响系统日志、通信协议或定时任务。传统方案是直接切换至内部低速 RC(LSI),但 LSI 精度差(±5%),且切换过程可能造成时间跳变。本文提出一种“无痕热切换”策略:利用备份域寄存器保存时间上下文,在 LSE 停振瞬间无缝切换至 LSI,并通过软件补偿维持时间精度,实现系统无感知的时钟接管。
# 原理剖析
## 1. 备份域结构
STM32F4 的备份域(Backup Domain)包含:
- **RTC 寄存器**:实时时钟核心,支持 LSE 或 LSI 作为时钟源。
- **备份寄存器(BKP)**:20 个 32 位寄存器(BKP0R~BKP19R),在 VBAT 供电下可保持数据。
- **备份域控制寄存器**:控制 RTC 使能、时钟源选择及复位。
关键点:备份域独立于主电源,由 VBAT 引脚供电。当 VDD 掉电时,备份域仍可工作,但 LSE 和 LSI 均可能失效(取决于供电设计)。
## 2. LSE 停振检测
RTC 提供时钟安全系统(CSS)功能,专用于 LSE 监测。当 LSE 停振时,CSS 会触发 RTC_CSS 中断(或事件),并自动将 RTC 时钟源切换至 LSI。但默认切换后,RTC 计数会基于 LSI 继续运行,但时间精度下降,且无补偿机制。
## 3. 热切换策略
我们的策略分为三步:
1. **预存时间戳**:在系统正常运行时,周期性将当前时间(如 Unix 时间戳)写入备份寄存器(例如 BKP0R)。
2. **检测与切换**:使能 CSS 中断,在中断服务函数中记录切换时刻,并立即读取 RTC 当前计数(基于 LSI)作为基准。
3. **软件补偿**:切换后,利用备份寄存器中的时间戳和切换时刻的 RTC 计数,通过软件定时器(如 SysTick)周期性校准时间,补偿 LSI 的频率偏差。
# 配置步骤
## 1. 使能备份域时钟与访问
```c
// 使能 PWR 和 RCC 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE); // 若使用备份SRAM
// 允许访问备份域
PWR_BackupAccessCmd(ENABLE);
// 复位备份域(可选,首次配置时)
RCC_BackupResetCmd(ENABLE);
RCC_BackupResetCmd(DISABLE);
```
## 2. 配置 LSE 并启用 CSS
```c
RCC_LSEConfig(RCC_LSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待就绪
// 选择 LSE 作为 RTC 时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
// 使能 RTC 时钟
RCC_RTCCLKCmd(ENABLE);
// 等待 RTC 寄存器同步
RTC_WaitForSynchro();
// 使能 CSS 中断(在 NVIC 中配置)
RTC_CSSConfig(ENABLE);
```
## 3. 初始化 RTC 并设置时间
```c
RTC_InitTypeDef RTC_InitStruct;
RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
RTC_InitStruct.RTC_AsynchPrediv = 0x7F; // 异步分频 128
RTC_InitStruct.RTC_SynchPrediv = 0xFF; // 同步分频 256,得到 1Hz
RTC_Init(RTC_InitStruct);
// 设置时间(示例:2025-01-01 00:00:00)
RTC_TimeTypeDef time = {0};
time.RTC_Hours = 0;
time.RTC_Minutes = 0;
time.RTC_Seconds = 0;
RTC_SetTime(RTC_Format_BIN, &time);
// 保存初始时间戳到备份寄存器
uint32_t timestamp = get_unix_timestamp(); // 自定义函数
BKP_WriteBackupRegister(BKP_DR1, timestamp);
```
## 4. 编写 CSS 中断服务函数
```c
void RTC_IRQHandler(void) {
if (RTC_GetITStatus(RTC_IT_CSS) != RESET) {
RTC_ClearITPendingBit(RTC_IT_CSS);
// 记录切换时刻的 RTC 计数(基于 LSI)
uint32_t switch_sec = RTC_GetCounter(); // 若使用日历模式,则读取时间并转换
// 保存切换标志和时间到备份寄存器
BKP_WriteBackupRegister(BKP_DR2, switch_sec);
BKP_WriteBackupRegister(BKP_DR3, 0x5A5A); // 标志
// 可选:切换至 LSI 并重新配置分频器(CSS 已自动切换,但需确认)
// 通常 CSS 已自动切换,无需手动操作
// 启动软件补偿定时器(如 SysTick)
SysTick_Config(SystemCoreClock / 1000); // 1ms 中断
}
}
```
## 5. 软件补偿实现
在 SysTick 中断中,每 1 秒(或 10 秒)校准一次时间:
```c
volatile uint32_t tick_count = 0;
volatile uint32_t last_sec = 0;
void SysTick_Handler(void) {
tick_count++;
if (tick_count >= 1000) { // 1 秒
tick_count = 0;
// 读取当前 RTC 计数(基于 LSI)
uint32_t current_sec = RTC_GetCounter();
// 计算偏差:假设 LSI 实际频率为 f_lsi,理想为 32kHz,但实际可能偏差
// 通过备份寄存器中的基准时间戳和切换时刻,计算补偿值
uint32_t base_ts = BKP_ReadBackupRegister(BKP_DR1);
uint32_t switch_sec = BKP_ReadBackupRegister(BKP_DR2);
uint32_t elapsed = current_sec - switch_sec; // 经过的秒数(基于LSI)
uint32_t real_elapsed = (tick_count / 1000) + (base_ts - switch_sec); // 简化,实际需用系统时钟
// 这里仅演示,实际应使用系统时钟(如 TIM)来测量真实时间
// 若偏差超过阈值,则调整 RTC 计数(需谨慎)
// 更稳健的方法:使用 RTC 同步分频器校准,但需要重新初始化
}
}
```
实际工程中,建议使用一个硬件定时器(如 TIM2)作为高精度时间基准,在切换后持续校准 RTC。
# 完整代码示例
以下为关键部分整合(省略初始化细节):
```c
// main.c
int main(void) {
// 系统时钟配置(略)
// 使能备份域访问
PWR_BackupAccessCmd(ENABLE);
// 检查备份寄存器标志,判断是否发生过切换
if (BKP_ReadBackupRegister(BKP_DR3) == 0x5A5A) {
// 已切换至 LSI,执行恢复流程
// 读取备份时间戳,并设置 RTC 时间
uint32_t saved_ts = BKP_ReadBackupRegister(BKP_DR1);
// 转换为 RTC 时间并设置(略)
} else {
// 正常启动,配置 LSE 和 RTC
// ...(见上文配置步骤)
}
// 使能 CSS 中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
while (1) {
// 主循环
}
}
```
# 注意事项
- **备份域供电**:确保 VBAT 引脚连接到电池或超级电容,否则掉电后备份寄存器数据丢失,热切换失效。
- **CSS 中断优先级**:应设置为高优先级,确保及时响应。
- **LSI 校准**:LSI 频率随温度变化,建议定期(如每小时)通过外部高精度时钟(如 GPS 或网络)校准一次。
- **时间戳保存频率**:不要过于频繁写入备份寄存器(寿命有限),建议每分钟或每分钟一次。
- **切换后的 RTC 配置**:CSS 自动切换后,RTC 的分频器仍按 LSE 参数配置,但 LSI 频率不同,会导致时间快慢。因此,切换后需重新配置分频器(例如,LSI 典型 32kHz,但实际可能 30-34kHz,需测量)。
- **软件补偿的复杂度**:若对时间精度要求极高(如 <1ppm),建议使用外部高精度时钟源,或采用双 RTC 方案。
# 总结
通过备份域寄存器和 CSS 中断,STM32F4 可以在 LSE 停振时无缝切换至 LSI,并利用预存时间戳和软件补偿保持时间连续性。该方案成本低、实现简单,适用于大多数工业场景。开发者应根据实际需求调整补偿策略,并充分测试 LSI 频率特性。