# 引言 在嵌入式系统中,实时时钟(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` 实现秒级精度保持。本文提供了原理、配置步骤和代码示例,开发者可根据实际场景集成到产品中。关键在于定期测量实际频率并更新校准值,以应对环境变化。