引言

在嵌入式系统中,实时时钟(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)

通过调整 CALMCALP,可以在 ±0.477 ppm 的精度内微调频率,对应秒级误差约 ±0.04 秒/天。

3. LSE 停振检测与切换

STM32F4 的 RTC 支持时钟源自动切换(通过 RCC_BDCR 的 RTCSEL 位),但默认不会自动检测 LSE 故障。需要借助 LSE 监视器(LSEM)或外部中断检测,然后软件切换时钟源。本文聚焦于切换后的校准补偿。

配置步骤

1. 初始化 RTC 并启用校准

首先,确保 RTC 已配置为 LSE 或 LSI 时钟源,并启用校准功能。以下代码基于 STM32F4xx HAL 库。

#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 个脉冲,补偿范围有限。对于较大偏差,需结合 CALMCALP

更实用的方法是:先调整分频值,使输出接近 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 减少脉冲。

计算 CALMCALM = (f_rtc / ( (ASYNC+1)*(SYNC+1) ) - 1) * 2^20

本例中:CALM = (32010/32000 - 1) * 1048576 ≈ 327.68,取整为 328。

3. 写入校准寄存器

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(若可用)的比值。

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 检测和切换逻辑。

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