引言

在工业控制、物联网设备等嵌入式场景中,RTC(实时时钟)是记录事件时间戳的核心外设。STM32F4 系列通常使用 32.768kHz 的 LSE 晶振为 RTC 提供低功耗、高精度的时钟源。然而,LSE 晶振可能因焊接不良、温漂、振动或 EMI 干扰而停振,一旦停振,RTC 将失去时钟基准,系统时间立即失效。

本文提出一种嵌入式解决方案:当检测到 LSE 停振时,自动切换至内部 HSI(高速内部振荡器,16MHz)作为 RTC 时钟源,并通过软件校准算法补偿 HSI 的频率偏差,使 RTC 在无外部晶振情况下仍能保持较高精度(典型误差 < 50ppm)。

原理剖析

1. RTC 时钟源架构

STM32F4 的 RTC 时钟源可选:

  • LSE:外部低速晶振,精度高(±20ppm),但依赖外部硬件。
  • LSI:内部低速 RC 振荡器,约 32kHz,精度差(±50%),不适合计时。
  • HSE/HSI 分频:通过 RTCSEL[1:0] 位选择,HSI 经 32 分频后得到 500kHz,再经异步预分频器得到 1Hz 计数时钟。

关键点:RTC 时钟源必须在 RTC 使能前选定,切换时钟源需复位 RTC 域。

2. HSI 频率误差与校准原理

HSI 出厂精度约 ±1%,温度漂移可达 ±2%。直接使用会导致 RTC 日误差达数百秒。校准思路:

  • 利用 STM32F4 内部的 RTC 校准寄存器(RTC_CALR)进行数字校准,可调整 ±0.95 ppm 步进。
  • 但 HSI 误差较大,需先通过软件测量 HSI 与已知参考(如系统主时钟 HSE 或外部信号)的偏差,然后计算校准值。

本文采用更实用的方法:利用定时器输入捕获测量 HSI 分频后的 500kHz 信号周期,与理想值比较,得到误差比例,再动态调整 RTC 的异步预分频因子(ASYNCHPREDIV)或使用 RTC 平滑校准寄存器。

3. 检测 LSE 停振

STM32F4 的 RTC 提供 LSEDRV 和 LSEMOD 位,但无硬件停振检测。常用方法:

  • 周期性读取 RTC 秒值,若在超时时间内未更新,则判定 LSE 失效。
  • 或使用 LSE 就绪标志(LSERDY)轮询,若长时间未置位则停振。

配置步骤

1. 初始化 RTC 使用 LSE

  • 使能 PWR 和备份域时钟。
  • 解锁备份域写保护。
  • 配置 LSE 并等待就绪。
  • 选择 LSE 作为 RTC 时钟源。
  • 配置预分频器(异步 127,同步 255)得到 1Hz。

2. 检测 LSE 停振并切换至 HSI

  • 在主循环中监测 RTC 秒更新标志。
  • 若超时(如 2 秒)未更新,则执行切换流程。

3. HSI 校准流程

  • 使能 HSI 并等待稳定。
  • 配置 TIM5 输入捕获,测量 HSI/32 信号(即 500kHz)的周期。
  • 计算实际频率与理想值的偏差。
  • 根据偏差调整 RTC 的同步预分频器(或使用校准寄存器)。

完整代码示例

以下代码基于 STM32F407,使用 HAL 库。

// 全局变量
volatile uint8_t rtc_updated = 0;
volatile uint32_t last_rtc_sec = 0;

// 初始化 RTC 使用 LSE
void RTC_Init_LSE(void) {
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    __HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
        // 超时处理
    }
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
    __HAL_RCC_RTC_ENABLE();
    
    RTC_HandleTypeDef hrtc = {0};
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    HAL_RTC_Init(&hrtc);
}

// 检测 LSE 停振
uint8_t LSE_Stopped(void) {
    uint32_t current_sec = HAL_RTC_GetTime(&hrtc, RTC_FORMAT_BIN);
    if (current_sec == last_rtc_sec) {
        return 1; // 未更新,可能停振
    }
    last_rtc_sec = current_sec;
    return 0;
}

// 切换至 HSI 并校准
void RTC_SwitchToHSI_Calibrate(void) {
    // 1. 使能 HSI
    __HAL_RCC_HSI_ENABLE();
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET);
    
    // 2. 配置 TIM5 输入捕获测量 HSI/32 频率
    // 假设 TIM5_CH1 连接到 MCO1 输出 HSI/32(需配置 GPIO 和 MCO)
    // 此处省略具体配置,假设测得实际频率为 freq_hz
    uint32_t measured_freq = TIM5_MeasureFrequency(); // 返回 Hz
    
    // 3. 计算误差比例
    float error_ratio = (float)measured_freq / 500000.0f; // 理想 500kHz
    
    // 4. 调整同步预分频器(以 1Hz 输出为例)
    // 同步预分频器值 = (500000 / error_ratio) - 1,但需取整
    uint32_t sync_prediv = (uint32_t)((500000.0f / error_ratio) - 1);
    if (sync_prediv > 0x3FFF) sync_prediv = 0x3FFF; // 限制范围
    
    // 5. 重新配置 RTC 时钟源为 HSI
    __HAL_RCC_RTC_DISABLE();
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSI_DIV32);
    __HAL_RCC_RTC_ENABLE();
    
    // 6. 设置新的预分频值
    hrtc.Init.AsynchPrediv = 127; // 保持不变
    hrtc.Init.SynchPrediv = sync_prediv;
    HAL_RTC_Init(&hrtc);
    
    // 7. 重新设置时间(可选,从上次保存的时间恢复)
    // HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}

// 主循环
int main(void) {
    HAL_Init();
    RTC_Init_LSE();
    
    while (1) {
        if (LSE_Stopped()) {
            RTC_SwitchToHSI_Calibrate();
            // 记录错误日志,并继续运行
        }
        HAL_Delay(1000);
    }
}

注意事项

  • 切换时机:必须在 RTC 使能前切换时钟源,否则需复位 RTC 域,导致时间丢失。建议在检测到停振后立即保存当前时间到备份寄存器,再执行切换。
  • 校准精度:HSI 受温度影响较大,建议在系统空闲时周期性重新校准(如每 10 分钟)。
  • 测量频率:使用 TIM 输入捕获时,需确保参考时钟(如 HSE)稳定,否则测量误差会传递到校准结果。
  • 功耗考量:HSI 功耗高于 LSE,若系统对功耗敏感,可考虑在 LSE 恢复后自动切回。
  • 备份域写保护:操作 RTC 和备份寄存器前务必解锁备份域,否则写操作无效。
  • 中断处理:建议使用 RTC 闹钟或唤醒定时器定期检测秒更新,避免主循环阻塞。

总结

通过上述方案,STM32F4 在 LSE 停振后仍能依靠 HSI 维持 RTC 运行,并通过软件校准将精度控制在可接受范围。该方法无需额外硬件,适用于对成本敏感的嵌入式产品。开发者可根据实际需求调整校准周期和预分频计算,实现更优的时间保持能力。