STM32F4 嵌入式实战:LSE 停振后基于 HSI 的 RTC 自动校准与时间精度保持
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式系统中,RTC 常依赖 LSE 晶振维持时间基准,但 LSE 易受环境干扰或老化影响而停振,导致时间丢失。本文深入剖析 STM32F4 系列在 LSE 失效后的应对策略,重点讲解如何利用内部 HSI 振荡器作为备用时钟源,并通过软件校准算法补偿 HSI 的频率误差,从而在无外部晶振的情况下维持 RTC 的长期精度。文章涵盖原理分析、配置步骤、完整代码示例及工程注意事项,帮助开发者构建高可靠的嵌入式时钟系统。
# 引言
在工业控制、物联网设备等嵌入式场景中,RTC(实时时钟)是记录事件时间戳的核心外设。STM32F4 系列通常使用 32.768kHz 的 LSE 晶振为 RTC 提供低功耗、高精度的时钟源。然而,LSE 晶振可能因焊接不良、温漂、振动或 EMI 干扰而停振,一旦停振,RTC 将失去时钟基准,系统时间立即失效。
本文提出一种嵌入式解决方案:当检测到 LSE 停振时,自动切换至内部 HSI(高速内部振荡器,16MHz)作为 RTC 时钟源,并通过软件校准算法补偿 HSI 的频率偏差,使 RTC 在无外部晶振情况下仍能保持较高精度(典型误差 < 50ppm)。
# 原理剖析
## 1. RTC 时钟源架构
STM32F4 的 RTC 时钟源可选:
- LSE:外部低速晶振,精度高(±20ppm),但依赖外部硬件。
- LSI:内部低速 RC 振荡器,约 32kHz,精度差(±50%),不适合计时。
- HSE/HSI 分频:通过 RTCSEL[1:0] 位选择,HSI 经 32 分频后得到 500kHz,再经异步预分频器得到 1Hz 计数时钟。
关键点:RTC 时钟源必须在 RTC 使能前选定,切换时钟源需复位 RTC 域。
## 2. HSI 频率误差与校准原理
HSI 出厂精度约 ±1%,温度漂移可达 ±2%。直接使用会导致 RTC 日误差达数百秒。校准思路:
- 利用 STM32F4 内部的 RTC 校准寄存器(RTC_CALR)进行数字校准,可调整 ±0.95 ppm 步进。
- 但 HSI 误差较大,需先通过软件测量 HSI 与已知参考(如系统主时钟 HSE 或外部信号)的偏差,然后计算校准值。
本文采用更实用的方法:利用定时器输入捕获测量 HSI 分频后的 500kHz 信号周期,与理想值比较,得到误差比例,再动态调整 RTC 的异步预分频因子(ASYNCHPREDIV)或使用 RTC 平滑校准寄存器。
## 3. 检测 LSE 停振
STM32F4 的 RTC 提供 LSEDRV 和 LSEMOD 位,但无硬件停振检测。常用方法:
- 周期性读取 RTC 秒值,若在超时时间内未更新,则判定 LSE 失效。
- 或使用 LSE 就绪标志(LSERDY)轮询,若长时间未置位则停振。
# 配置步骤
## 1. 初始化 RTC 使用 LSE
- 使能 PWR 和备份域时钟。
- 解锁备份域写保护。
- 配置 LSE 并等待就绪。
- 选择 LSE 作为 RTC 时钟源。
- 配置预分频器(异步 127,同步 255)得到 1Hz。
## 2. 检测 LSE 停振并切换至 HSI
- 在主循环中监测 RTC 秒更新标志。
- 若超时(如 2 秒)未更新,则执行切换流程。
## 3. HSI 校准流程
- 使能 HSI 并等待稳定。
- 配置 TIM5 输入捕获,测量 HSI/32 信号(即 500kHz)的周期。
- 计算实际频率与理想值的偏差。
- 根据偏差调整 RTC 的同步预分频器(或使用校准寄存器)。
# 完整代码示例
以下代码基于 STM32F407,使用 HAL 库。
```c
// 全局变量
volatile uint8_t rtc_updated = 0;
volatile uint32_t last_rtc_sec = 0;
// 初始化 RTC 使用 LSE
void RTC_Init_LSE(void) {
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
// 超时处理
}
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
__HAL_RCC_RTC_ENABLE();
RTC_HandleTypeDef hrtc = {0};
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 停振
uint8_t LSE_Stopped(void) {
uint32_t current_sec = HAL_RTC_GetTime(&hrtc, RTC_FORMAT_BIN);
if (current_sec == last_rtc_sec) {
return 1; // 未更新,可能停振
}
last_rtc_sec = current_sec;
return 0;
}
// 切换至 HSI 并校准
void RTC_SwitchToHSI_Calibrate(void) {
// 1. 使能 HSI
__HAL_RCC_HSI_ENABLE();
while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET);
// 2. 配置 TIM5 输入捕获测量 HSI/32 频率
// 假设 TIM5_CH1 连接到 MCO1 输出 HSI/32(需配置 GPIO 和 MCO)
// 此处省略具体配置,假设测得实际频率为 freq_hz
uint32_t measured_freq = TIM5_MeasureFrequency(); // 返回 Hz
// 3. 计算误差比例
float error_ratio = (float)measured_freq / 500000.0f; // 理想 500kHz
// 4. 调整同步预分频器(以 1Hz 输出为例)
// 同步预分频器值 = (500000 / error_ratio) - 1,但需取整
uint32_t sync_prediv = (uint32_t)((500000.0f / error_ratio) - 1);
if (sync_prediv > 0x3FFF) sync_prediv = 0x3FFF; // 限制范围
// 5. 重新配置 RTC 时钟源为 HSI
__HAL_RCC_RTC_DISABLE();
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV32);
__HAL_RCC_RTC_ENABLE();
// 6. 设置新的预分频值
hrtc.Init.AsynchPrediv = 127; // 保持不变
hrtc.Init.SynchPrediv = sync_prediv;
HAL_RTC_Init(&hrtc);
// 7. 重新设置时间(可选,从上次保存的时间恢复)
// HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}
// 主循环
int main(void) {
HAL_Init();
RTC_Init_LSE();
while (1) {
if (LSE_Stopped()) {
RTC_SwitchToHSI_Calibrate();
// 记录错误日志,并继续运行
}
HAL_Delay(1000);
}
}
```
# 注意事项
- **切换时机**:必须在 RTC 使能前切换时钟源,否则需复位 RTC 域,导致时间丢失。建议在检测到停振后立即保存当前时间到备份寄存器,再执行切换。
- **校准精度**:HSI 受温度影响较大,建议在系统空闲时周期性重新校准(如每 10 分钟)。
- **测量频率**:使用 TIM 输入捕获时,需确保参考时钟(如 HSE)稳定,否则测量误差会传递到校准结果。
- **功耗考量**:HSI 功耗高于 LSE,若系统对功耗敏感,可考虑在 LSE 恢复后自动切回。
- **备份域写保护**:操作 RTC 和备份寄存器前务必解锁备份域,否则写操作无效。
- **中断处理**:建议使用 RTC 闹钟或唤醒定时器定期检测秒更新,避免主循环阻塞。
# 总结
通过上述方案,STM32F4 在 LSE 停振后仍能依靠 HSI 维持 RTC 运行,并通过软件校准将精度控制在可接受范围。该方法无需额外硬件,适用于对成本敏感的嵌入式产品。开发者可根据实际需求调整校准周期和预分频计算,实现更优的时间保持能力。