STM32F4 系列 LSE 停振后基于 RTC 备份域寄存器的无痕恢复策略
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列 MCU 中,LSE(低速外部晶振)停振会导致 RTC 时间丢失,进而影响依赖时间戳的应用。本文深入剖析 LSE 失效的检测机制,并给出一种利用 RTC 备份域寄存器保存关键状态、在复位后无痕恢复的完整方案。内容包括硬件原理、寄存器配置、代码实现及注意事项,帮助开发者构建高可靠的嵌入式时间系统。
# STM32F4 系列 LSE 停振后基于 RTC 备份域寄存器的无痕恢复策略
## 1. 问题背景与原理
在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(32.768kHz 外部晶振)提供精准时基。然而,LSE 可能因晶振老化、外部干扰或 PCB 布局不当而停振。一旦 LSE 失效,RTC 将失去时钟源,时间数据无法更新,甚至可能导致系统复位后时间归零。
STM32F4 系列提供了 **RTC 备份域寄存器(BKP)**,这是一块由 VBAT 供电的独立存储区域,即使在主电源断电或系统复位时,只要 VBAT 有电,数据就能保留。利用这一特性,我们可以在 LSE 停振前将关键状态(如时间戳、校准值、标志位)存入备份域,并在复位后读取,实现“无痕恢复”。
## 2. 硬件与软件准备
- 硬件:STM32F4 开发板(如 STM32F407)、外部 32.768kHz 晶振、备用电池(连接 VBAT)。
- 软件:STM32CubeIDE 或 Keil MDK,HAL 库或标准外设库。本文以 HAL 库为例。
## 3. 核心原理:备份域寄存器与 RTC 配置
### 3.1 备份域寄存器(BKP)
备份域包含 20 个 32 位数据寄存器(BKP0R~BKP19R),以及控制寄存器。它们由 VBAT 供电,复位后数据不丢失(除非 VBAT 掉电)。访问前需使能 PWR 和 BKP 时钟,并设置 PWR_CR 的 DBP 位(备份域写保护)。
### 3.2 LSE 停振检测
RTC 提供 **RTC_ISR 寄存器的 LSERDY 标志** 来指示 LSE 是否就绪。当 LSERDY 为 0 时,表示 LSE 停振。我们可以在主循环中周期性检查该标志,一旦发现停振,立即将当前时间戳和状态写入备份域。
### 3.3 恢复流程
1. 系统上电或复位后,先检查备份域中的标志位(如 0xA5A5)是否有效。
2. 若有效,则读取备份的时间戳,并重新配置 RTC(使用 LSI 或 HSE 作为备用时钟源),将时间写入 RTC。
3. 清除标志位,完成恢复。
## 4. 配置步骤
### 4.1 使能备份域访问
```c
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess(); // 设置 DBP 位
```
### 4.2 配置 RTC(正常模式,使用 LSE)
```c
RTC_HandleTypeDef hrtc;
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
HAL_RTC_Init(&hrtc);
// 启用 LSE
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) != SET); // 等待就绪
```
### 4.3 检测 LSE 停振并保存状态
在主循环中,定期检查 LSERDY 标志:
```c
if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
// LSE 停振,保存当前时间到备份域
uint32_t timestamp = HAL_RTC_GetTimestamp(&hrtc); // 自定义函数获取时间戳
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, timestamp);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0xA5A5); // 标志位
// 可切换至 LSI 继续运行,或进入安全模式
}
```
### 4.4 复位后恢复
在系统初始化时,检查备份域标志:
```c
uint32_t flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
if (flag == 0xA5A5) {
uint32_t saved_time = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
// 重新初始化 RTC,使用 LSI 作为时钟源
__HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
__HAL_RCC_LSI_ENABLE();
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) != SET);
// 配置 RTC 使用 LSI,并设置时间
hrtc.Init.SynchPrediv = 0x7A; // 根据 LSI 频率调整
HAL_RTC_Init(&hrtc);
HAL_RTC_SetTime(&hrtc, &saved_time, RTC_FORMAT_BIN);
// 清除标志
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0);
}
```
## 5. 完整代码示例
以下是一个简化的完整示例,包含初始化、检测和恢复逻辑:
```c
#include "stm32f4xx_hal.h"
RTC_HandleTypeDef hrtc;
void RTC_Init(void) {
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
HAL_RTC_Init(&hrtc);
// 尝试启用 LSE
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
uint32_t timeout = 1000;
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) != SET) {
if (--timeout == 0) break;
}
// 检查备份域标志,决定是否恢复
if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2) == 0xA5A5) {
// 恢复时间
RTC_TimeTypeDef sTime;
sTime.Hours = (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) >> 16) & 0xFF;
sTime.Minutes = (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) >> 8) & 0xFF;
sTime.Seconds = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) & 0xFF;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0); // 清除标志
} else {
// 正常初始化时间
RTC_TimeTypeDef sTime = {0};
sTime.Hours = 12;
sTime.Minutes = 0;
sTime.Seconds = 0;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}
}
void Check_LSE_And_Save(void) {
if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
// LSE 停振,保存当前时间
RTC_TimeTypeDef sTime;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
uint32_t packed = (sTime.Hours << 16) | (sTime.Minutes << 8) | sTime.Seconds;
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, packed);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 0xA5A5);
// 可切换至 LSI 继续运行
__HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
__HAL_RCC_LSI_ENABLE();
// 重新配置 RTC 使用 LSI(略)
}
}
int main(void) {
HAL_Init();
RTC_Init();
while (1) {
Check_LSE_And_Save();
HAL_Delay(1000);
}
}
```
## 6. 注意事项
- **备份域写保护**:访问 BKP 寄存器前必须使能 PWR 和 BKP 时钟,并设置 DBP 位,否则写入无效。
- **VBAT 供电**:确保 VBAT 引脚连接电池或大电容,否则备份域数据在断电后丢失。
- **时间戳格式**:示例中简单打包了时分秒,实际应用可扩展为完整日期时间(使用 BKP 多个寄存器)。
- **时钟切换**:LSE 停振后,RTC 时钟源需切换为 LSI,但 LSI 精度较低,可能影响时间准确性,需在恢复后重新校准。
- **标志位唯一性**:使用 0xA5A5 作为有效标志,避免随机数据误判。
- **中断处理**:可配置 LSE 中断(RTC_LSECSS)来立即响应停振,而不是轮询。
## 7. 总结
通过利用 RTC 备份域寄存器,我们可以在 LSE 停振时保存关键时间信息,并在复位后无痕恢复,显著提升系统的可靠性。该方法实现简单,成本低,适用于对时间连续性要求高的嵌入式应用。开发者可根据实际需求扩展,例如保存更多状态或实现多级备份。