STM32F4 LSE 停振后秒级精度保持:硬件 RTC 校准补偿实战指南
👁 3 阅读 · 2026-08-27 · 嵌入式
在嵌入式系统中,LSE(低速外部晶振)停振是导致 RTC 时间漂移的常见故障,尤其在工业或电池供电场景。本文深入剖析 STM32F4 系列 RTC 的硬件校准机制,包括同步移位和异步预分频补偿原理,并给出完整的配置步骤与代码示例,帮助开发者实现停振后秒级精度保持,提升系统鲁棒性。
# 引言
在嵌入式系统中,实时时钟(RTC)依赖 LSE(32.768 kHz 晶振)维持时间基准。然而,晶振老化、温度变化或 EMI 干扰可能导致 LSE 停振,此时 RTC 若继续使用内部 RC 振荡器(LSI),其精度通常只有 ±1% 左右,秒级误差会迅速累积。STM32F4 系列提供了硬件 RTC 校准功能,通过调整同步移位和异步预分频值,即使 LSE 停振后切换到 LSI,也能将秒级误差控制在可接受范围。本文将从原理到实践,详细讲解如何实现这一目标。
# 原理剖析
## 1. RTC 时钟架构
STM32F4 的 RTC 核心由两个分频器组成:
- **异步预分频器**:默认值为 127(7 位),产生 `ck_apre`(通常 256 Hz)。
- **同步预分频器**:默认值为 255(8 位),产生 1 Hz 的 `ck_spre`。
当 LSE 正常时,`RTC_CLK = 32768 Hz`,分频后得到精确的 1 Hz。若 LSE 停振,可切换到 LSI(典型 32 kHz,实际范围 17~47 kHz),此时分频比固定,输出频率偏差极大。
## 2. 校准原理
RTC 校准通过两个寄存器实现:
- **RTC_CALR**:包含 `CALM`(校准减)和 `CALP`(校准加)字段。
- `CALM`:在每 2^20 个 `ck_apre` 周期内,减少若干脉冲,用于补偿频率偏高。
- `CALP`:每 2^20 个周期增加 1 个脉冲,用于补偿频率偏低。
- **RTC_SHIFTR**:用于亚秒级同步,可提前或延迟 RTC 时钟,但校准主要依赖 CALR。
校准公式:
`实际频率 = RTC_CLK / ( (ASYNC_PRESC+1) * (SYNC_PRESC+1) ) * (1 - CALM/2^20 + CALP/2^20)`
通过调整 `CALM` 和 `CALP`,可以在 ±0.477 ppm 的精度内微调频率,对应秒级误差约 ±0.04 秒/天。
## 3. LSE 停振检测与切换
STM32F4 的 RTC 支持时钟源自动切换(通过 RCC_BDCR 的 RTCSEL 位),但默认不会自动检测 LSE 故障。需要借助 LSE 监视器(LSEM)或外部中断检测,然后软件切换时钟源。本文聚焦于切换后的校准补偿。
# 配置步骤
## 1. 初始化 RTC 并启用校准
首先,确保 RTC 已配置为 LSE 或 LSI 时钟源,并启用校准功能。以下代码基于 STM32F4xx HAL 库。
```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();
// 启用 LSI 作为备用时钟(若 LSE 停振)
RCC_LSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
// 选择 LSI 作为 RTC 时钟(实际应用中可先尝试 LSE,失败后切换)
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
__HAL_RCC_RTC_ENABLE();
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;
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
Error_Handler();
}
}
```
## 2. 计算校准值
实际测量 LSI 频率(例如通过定时器捕获),假设测得 `f_lsi = 32000 Hz`,而目标频率为 32768 Hz。则频率误差为:
`err = (32000 - 32768) / 32768 = -0.0234`(即偏低 2.34%)
由于 LSI 频率偏低,需要增加频率,因此应使用 `CALP` 加脉冲。但 `CALP` 只能每 2^20 周期加 1 个脉冲,补偿范围有限。对于较大偏差,需结合 `CALM` 和 `CALP`。
更实用的方法是:先调整分频值,使输出接近 1 Hz,再用校准微调。例如,若 LSI 为 32000 Hz,可将异步分频设为 124(125 分频),同步分频设为 255(256 分频),得到 `32000/(125*256) = 1.0 Hz` 恰好。但实际频率可能不是整数,因此需要校准。
假设异步分频为 124,同步为 255,实际 `f_rtc = 32000`,则输出频率为 1.0 Hz,误差为 0。若实际为 32010 Hz,则输出为 `32010/(125*256) = 1.0003125 Hz`,偏高,需用 `CALM` 减少脉冲。
计算 `CALM`:
`CALM = (f_rtc / ( (ASYNC+1)*(SYNC+1) ) - 1) * 2^20`
本例中:`CALM = (32010/32000 - 1) * 1048576 ≈ 327.68`,取整为 328。
## 3. 写入校准寄存器
```c
void RTC_Calibrate(uint32_t calm, uint32_t calp) {
// 进入初始化模式
HAL_RTC_EnterInitMode(&hrtc);
// 设置 CALM 和 CALP
MODIFY_REG(RTC->CALR, RTC_CALR_CALM, calm);
MODIFY_REG(RTC->CALR, RTC_CALR_CALP, calp << 15); // CALP 是位15
// 退出初始化模式
HAL_RTC_ExitInitMode(&hrtc);
}
```
注意:`CALM` 是 9 位(0-511),`CALP` 是 1 位。写入前需确保 RTC 未处于配置状态。
## 4. 动态调整策略
由于 LSI 频率随温度变化,建议周期性(如每分钟)测量实际频率并更新校准值。可使用定时器输入捕获或比较器测量 LSI 与 LSE(若可用)的比值。
```c
void RTC_AutoCalibrate(void) {
// 假设通过硬件测量得到 f_lsi(例如 32010 Hz)
uint32_t f_lsi = Measure_LSI_Frequency();
uint32_t target = 32768;
float ratio = (float)f_lsi / target;
// 调整分频值(可选)
uint32_t async = 127;
uint32_t sync = 255;
float out_freq = (float)f_lsi / ((async+1)*(sync+1));
float error = out_freq - 1.0f;
uint32_t calm = 0;
uint32_t calp = 0;
if (error > 0) {
calm = (uint32_t)(error * 1048576.0f);
if (calm > 511) calm = 511;
} else {
calp = 1;
}
RTC_Calibrate(calm, calp);
}
```
# 完整代码示例
以下是一个完整的初始化与校准流程,包含 LSE 检测和切换逻辑。
```c
void SystemClock_Config(void);
void RTC_Init_With_Fallback(void);
int main(void) {
HAL_Init();
SystemClock_Config();
// 初始化 RTC,优先 LSE,失败则 LSI
RTC_Init_With_Fallback();
// 每 60 秒自动校准一次
while (1) {
HAL_Delay(60000);
RTC_AutoCalibrate();
}
}
void RTC_Init_With_Fallback(void) {
// 尝试启用 LSE
RCC_LSEConfig(RCC_LSE_ON);
uint32_t timeout = 1000;
while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout-- > 0)) {
// 等待 LSE 就绪
}
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == SET) {
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
} else {
// LSE 停振,切换到 LSI
RCC_LSEConfig(RCC_LSE_OFF);
RCC_LSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
}
__HAL_RCC_RTC_ENABLE();
// 后续初始化同上
}
```
# 注意事项
- **校准范围**:`CALM` 最大 511,对应补偿约 487 ppm;`CALP` 仅补偿 0.954 ppm。对于 LSI 的大偏差,可能需要调整分频值,否则校准不够。
- **写入时机**:修改 `CALR` 必须在 RTC 初始化模式下,否则无效。
- **温度漂移**:LSI 频率随温度变化明显,建议定期重新校准,或使用温度传感器进行查表补偿。
- **功耗考虑**:LSI 功耗低于 LSE,但精度差,校准可弥补,但需权衡。
- **硬件设计**:若 LSE 停振频繁,考虑增加晶振负载电容或使用有源晶振。
# 总结
通过 STM32F4 的硬件 RTC 校准功能,即使在 LSE 停振后切换到 LSI,也能通过动态调整 `CALM`/`CALP` 实现秒级精度保持。本文提供了原理、配置步骤和代码示例,开发者可根据实际场景集成到产品中。关键在于定期测量实际频率并更新校准值,以应对环境变化。