STM32F4 LSE 停振后的无感恢复:硬件 RTC 备份域时间校准实战指南

1. 问题背景与挑战

在工业控制、智能仪表等嵌入式场景中,STM32F4 常依赖 LSE(32.768kHz 晶振)驱动 RTC 提供精确时间。然而,晶振受温度、振动或老化影响可能停振,导致 RTC 停止计时。若系统未做处理,重启后时间将丢失或错误,影响数据记录与通信同步。

核心挑战

  • 如何快速检测 LSE 停振?
  • 如何在无外部 RTC 芯片的情况下,利用内部资源实现时间保持?
  • 如何在晶振恢复后自动校准,且不中断主流程?

STM32F4 的硬件 RTC 与备份域(BKP)提供了解决方案:备份寄存器可在 VDD 掉电时由 VBAT 供电保持数据,而 RTC 的校准功能可修正晶振频率偏差。本文将结合这两者,设计一套无感恢复机制。

2. 硬件原理与备份域架构

2.1 RTC 与备份域的关系

STM32F4 的 RTC 位于备份域中,由 VBAT 引脚供电(当 VDD 断开时)。备份域包含:

  • RTC 计数器、预分频器、校准寄存器
  • 20 个 32 位备份寄存器(BKP Registers)
  • 备份域控制寄存器(RCC_BDCR)

LSE 振荡器也由备份域供电,因此即使主电源掉电,只要 VBAT 有电,RTC 仍可运行。但 LSE 停振时,RTC 时钟源失效,计数器停止。

2.2 LSE 停振检测机制

STM32F4 提供 LSE 就绪标志(LSERDY)和中断。当 LSE 停振时,LSERDY 会清零,可触发 LSE 中断(需在 RCC_BDCR 中使能)。此外,可通过读取 RTC 的 RTC_ISR 中的 RSF(寄存器同步标志)或直接监控 LSE 状态。

推荐方案:使用 LSE 中断 + 定时轮询双重检测,避免中断丢失。

3. 无感恢复与时间校准设计

3.1 整体流程

  1. 初始化阶段:配置 LSE、RTC,使能 LSE 中断,将当前时间戳存入备份寄存器。
  2. 运行阶段:正常使用 RTC 时间,同时每秒检查 LSE 状态。
  3. 停振检测:若 LSE 停振,立即切换 RTC 时钟源到 LSI(内部低速时钟,约 32kHz),并记录停振时刻到备份寄存器。
  4. 恢复校准:当 LSE 重新就绪,计算停振时长(基于 LSI 计数的 RTC 差值),修正 RTC 计数器,并更新备份寄存器。

3.2 时钟源切换与时间保持

LSE 停振后,RTC 可自动切换到 LSI(需在 RCC_BDCR 中设置 RTCSEL)。但 LSI 精度较差(±5%),长时间运行会累积误差。因此,我们仅在停振期间使用 LSI,并记录停振时间点,恢复后补偿。

关键点:RTC 计数器在切换时钟源时不会复位,但预分频器需重新配置。我们采用异步预分频(ASYNC_PREDIV)和同步预分频(SYNC_PREDIV)来适配不同时钟频率。

4. 配置步骤与代码实现

4.1 初始化 RTC 与备份域

void RTC_Init(void) {
    // 1. 使能 PWR 和 BKP 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE); // 允许访问备份域

    // 2. 使能 LSE,等待就绪
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
        // 超时处理,例如 1 秒
    }

    // 3. 选择 LSE 作为 RTC 时钟源
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);

    // 4. 配置 RTC 预分频(LSE=32.768kHz,1秒中断)
    RTC_InitTypeDef RTC_InitStruct;
    RTC_InitStruct.RTC_AsynchPrediv = 0x7F;   // 128 分频
    RTC_InitStruct.RTC_SynchPrediv = 0xFF;    // 256 分频,得到 1Hz
    RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
    RTC_Init(&RTC_InitStruct);

    // 5. 使能 LSE 中断(在 RCC_BDCR 中)
    RCC_BDCRCmd(ENABLE); // 使能备份域控制寄存器访问
    RCC_BDCR->BDCR |= RCC_BDCR_LSEON | RCC_BDCR_LSEBYP; // 若需要旁路,可设置
    // 注意:LSE 中断使能需在 EXTI 中配置,此处略

    // 6. 保存初始时间戳到备份寄存器
    uint32_t timestamp = RTC_GetCounter();
    BKP_WriteBackupRegister(BKP_DR1, timestamp);
}

4.2 LSE 停振检测与时钟切换

void LSE_Stop_Handler(void) {
    // 记录停振时刻(基于当前 RTC 计数器)
    uint32_t stop_time = RTC_GetCounter();
    BKP_WriteBackupRegister(BKP_DR2, stop_time);

    // 切换 RTC 时钟源到 LSI(需重新初始化 RTC 时钟)
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
    // 重新配置预分频以适配 LSI(约 32kHz,但精度差)
    RTC_InitTypeDef RTC_InitStruct;
    RTC_InitStruct.RTC_AsynchPrediv = 0x7F;
    RTC_InitStruct.RTC_SynchPrediv = 0xFF;
    RTC_Init(&RTC_InitStruct);

    // 设置标志位,表示处于停振状态
    BKP_WriteBackupRegister(BKP_DR3, 0xA5A5); // 自定义标志
}

4.3 LSE 恢复与时间校准

void LSE_Resume_Handler(void) {
    if (BKP_ReadBackupRegister(BKP_DR3) == 0xA5A5) {
        // 读取停振时刻和当前 LSI 计数值
        uint32_t stop_time = BKP_ReadBackupRegister(BKP_DR2);
        uint32_t current_time = RTC_GetCounter();

        // 计算停振时长(基于 LSI 的计数差值,需考虑预分频差异)
        // 假设 LSI 频率为 32.768kHz,与 LSE 相同,则差值即为秒数
        uint32_t elapsed = current_time - stop_time;

        // 切换回 LSE
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
        // 重新初始化 RTC 预分频
        RTC_InitTypeDef RTC_InitStruct;
        RTC_InitStruct.RTC_AsynchPrediv = 0x7F;
        RTC_InitStruct.RTC_SynchPrediv = 0xFF;
        RTC_Init(&RTC_InitStruct);

        // 校准时间:将当前计数器加上 elapsed(因为 LSI 期间时间仍在流逝)
        RTC_SetCounter(current_time + elapsed);

        // 清除标志
        BKP_WriteBackupRegister(BKP_DR3, 0x0000);
    }
}

4.4 主循环与中断处理

void EXTI_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line18) != RESET) { // LSE 中断线
        EXTI_ClearITPendingBit(EXTI_Line18);
        if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
            LSE_Stop_Handler();
        } else {
            LSE_Resume_Handler();
        }
    }
}

int main(void) {
    // 系统初始化...
    RTC_Init();
    // 配置 EXTI 线18(LSE 事件)
    EXTI_InitTypeDef EXTI_InitStruct;
    EXTI_InitStruct.EXTI_Line = EXTI_Line18;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);

    while (1) {
        // 主任务...
        // 每秒轮询 LSE 状态作为备份检测
        if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
            LSE_Stop_Handler();
        } else {
            LSE_Resume_Handler();
        }
        Delay(1000);
    }
}

5. 注意事项与优化建议

  • 备份寄存器访问:必须在使能 PWR 备份访问后才能读写,否则硬件错误。
  • 时钟切换时序:切换 RTC 时钟源时,需确保 RTC 已禁用(RTC_WaitForSynchro),否则可能产生毛刺。
  • LSI 精度补偿:若停振时间较长,LSI 误差累积,可在恢复时通过 RTC 校准寄存器(RTC_CALR)微调,或使用外部时间源(如 GPS/网络)进行绝对校准。
  • 中断优先级:LSE 中断应设为高优先级,避免丢失恢复事件。
  • 测试方法:可通过断开 LSE 晶振引脚模拟停振,观察备份寄存器变化及时间连续性。

6. 总结

本文基于 STM32F4 的硬件 RTC 备份域,实现了 LSE 停振后的无感恢复与时间校准。通过备份寄存器保存关键时间戳,利用 LSI 作为临时时钟源,并在 LSE 恢复后自动补偿时间差,确保了时间系统的连续性。该方案无需外部硬件,成本低且可靠,适用于对时间敏感的嵌入式应用。开发者可根据实际需求扩展,如增加温度补偿或网络校时功能,进一步提升精度。