# STM32F4 LSE 停振自愈:RTC 校准寄存器实现秒级自愈的实战指南 ## 一、问题背景与自愈思路 LSE(32.768kHz 低速外部晶振)为 RTC 提供高精度时钟源,但受温漂、振动或焊接缺陷影响可能停振。一旦停振,RTC 时间停止更新,若系统依赖 RTC 做定时或时间戳,将引发逻辑错误。传统方案是检测到故障后重启 RTC,但会丢失当前时间,且重启耗时不可控。 本文采用 **“CSS 检测 + LSI 备用 + 校准补偿”** 策略: - 利用 RTC 的时钟安全系统(CSS)在 LSE 失效时自动切换至 LSI(约 32kHz 内部 RC),保证 RTC 继续运行。 - 通过 RTC 校准寄存器(RTC_CALR)对 LSI 的频率偏差进行数字补偿,使 RTC 在备用时钟下仍能保持较高精度。 - 同时,软件轮询检测 CSS 标志,一旦 LSE 恢复(如重新焊接或环境改善),可平滑切回 LSE 并重新校准。 此方案无需外部硬件,仅靠 STM32F4 内部资源即可实现秒级自愈,适用于对时间连续性要求高的嵌入式系统。 ## 二、原理剖析:LSE 停振检测与 RTC 校准机制 ### 1. LSE 停振检测:CSS 与 RTC_ISR STM32F4 的 RTC 模块内置 CSS(Clock Security System),专门监测 LSE。当 LSE 振幅低于阈值时,硬件自动执行以下操作: - 将 RTC 时钟源切换至 LSI(若已配置为备用)。 - 置位 RTC_ISR 的 `LSECSSF` 标志,并产生中断(若使能)。 - 同时,LSECSSE 位被硬件清零,需软件重新使能。 注意:CSS 仅适用于 LSE,且必须在 RTC 初始化时使能。检测到故障后,RTC 继续以 LSI 运行,但时间精度下降(LSI 误差约 ±5%)。 ### 2. RTC 校准寄存器:RTC_CALR 的平滑补偿 RTC 校准通过调整 RTC 时钟的计数频率实现。STM32F4 的 RTC_CALR 包含: - `CALP`(1 位):当置 1 时,每 2^20 个时钟周期增加 1 个时钟周期(相当于 +0.954 ppm)。 - `CALW8`(1 位):选择 8 秒校准窗口(默认 32 秒)。 - `CALM`(9 位):在窗口内减少的时钟周期数,范围 0~511,每个单位约 -0.954 ppm(32 秒窗口)。 实际校准值 = (CALP 贡献) - (CALM 贡献)。通过组合,可补偿 LSI 的偏差。例如,若 LSI 实测频率为 32.1kHz(偏差 +3125 ppm),则需设置 CALM 约为 3277(超出 9 位范围),此时可结合 CALP 或使用 8 秒窗口(单位变为 -3.815 ppm)。因此,校准能力有限,适合 LSI 偏差在 ±500 ppm 内的场景,而 LSI 典型偏差在 ±1% 内,故需先测量实际频率再计算。 ### 3. 秒级自愈的流程 - 正常模式:RTC 使用 LSE,时间精确。 - 故障发生:CSS 触发,RTC 切至 LSI,置位标志。 - 软件响应:在中断或轮询中读取标志,记录切换时刻,并启动 LSI 频率测量(利用 TIM 或 RTC 自身)。 - 校准:根据测量结果设置 RTC_CALR,使 RTC 在 LSI 下继续走时,误差控制在可接受范围。 - 恢复:若 LSE 重新起振(可通过重新使能 CSS 并检测标志),则切回 LSE,并清除校准。 ## 三、配置步骤(基于 HAL 库) ### 1. 初始化 RTC 与 CSS ```c RTC_HandleTypeDef hrtc; void RTC_Init(void) { __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_RTC_ENABLE(); HAL_PWR_EnableBkUpAccess(); hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; // 异步分频,产生 1Hz hrtc.Init.SynchPrediv = 255; hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; // 关键:使能 LSE 时钟安全系统 hrtc.Init.ClockSecuritySystem = RTC_CLOCKSECURITY_ENABLE; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } // 使能 CSS 中断(可选) HAL_RTC_ActivateCSS(&hrtc); HAL_NVIC_SetPriority(RTC_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_IRQn); } ``` 注意:`HAL_RTC_Init` 内部会配置时钟源,需在 `HAL_RCC_GetHSEValue` 等之前设置好。CSS 使能后,若 LSE 停振,硬件自动切换至 LSI(前提是 RCC 中已配置 LSI 作为 RTC 备用时钟)。 ### 2. 配置 RCC 时钟源(LSE 为主,LSI 为备用) 在 `HAL_RCC_OscConfig` 中: ```c RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE | RCC_OSCILLATORTYPE_LSI; RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 启用 LSE RCC_OscInitStruct.LSIState = RCC_LSI_ON; // 启用 LSI 作为备用 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // 选择 LSE 作为 RTC 时钟源 __HAL_RCC_RTC_CLKPRESCALER(RCC_RTCCLKSOURCE_LSE); ``` 当 CSS 触发时,硬件自动将 RTC 时钟源切换为 LSI,无需软件干预。但需确保 LSI 已启用。 ### 3. 检测 CSS 标志与中断处理 在中断回调中处理: ```c void RTC_IRQHandler(void) { HAL_RTC_IRQHandler(&hrtc); } void HAL_RTC_CSSCallback(RTC_HandleTypeDef *hrtc) { // LSE 停振,RTC 已切至 LSI // 记录切换时间点(从 RTC 读取当前时间) RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate; HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN); // 保存到全局变量,用于后续补偿 // 重新使能 CSS(硬件自动清零) HAL_RTC_ActivateCSS(hrtc); // 启动 LSI 频率测量(见下文) Start_LSI_Measurement(); } ``` 注意:CSS 中断标志 `LSECSSF` 在 `HAL_RTC_IRQHandler` 中被清除,但 `LSECSSE` 位需重新置位。 ### 4. 测量 LSI 频率并计算校准值 利用 TIM 或 RTC 自身测量 LSI。简单方法:使用 TIM5 的通道 1 捕获 LSI 输出(需将 LSI 映射到 MCO 或 TIM 输入)。但更常用的是利用 RTC 的同步分频:在 LSI 下,RTC 的异步分频器可产生一个参考信号,通过比较 1 秒内计数来估算。 这里给出基于 TIM 的测量示例(假设 LSI 已连接到 TIM5_CH1): ```c void Start_LSI_Measurement(void) { // 配置 TIM5 输入捕获,测量 LSI 频率 TIM_IC_InitTypeDef sConfigIC = {0}; htim5.Instance = TIM5; htim5.Init.Prescaler = 0; htim5.Init.CounterMode = TIM_COUNTERMODE_UP; htim5.Init.Period = 0xFFFFFFFF; HAL_TIM_IC_Init(&htim5); sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1); } // 在捕获中断中计算频率(两次捕获间隔) void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static uint32_t last_cap = 0; uint32_t cap = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t diff = cap - last_cap; last_cap = cap; if (diff > 0) { uint32_t freq = 84000000 / diff; // 假设定时器时钟 84MHz // 计算校准值:目标 32768Hz,偏差 ppm = (freq - 32768)*1e6/32768 int32_t ppm = ((int32_t)freq - 32768) * 1000000 / 32768; // 设置 RTC_CALR Set_RTC_Calibration(ppm); } } ``` ### 5. 设置 RTC 校准寄存器 根据 ppm 值计算 CALM 和 CALP。公式: - 若 ppm > 0(频率偏高),需减少时钟周期,设置 CALM = ppm / 0.954(32 秒窗口)或 ppm / 3.815(8 秒窗口)。 - 若 ppm < 0,可设置 CALP 增加周期,但 CALP 只有 1 位,通常用于微调。 示例代码: ```c void Set_RTC_Calibration(int32_t ppm) { uint32_t calm = 0; uint32_t calp = 0; if (ppm > 0) { // 使用 8 秒窗口,单位 3.815 ppm calm = (uint32_t)(ppm / 3.815); if (calm > 511) calm = 511; // 设置 CALW8=1,CALM=calm WRITE_REG(RTC->CALR, (RTC_CALR_CALW8 | (calm << RTC_CALR_CALM_Pos))); } else if (ppm < 0) { // 负偏差,使用 CALP 增加周期,但 CALP 只能增加 0.954 ppm,若偏差大需结合 CALM // 此处简化,仅演示 calp = 1; WRITE_REG(RTC->CALR, (RTC_CALR_CALP | (0 << RTC_CALR_CALM_Pos))); } else { WRITE_REG(RTC->CALR, 0); } } ``` 注意:校准寄存器写入后,需等待 RTC 同步(检查 `RTC_ISR_RECALPF` 标志)。 ### 6. 恢复 LSE 的检测与切换 若 LSE 恢复(如重新起振),可通过重新使能 CSS 并检查标志来感知。但硬件不会自动切回,需软件操作: ```c void Check_LSE_Recovery(void) { // 重新使能 CSS(若之前被清零) HAL_RTC_ActivateCSS(&hrtc); // 等待一段时间,若 LSE 正常,则 CSS 不会触发;若仍停振,会再次触发中断 // 可通过读取 RCC->BDCR 的 LSERDY 位判断 LSE 状态 if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY)) { // LSE 已就绪,切换回 LSE __HAL_RCC_RTC_CLKPRESCALER(RCC_RTCCLKSOURCE_LSE); // 清除校准 WRITE_REG(RTC->CALR, 0); // 重新初始化 RTC 同步 HAL_RTC_Init(&hrtc); } } ``` ## 四、完整代码示例(精简) 以下为关键部分整合,省略错误处理等。 ```c // 全局变量 volatile uint32_t lse_fail_time = 0; void RTC_Init(void) { // ... 如上,使能 CSS } void HAL_RTC_CSSCallback(RTC_HandleTypeDef *hrtc) { RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate; HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN); lse_fail_time = sTime.Hours*3600 + sTime.Minutes*60 + sTime.Seconds; // 重新使能 CSS HAL_RTC_ActivateCSS(hrtc); // 启动 LSI 测量 Start_LSI_Measurement(); } // 主循环中周期检查 LSE 恢复 while (1) { if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY)) { // LSE 恢复,切回 __HAL_RCC_RTC_CLKPRESCALER(RCC_RTCCLKSOURCE_LSE); WRITE_REG(RTC->CALR, 0); HAL_RTC_Init(&hrtc); break; } HAL_Delay(1000); } ``` ## 五、注意事项与避坑指南 - **CSS 使能时机**:必须在 RTC 初始化时使能,且每次触发后需重新使能(硬件自动清零)。 - **LSI 精度**:LSI 受温度影响大,校准仅能补偿静态偏差,动态漂移需定期重新校准。 - **校准窗口**:8 秒窗口可提供更细粒度,但校准值范围有限,需根据实际偏差选择。 - **中断优先级**:RTC 中断应设为高优先级,避免丢失 CSS 事件。 - **备份域复位**:LSE 故障可能涉及备份域,注意 `PWR_BackupAccess` 使能。 - **测量 LSI**:若无法使用 TIM,可借助 RTC 的异步分频器输出 1Hz 信号,用另一个定时器测量周期。 - **恢复切换**:切回 LSE 时,需重新初始化 RTC 分频器,否则时间可能跳变。 ## 六、总结 通过 RTC 的 CSS 机制和校准寄存器,STM32F4 能在 LSE 停振后无缝切换至 LSI,并利用校准补偿保证秒级走时精度,实现自愈。该方案成本低、响应快,适合对时间连续性要求高的嵌入式应用。开发者需注意校准的局限性,并设计合理的恢复流程。