STM32F4 无外部晶振秒中断补偿:LSE 停振后 RTC 备份寄存器的妙用
👁 3 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列中,LSE(低速外部晶振)常被用于 RTC 以提供精准的秒中断,但在某些低成本或高可靠性场景下,LSE 可能停振或未焊接。此时,如何利用内部 LSI 和 RTC 备份寄存器实现无外部晶振的秒中断补偿?本文将深入剖析原理,给出配置步骤与完整代码示例,并分享关键注意事项,助你轻松应对这一嵌入式开发难题。
# 引言
在嵌入式系统中,RTC(实时时钟)的秒中断常用于定时任务、日志时间戳或低功耗唤醒。STM32F4 系列默认推荐使用 LSE(32.768kHz 外部晶振)驱动 RTC,以获得高精度。但在实际项目中,LSE 可能因成本、空间或可靠性原因被省略,或运行中因振动、温度而停振。此时,若改用内部 LSI(约 32kHz,精度较差),秒中断会累积误差。本文将介绍一种巧妙方案:利用 RTC 备份寄存器记录补偿值,在 LSE 停振后,通过软件校准实现无外部晶振的秒中断补偿。
# 原理剖析
## 1. LSE 与 LSI 的差异
- LSE:外部 32.768kHz 晶振,精度可达 ±20ppm,温度稳定性好,是 RTC 的理想时钟源。
- LSI:内部 RC 振荡器,频率约 32kHz(实际范围 17~47kHz),精度差(±5% 左右),受温度影响大。
当 LSE 停振时,RTC 可切换到 LSI,但秒中断周期会偏离 1 秒。例如,若 LSI 实际为 31.5kHz,则 RTC 每秒计数 31500 次,导致实际秒周期为 31500/32768 ≈ 0.961 秒,即每秒快约 39ms。
## 2. RTC 备份寄存器的作用
RTC 备份寄存器(BKP Registers)是 RTC 域内的 20 个 32 位寄存器(STM32F4 系列),可在 VDD 掉电时由 VBAT 供电保持数据。它们常用来存储系统状态、校准参数等。在本方案中,我们利用备份寄存器保存两个关键值:
- 补偿系数:每秒需要调整的计数偏移量。
- 累计误差:用于动态修正的累积值。
## 3. 补偿算法设计
核心思想:在秒中断服务函数中,根据实际 LSI 频率与理想频率的偏差,动态调整 RTC 的预分频值或通过软件计数修正。由于 RTC 的异步预分频器(ASYNC_PREDIV)和同步预分频器(SYNC_PREDIV)可配置,但修改它们需要重新初始化 RTC,不适合频繁操作。因此,我们采用软件补偿:
- 设定 RTC 使用 LSI 作为时钟源,并配置一个固定的预分频值(例如,假设 LSI 为 32kHz,设置同步预分频为 32767,得到 1Hz 中断)。
- 在秒中断中,读取一个高精度定时器(如 TIM2)的计数值,测量实际秒周期。
- 计算误差,并累加到备份寄存器中的累计误差变量。当累计误差超过一定阈值(如 1ms),则调整下一次中断的唤醒时间(通过修改 RTC 的唤醒定时器或利用软件延时)。
但更简洁的方法是:利用 RTC 的日历功能,在秒中断中直接修改 RTC 的秒值(通过写 RTC_TR 寄存器),但需谨慎,因为写操作可能引发冲突。本方案采用“计数补偿”法:在秒中断中,若累计误差为正(RTC 快),则跳过下一次秒中断(即软件计数减 1);若为负,则额外触发一次中断(通过设置事件标志)。
# 配置步骤
## 1. 硬件准备
- STM32F4 开发板(如 STM32F407)
- 无 LSE 晶振(或断开 LSE)
- 调试器(ST-Link/J-Link)
## 2. 软件环境
- STM32CubeMX 或 HAL 库
- Keil MDK 或 IAR
## 3. 初始化流程
### 步骤 1:配置 RTC 时钟源为 LSI
在 CubeMX 中,将 RTC 时钟源选择为 LSI,并启用 RTC 日历和秒中断。
### 步骤 2:配置备份寄存器
使能 PWR 和 RTC 备份访问,然后写入初始补偿参数。
### 步骤 3:编写秒中断处理函数
在中断中实现补偿逻辑。
# 完整代码示例
以下基于 STM32F4 HAL 库,演示核心代码。
```c
/* 备份寄存器地址定义 */
#define BKP_COMPENSATION RTC->BKP0R /* 补偿系数 */
#define BKP_ERROR_ACC RTC->BKP1R /* 累计误差 */
/* 初始化 RTC 为 LSI 时钟,1Hz 中断 */
void RTC_Init(void)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
__HAL_RTC_PWR_CLK_ENABLE();
__HAL_RTC_RTC_ENABLE();
/* 允许访问备份域 */
HAL_PWR_EnableBkUpAccess();
/* 选择 LSI 作为 RTC 时钟 */
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
__HAL_RCC_RTC_ENABLE();
/* 初始化 RTC */
RTC_HandleTypeDef hrtc;
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24HOUR;
hrtc.Init.AsynchPrediv = 0x7F; /* 异步预分频 127 */
hrtc.Init.SynchPrediv = 0xFF; /* 同步预分频 255,配合 LSI 约 32kHz,得到 1Hz */
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
HAL_RTC_Init(&hrtc);
/* 设置日期时间 */
sTime.Hours = 0;
sTime.Minutes = 0;
sTime.Seconds = 0;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
sDate.Year = 24;
sDate.Month = 1;
sDate.Date = 1;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
/* 使能秒中断 */
HAL_RTCEx_SetSecond_IT(&hrtc);
/* 初始化备份寄存器 */
BKP_COMPENSATION = 0; /* 补偿系数,0 表示无补偿 */
BKP_ERROR_ACC = 0; /* 累计误差,单位 ms */
/* 启动定时器用于测量实际秒周期(如 TIM2) */
TIM2_Init();
}
/* 秒中断回调 */
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc)
{
static uint32_t last_tick = 0;
uint32_t current_tick = TIM2->CNT; /* 读取定时器计数值 */
int32_t delta = (int32_t)(current_tick - last_tick);
last_tick = current_tick;
/* 假设 TIM2 计数频率为 1MHz,则 delta 为实际秒周期(us) */
int32_t error = delta - 1000000; /* 与理想 1 秒的误差,单位 us */
/* 累计误差,单位 ms */
int32_t acc = (int32_t)BKP_ERROR_ACC + error / 1000;
BKP_ERROR_ACC = acc;
/* 根据累计误差进行补偿 */
if (acc >= 1) {
/* RTC 快了,跳过下一次秒中断 */
BKP_ERROR_ACC = acc - 1;
/* 设置标志,在下一个中断中跳过处理 */
skip_next = 1;
} else if (acc <= -1) {
/* RTC 慢了,额外触发一次中断(通过软件设置事件) */
BKP_ERROR_ACC = acc + 1;
/* 手动调用一次处理函数 */
RTC_CompensationHandler();
}
if (skip_next) {
skip_next = 0;
return; /* 跳过本次处理 */
}
/* 正常秒中断处理 */
RTC_CompensationHandler();
}
/* 实际任务处理函数 */
void RTC_CompensationHandler(void)
{
/* 例如:翻转 LED,记录时间戳等 */
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
```
# 注意事项
- **备份寄存器访问**:必须先使能 PWR 时钟并调用 `HAL_PWR_EnableBkUpAccess()`,否则写操作无效。
- **LSI 频率校准**:首次上电时,建议通过定时器测量 LSI 实际频率,并计算初始补偿系数,写入备份寄存器。
- **中断优先级**:秒中断应设置为较高优先级,避免丢失补偿时机。
- **误差累积**:补偿算法中,误差阈值设为 1ms,可根据需求调整。若系统对时间精度要求高,可改用更高分辨率的定时器(如 TIM2 的 1MHz 计数)。
- **低功耗模式**:在低功耗模式下,备份寄存器仍保持数据,但 TIM2 可能停止,需在唤醒后重新校准。
- **备份寄存器数量**:STM32F4 有 20 个备份寄存器,但部分可能被其他功能占用,请合理规划。
# 总结
通过 RTC 备份寄存器保存补偿参数,结合定时器测量实际秒周期,可以在 LSE 停振或缺失的情况下,利用 LSI 实现可接受的秒中断精度。该方法无需额外硬件,仅依赖软件算法,适用于对成本敏感或高可靠性的嵌入式项目。实际测试中,经过补偿后,日误差可控制在 1 秒以内(取决于 LSI 稳定性和校准频率)。希望本文能为你提供实用的解决方案。