引言

在工业控制、物联网设备中,RTC(实时时钟)是时间戳、定时唤醒的核心。STM32F4 系列默认使用 LSE(32.768kHz 外部晶振)驱动 RTC,其精度高、功耗低。但 LSE 起振慢、抗振性差,在强电磁干扰或晶振老化时易停振。一旦 LSE 失效,RTC 若未配置备用时钟,将停止计时,造成系统时间错乱。本文介绍一种实用方案:利用内部 HSI(16MHz)作为备份时钟,并通过软件校准补偿频率偏差,确保时间连续且精度可控。

1. 原理剖析:RTC 时钟树与 LSE 故障机制

STM32F4 的 RTC 时钟源可选 LSE、LSI(约 32kHz)或 HSE 分频。RTC 内核使用 1Hz 的计数时钟,由异步预分频器(ASYNC_PRE)和同步预分频器(SYNC_PRE)对时钟源分频得到。典型配置:LSE 32.768kHz,ASYNC=127,SYNC=255,输出 1Hz。

LSE 停振时,硬件会置位 RTC_ISR 中的 LSERDY 位(若使能),但 RTC 不会自动切换时钟源。若继续使用 LSE,RTC 将停止更新。因此,需在软件中检测 LSE 故障,并切换至 HSI。HSI 为 16MHz,经 PWR 域的 RTC 时钟选择器(RTCSEL)可配置为 HSI/32(即 500kHz),再经预分频器得到 1Hz。但 HSI 精度(±1%)远低于 LSE(±20ppm),直接使用会导致日误差达 864 秒,必须校准。

2. 校准策略:基于 HSI 的软件补偿

HSI 频率固定,但受温度影响漂移。校准思路:在 LSE 正常时,测量 HSI 的实际频率(通过定时器捕获或 RTC 校准寄存器),计算偏差系数。当切换至 HSI 后,利用 RTC 的同步预分频器(可动态调整)或软件累加器进行补偿。

具体实现:

  • 在 LSE 运行期间,使用 TIM5 的捕获通道测量 HSI 周期,得到实际频率 f_HSI_actual。
  • 计算理想分频值:理想情况下,HSI/32=500kHz,预分频后 1Hz。实际频率偏差导致每秒计数偏差 Δ = f_HSI_actual/32 - 500000。
  • 切换后,每秒软件增加或减少相应计数,或调整 SYNC_PRE 值(但 SYNC_PRE 只能在 RTC 初始化时设置,运行中修改需重新初始化,故采用软件补偿更灵活)。

3. 配置步骤

  1. 使能 PWR 和 RTC 时钟

    • 开启 PWR 时钟,进入备份域访问。
    • 使能 RTC 时钟(通过 RCC_BDCR 寄存器)。
  2. 检测 LSE 状态

    • 读取 RTC_ISR 的 LSERDY 位,若为 0 表示 LSE 未就绪。
    • 也可使能 LSE 中断(LSIE)以异步通知。
  3. 切换时钟源至 HSI

    • 修改 RCC_BDCR 的 RTCSEL 位为 10(HSI/32)。
    • 注意:切换前需确保 RTC 已停止(通过 RTC_CR 的 BCE 位控制备份域写保护)。
  4. 配置 RTC 预分频器

    • 设置 ASYNC_PRE=127,SYNC_PRE=255(针对 500kHz 输入)。
    • 若 HSI 实际频率有偏差,可微调 SYNC_PRE,但需重新初始化 RTC。
  5. 软件补偿

    • 在秒中断中,根据校准系数调整 RTC 计数(通过 RTC_SSR 或软件变量)。

4. 完整代码示例

以下代码基于 STM32F407,使用 HAL 库,实现 LSE 故障检测、切换 HSI 及软件补偿。

// 全局变量
volatile uint32_t g_rtc_second_count = 0;
volatile uint8_t g_lse_failure = 0;
float g_hsi_calibration = 1.0f; // 校准系数,实际频率/理想频率

// LSE 故障中断回调(在 RTC_IRQHandler 中调用)
void HAL_RTC_LSE_ErrorCallback(RTC_HandleTypeDef *hrtc) {
    g_lse_failure = 1;
    // 切换时钟源至 HSI
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV32);
    // 重新初始化 RTC 预分频器
    RTC_TimeTypeDef sTime = {0};
    RTC_DateTypeDef sDate = {0};
    HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
    // 停止 RTC
    HAL_RTC_DeInit(hrtc);
    // 重新配置
    RTC_InitTypeDef init = {0};
    init.HourFormat = RTC_HOURFORMAT_24;
    init.AsynchPrediv = 127;
    init.SynchPrediv = 255;
    init.OutPut = RTC_OUTPUT_DISABLE;
    HAL_RTC_Init(hrtc);
    // 设置时间
    HAL_RTC_SetTime(hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_SetDate(hrtc, &sDate, RTC_FORMAT_BIN);
}

// 秒中断回调(用于软件补偿)
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) {
    g_rtc_second_count++;
    if (g_lse_failure) {
        // 补偿:每秒根据校准系数调整计数
        // 例如,若实际频率偏高,则每秒多计数 0.001 秒,通过软件累加
        static float error_accum = 0.0f;
        error_accum += (g_hsi_calibration - 1.0f);
        if (error_accum >= 1.0f) {
            // 增加 1 秒(通过设置 RTC 的 SSR 或直接修改时间)
            RTC_TimeTypeDef sTime;
            HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);
            sTime.Seconds++;
            if (sTime.Seconds >= 60) sTime.Seconds = 0;
            HAL_RTC_SetTime(hrtc, &sTime, RTC_FORMAT_BIN);
            error_accum -= 1.0f;
        }
    }
}

// 初始化时测量 HSI 频率(示例:使用 TIM5 捕获)
void Measure_HSI_Frequency(void) {
    // 配置 TIM5 输入捕获,测量 HSI/32 信号(需外部连接或内部路由)
    // 此处省略具体配置,假设得到实际频率 f_actual
    float f_actual = 500123.0f; // 示例值
    g_hsi_calibration = f_actual / 500000.0f;
}

// 主程序初始化
int main(void) {
    HAL_Init();
    // 使能 PWR 时钟,允许访问备份域
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    // 使能 LSE 并等待就绪
    __HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
        // 超时处理
    }
    // 配置 RTC 时钟源为 LSE
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
    __HAL_RCC_RTC_ENABLE();
    // 初始化 RTC(略)
    // 使能 LSE 错误中断
    HAL_RTCEx_SetLSECallback(HAL_RTC_LSE_ErrorCallback);
    // 测量 HSI 校准系数
    Measure_HSI_Frequency();
    // 主循环
    while (1) {
        // 应用逻辑
    }
}

5. 注意事项

  • 备份域写保护:修改 RTC 时钟源前,必须关闭备份域写保护(通过 PWR_CR 的 DBP 位),否则操作无效。
  • 切换时机:LSE 停振后,RTC 可能已停止,切换前需保存当前时间,避免丢失。
  • 校准精度:HSI 受温度影响,建议定期在 LSE 正常时重新测量校准系数,并存储到备份寄存器。
  • 中断优先级:LSE 错误中断应设为高优先级,确保及时响应。
  • 测试验证:在强干扰环境下测试,观察时间误差,必要时调整补偿算法(如使用 PID)。

结语

通过上述方案,STM32F4 在 LSE 失效时能自动切换至 HSI,并借助软件校准保持时间精度,极大提升了系统可靠性。该方法适用于对成本敏感、无法使用外部高精度晶振的场景。开发者可根据实际需求优化补偿算法,实现更精准的计时。