STM32F4 嵌入式设计:LSE 停振后利用 HSI 校准 RTC 并保持时间精度的实战指南
👁 3 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 嵌入式系统中,LSE(低速外部晶振)常为 RTC 提供精准时钟源,但受环境或硬件影响可能停振,导致时间丢失。本文深入剖析 LSE 失效机制,提出一种基于 HSI(高速内部振荡器)的软件校准方案,通过自动检测、频率补偿和 RTC 重同步,在无外部晶振时维持秒级精度。文章涵盖原理分析、寄存器配置、完整代码示例及工程注意事项,助力开发者构建高可靠的时钟系统。
# 引言
在工业控制、物联网设备等嵌入式应用中,RTC(实时时钟)是时间戳和定时任务的核心。STM32F4 系列默认使用 LSE(32.768 kHz 外部晶振)驱动 RTC,其精度可达 ±20 ppm。然而,LSE 可能因晶振老化、焊接缺陷或强电磁干扰而停振。一旦 LSE 失效,RTC 将停止计时,系统时间归零,造成数据错乱。本文提出一种嵌入式软件容错方案:当检测到 LSE 停振时,自动切换至 HSI(16 MHz 内部 RC 振荡器),并通过软件校准补偿 HSI 的频率误差,使 RTC 在无外部晶振下仍能保持较高精度。
# 原理分析
## 1. STM32F4 时钟树与 RTC 时钟源
STM32F4 的 RTC 时钟源可选:LSE、LSI(约 32 kHz 内部低速时钟)或 HSE/128。LSE 精度最高,但依赖外部硬件;LSI 精度差(±50%),不适合长时间计时;HSE 精度高但通常被系统主时钟占用。HSI 虽为高速时钟,但可通过分频得到 32.768 kHz(16 MHz / 488.28125 非整数,需特殊处理)。
## 2. LSE 停振检测机制
STM32F4 的 RTC 模块提供时钟安全系统(CSS)功能,可监测 LSE 信号。当 LSE 振幅低于阈值时,CSS 产生中断,置位 RTC_ISR 中的 LSECSSF 标志。软件可读取该标志或使能中断来感知停振。
## 3. HSI 校准原理
HSI 出厂精度约 ±1%,在 25°C 下典型误差 ±0.5%。通过软件校准,可将其误差降至 ±0.1% 以内。校准方法:利用 RTC 的同步预分频器(PREDIV_S)和异步预分频器(PREDIV_A),将 HSI 分频至 1 Hz。由于 HSI 频率不精确,实际秒脉冲会偏移,通过测量偏移量并调整预分频值,可实现频率补偿。
# 硬件与软件准备
- 硬件:STM32F407 开发板(或其他 F4 系列)
- 软件:STM32CubeIDE 或 Keil MDK,HAL 库
- 关键外设:RTC、EXTI、NVIC、系统时钟
# 配置步骤
## 1. 初始化系统时钟与 RTC
首先,配置系统时钟为 HSI(或 PLL),并启用 RTC 时钟。以下代码初始化 RTC 使用 LSE,并开启 CSS 中断。
```c
void RTC_Init(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
__HAL_RCC_RTC_ENABLE();
RTC_HandleTypeDef hrtc;
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127; // 异步分频
hrtc.Init.SynchPrediv = 255; // 同步分频,得到 1 Hz
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 CSS 中断
HAL_RTCEx_EnableLSECSS(&hrtc);
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}
```
注意:`HAL_RTC_Init` 内部会尝试启动 LSE,若 LSE 未就绪,则返回错误。我们需在初始化时检测 LSE 状态,若失败则直接切换到 HSI 模式。
## 2. LSE 停振中断处理
当 LSE 停振时,CSS 中断触发。在中断回调中,我们关闭 LSE,切换 RTC 时钟源至 HSI,并启动校准流程。
```c
void HAL_RTCEx_LSECSSEventCallback(RTC_HandleTypeDef *hrtc) {
// 关闭 LSE 和 CSS
__HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
HAL_RTCEx_DisableLSECSS(hrtc);
// 切换 RTC 时钟源到 HSI(通过 RCC_BDCR 寄存器)
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV8); // 16MHz/8=2MHz,再分频得到 32.768kHz?实际需调整
// 注意:HSI 分频后需配合预分频器,见下文校准
// 重新初始化 RTC 预分频器
hrtc->Instance->PRER = (127 << 16) | 255; // 默认值,后续校准会调整
// 设置校准标志
lse_failure = 1;
}
```
注意:`RCC_RTCCLKSOURCE_HSI_DIV8` 在 F4 系列中并不存在,实际需使用 `RCC_RTCCLKSOURCE_HSI_DIV2` 或直接使用 HSI 经分频。查阅参考手册,RTC 时钟源可选 HSI/2(8 MHz)或 HSI/8(2 MHz)。我们选择 HSI/8 得到 2 MHz,然后通过预分频器分频至 1 Hz。
## 3. HSI 校准算法
HSI 频率误差导致秒脉冲偏移。校准思路:利用一个已知精度的参考时钟(如系统主时钟或外部信号)测量 RTC 秒脉冲的实际周期,然后调整同步预分频值。
具体实现:
- 使用定时器(如 TIM2)在 1 秒内计数,测量 RTC 秒中断的间隔。
- 假设理想周期为 32768 个 HSI/8 时钟(即 2 MHz / 32768 = 61.035 Hz,非整数,实际需用小数分频)。
- 由于 HSI 频率偏差,实际计数会偏离理论值。通过多次测量取平均,计算偏差比例,然后调整 PREDIV_S 值。
```c
#define HSI_FREQ 16000000
#define HSI_DIV 8
#define RTC_CLK (HSI_FREQ / HSI_DIV) // 2 MHz
#define TARGET_HZ 1
uint32_t measure_rtc_interval(void) {
// 配置 TIM2 为外部时钟模式,计数 RTC 秒脉冲?或使用内部时钟捕获
// 简化:使用 SysTick 或 DWT 计数器
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
DWT->CYCCNT = 0;
// 等待 RTC 秒中断标志
while (!(RTC->ISR & RTC_ISR_SECF));
RTC->ISR &= ~RTC_ISR_SECF;
uint32_t start = DWT->CYCCNT;
while (!(RTC->ISR & RTC_ISR_SECF));
RTC->ISR &= ~RTC_ISR_SECF;
uint32_t end = DWT->CYCCNT;
return (end - start) / (SystemCoreClock / 1000000); // 微秒
}
void calibrate_hsi(void) {
uint32_t measured_us = measure_rtc_interval();
uint32_t expected_us = 1000000; // 1 秒
int32_t error_ppm = ((int32_t)measured_us - expected_us) * 1000000 / expected_us;
// 调整 PREDIV_S:假设当前 PREDIV_S = 255,实际频率 = RTC_CLK / (PREDIV_A+1) / (PREDIV_S+1)
// 若误差为正,说明 RTC 慢了,需减小 PREDIV_S;反之增大
uint32_t new_prediv_s = 255 - (error_ppm * 255 / 1000000);
// 限制范围
if (new_prediv_s > 511) new_prediv_s = 511;
if (new_prediv_s < 0) new_prediv_s = 0;
// 写入预分频器(需在 RTC 写保护关闭后)
HAL_RTC_InitTypeDef init;
// 重新初始化 RTC 预分频
RTC->PRER = (127 << 16) | new_prediv_s;
}
```
注意:上述代码仅为示意,实际需处理 RTC 写保护(`HAL_RTC_Unlock`)和同步等待。
## 4. 完整代码示例
以下是一个集成上述功能的简化示例,包含初始化、中断处理和校准调用。
```c
volatile uint8_t lse_failure = 0;
void SystemClock_Config(void);
void RTC_Init(void);
void RTC_Calibration_Handler(void);
int main(void) {
HAL_Init();
SystemClock_Config();
RTC_Init();
while (1) {
if (lse_failure) {
RTC_Calibration_Handler();
lse_failure = 0;
}
// 其他任务
}
}
void RTC_Calibration_Handler(void) {
// 进行多次测量校准
for (int i = 0; i < 10; i++) {
calibrate_hsi();
HAL_Delay(100); // 等待稳定
}
}
void HAL_RTCEx_LSECSSEventCallback(RTC_HandleTypeDef *hrtc) {
// 切换时钟源
__HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV8);
// 重新初始化 RTC 预分频(默认值)
hrtc->Instance->PRER = (127 << 16) | 255;
lse_failure = 1;
}
```
# 注意事项
- **RTC 写保护**:修改 RTC 寄存器前必须解锁(`HAL_RTC_Unlock`),修改后重新锁定。
- **时钟切换时机**:切换 RTC 时钟源时,应确保 RTC 处于初始化模式(`HAL_RTC_DeInit` 或设置 INIT 位),否则可能产生毛刺。
- **校准精度**:HSI 受温度影响较大,建议在系统运行时周期性校准,或使用温度传感器补偿。
- **备份域电源**:LSE 和 RTC 位于备份域,切换时钟源时注意 VBAT 供电,避免数据丢失。
- **中断优先级**:CSS 中断应设为高优先级,确保及时响应。
- **测试验证**:在实际硬件上模拟 LSE 停振(如断开晶振),观察 RTC 是否继续走时,并测量精度。
# 总结
本文详细介绍了 STM32F4 在 LSE 停振后利用 HSI 校准 RTC 的嵌入式方案。通过 CSS 中断检测、时钟源切换和软件频率补偿,系统能够在失去外部晶振时维持时间精度,显著提高了可靠性。开发者可根据实际需求调整校准算法,如使用更精确的参考源或增加温度补偿。该方案适用于对成本敏感且环境恶劣的嵌入式应用。