STM32F4 无晶振时间保持:LSE 停振后基于 RTC 备份域寄存器的可靠方案
👁 1 阅读 · 2026-08-29 · 嵌入式
在嵌入式系统中,RTC 常依赖外部 32.768kHz 晶振(LSE)维持时间,但晶振易受环境或布局影响而停振,导致时间丢失。本文深入剖析 STM32F4 的 RTC 备份域机制,提出一种在 LSE 失效后自动切换至 LSI 并利用备份寄存器保存关键时间戳的软件补偿方案,实现无外部晶振情况下的可靠时间保持。文章涵盖原理分析、配置步骤、完整代码及工程注意事项,适合有 STM32 开发经验的工程师参考。
# STM32F4 无晶振时间保持:LSE 停振后基于 RTC 备份域寄存器的可靠方案
## 1. 问题背景与挑战
STM32F4 系列微控制器内置 RTC(实时时钟),通常依赖外部低速晶振(LSE,32.768kHz)提供精准时基。然而,LSE 晶振在恶劣环境(高湿度、强振动)或 PCB 布局不当(走线过长、寄生电容过大)时可能停振。一旦 LSE 失效,RTC 默认停止计时,系统重启后时间归零,严重影响数据记录或通信协议的时间戳功能。
传统解决方案是直接切换至内部 LSI(约 32kHz,精度 ±5%),但 LSI 频率偏差大,长时间运行会累积分钟级误差。本文提出一种结合备份域寄存器的软件补偿策略:在 LSE 停振时,自动切换至 LSI,并周期性将当前时间戳写入 RTC 备份寄存器(BKP),同时记录 LSI 的漂移系数,实现无外部晶振下的可靠时间保持。
## 2. 原理剖析:RTC 备份域与时钟源切换
### 2.1 备份域结构
STM32F4 的备份域包括 RTC、备份寄存器(BKP,20 个 32 位寄存器)和备份 SRAM(2KB)。该域由 VBAT 引脚供电,当主电源 VDD 掉电时,只要 VBAT 连接电池,RTC 和 BKP 数据依然保留。备份域复位独立于系统复位,可通过设置 PWR_CR 的 DBP 位解除写保护。
### 2.2 时钟源选择
RTC 时钟源可选 HSE/128、LSE 或 LSI。LSE 精度最高(±20ppm),LSI 精度较低(±5%)。在 LSE 停振时,硬件会自动切换至 LSI(若已使能),但 RTC 的计数频率变化会导致时间跳变。因此,我们需要在软件中检测 LSE 状态,并启动补偿机制。
### 2.3 补偿策略
- **检测**:通过 RTC_ISR 寄存器的 LSERDY 位判断 LSE 是否就绪。若为 0,则进入降级模式。
- **切换**:将 RTC 时钟源改为 LSI,并重新初始化 RTC 预分频器(异步预分频 127,同步预分频 255,得到 1Hz 计数)。
- **备份**:在切换前,将当前时间(秒数)写入 BKP 寄存器(如 BKP0R),并记录切换时刻的 LSI 计数值。
- **补偿**:在降级模式下,周期性(如每秒)读取 LSI 计数,计算实际经过时间,并修正 RTC 计数。
## 3. 配置步骤
### 3.1 使能备份域访问
首先,需要使能 PWR 和 BKP 时钟,并解除备份域写保护。
```c
// 使能 PWR 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 使能备份域访问
PWR_BackupAccessCmd(ENABLE);
// 使能 BKP 时钟(若使用 BKP 寄存器)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
```
### 3.2 配置 LSE 与 LSI
初始化 LSE 并等待就绪,同时使能 LSI 作为备用。
```c
RCC_LSEConfig(RCC_LSE_ON);
// 等待 LSE 就绪,超时处理
uint32_t timeout = 0;
while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout < 1000)) {
timeout++;
}
// 使能 LSI
RCC_LSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
```
### 3.3 初始化 RTC 并选择时钟源
若 LSE 就绪,使用 LSE;否则使用 LSI。
```c
RTC_InitTypeDef RTC_InitStruct;
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET) {
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
} else {
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
}
RCC_RTCCLKCmd(ENABLE);
RTC_InitStruct.RTC_AsynchPrediv = 0x7F; // 127
RTC_InitStruct.RTC_SynchPrediv = 0xFF; // 255
RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
RTC_Init(RTC_InitStruct);
```
### 3.4 备份关键时间戳
在切换时钟源前,将当前 Unix 时间戳写入 BKP 寄存器。
```c
// 假设当前时间已由外部同步,存储到 BKP0R
uint32_t timestamp = GetUnixTime(); // 自定义函数
BKP_WriteBackupRegister(BKP_DR1, timestamp);
// 同时存储 LSI 的当前计数值(若使用 LSI)
BKP_WriteBackupRegister(BKP_DR2, RTC_GetCounter());
```
## 4. 完整代码示例
以下代码演示了 LSE 停振检测与切换逻辑,以及基于 BKP 的补偿机制。
```c
#include "stm32f4xx.h"
#include
// 全局变量
volatile uint32_t last_lsi_count = 0;
volatile uint32_t base_timestamp = 0;
// 初始化 RTC 备份域
void RTC_BackupInit(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
// 使能 LSI
RCC_LSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
// 尝试 LSE
RCC_LSEConfig(RCC_LSE_ON);
uint32_t timeout = 100000;
while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout-- > 0));
// 选择时钟源
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET) {
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
} else {
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
}
RCC_RTCCLKCmd(ENABLE);
RTC_InitTypeDef RTC_InitStruct;
RTC_InitStruct.RTC_AsynchPrediv = 0x7F;
RTC_InitStruct.RTC_SynchPrediv = 0xFF;
RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
RTC_Init(RTC_InitStruct);
// 读取备份时间戳(若有效)
if (BKP_ReadBackupRegister(BKP_DR1) != 0xFFFFFFFF) {
base_timestamp = BKP_ReadBackupRegister(BKP_DR1);
last_lsi_count = BKP_ReadBackupRegister(BKP_DR2);
} else {
base_timestamp = 0; // 首次启动
last_lsi_count = RTC_GetCounter();
BKP_WriteBackupRegister(BKP_DR1, base_timestamp);
BKP_WriteBackupRegister(BKP_DR2, last_lsi_count);
}
}
// 获取当前时间戳(秒)
uint32_t RTC_GetTimestamp(void) {
uint32_t current_count = RTC_GetCounter();
uint32_t elapsed;
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET) {
// LSE 正常,直接返回计数
return base_timestamp + (current_count - last_lsi_count);
} else {
// LSI 模式,需要补偿(假设 LSI 频率为 32000Hz,实际测量后调整)
// 这里简单计算:LSI 计数差值 / 32000 得到秒数,但实际需校准
elapsed = (current_count - last_lsi_count) / 32000;
return base_timestamp + elapsed;
}
}
// 每秒调用一次,更新备份寄存器
void RTC_BackupUpdate(void) {
uint32_t current_count = RTC_GetCounter();
uint32_t current_ts = RTC_GetTimestamp();
// 更新备份寄存器
BKP_WriteBackupRegister(BKP_DR1, current_ts);
BKP_WriteBackupRegister(BKP_DR2, current_count);
}
int main(void) {
// 初始化系统时钟等
RTC_BackupInit();
while (1) {
// 主循环
delay(1000);
RTC_BackupUpdate();
printf("Timestamp: %lu\n", RTC_GetTimestamp());
}
}
```
## 5. 注意事项与优化建议
- **LSI 校准**:LSI 频率随温度漂移,建议在系统启动时测量实际 LSI 频率(通过定时器比较),并存储校准系数到 BKP 寄存器,补偿计算时使用该系数。
- **备份寄存器数量**:STM32F4 有 20 个 32 位备份寄存器,可存储多个时间戳或校准数据,但注意 VBAT 掉电后数据丢失,需定期更新。
- **切换时机**:检测到 LSE 停振后,应尽快切换,避免 RTC 计数中断。可在 RTC 闹钟中断或定时器中断中检测 LSERDY 位。
- **电源管理**:确保 VBAT 引脚连接电池或大电容,否则备份域数据在掉电时丢失。
- **代码健壮性**:在读取 BKP 寄存器前检查写保护状态,避免意外写入。
## 6. 总结
通过合理利用 STM32F4 的备份域寄存器和时钟源切换机制,可以在 LSE 停振后维持可靠的时间保持。本文提供的方案不仅解决了时间丢失问题,还通过备份时间戳和 LSI 补偿提高了精度。实际项目中,可根据需求扩展为多级补偿或外部同步,进一步提升可靠性。