STM32F4 LSE 停振无感恢复:RTC 备份域寄存器的时间校准实战

一、问题背景与原理

LSE(32.768kHz 晶振)是 STM32F4 RTC 的典型时钟源,但受温度、振动或老化影响可能停振。一旦 LSE 失效,RTC 将停止计数,导致时间丢失。传统方案是检测到停振后复位系统,但会中断业务。本文利用 STM32F4 的备份域寄存器(BKP)RTC 时钟源自动切换功能,实现无感恢复:

  • 检测机制:RTC 的 RTC_ISR 寄存器中的 LSERDY 位(RCC 时钟控制)和 RTC_ISRALRAF 等标志可指示异常。更可靠的是利用 RTC 的校准寄存器(RTC_CALR)BKP 寄存器中保存的校验值
  • 备份域特性:BKP 寄存器(20 个 32 位)在 VDD 掉电时由 VBAT 供电,可保存时间戳和状态标志。
  • 恢复策略:检测到 LSE 停振后,立即将 RTC 时钟源切换到 LSI(内部低速 RC,约 32kHz),并利用 BKP 中保存的最近有效时间戳和 LSI 的周期计数,计算出补偿量,实现时间校准。

二、硬件与软件准备

  • 硬件:STM32F407 开发板,外部 32.768kHz 晶振(LSE),VBAT 接 3V 电池。
  • 软件:STM32CubeIDE(HAL 库),配置 RTC 以 LSE 为时钟源,使能备份域访问。

三、配置步骤

1. 使能备份域访问并配置 RTC

void RTC_Init(void) {
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess(); // 允许访问备份域

    // 使能 LSE,等待就绪
    __HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
        // 超时处理,若超时则标记 LSE 故障
    }

    // 选择 LSE 作为 RTC 时钟源
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
    __HAL_RCC_RTC_ENABLE();

    // 初始化 RTC 为 1Hz 计数
    RTC_HandleTypeDef hrtc = {0};
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    HAL_RTC_Init(&hrtc);
}

2. 保存时间戳到备份域

每次 RTC 秒中断或定时更新时,将当前时间戳写入 BKP 寄存器,并附加一个校验值(如 CRC)。

void Save_TimeStamp(void) {
    uint32_t timestamp = HAL_RTC_GetTimestamp(&hrtc); // 自定义函数,返回秒数
    uint32_t crc = CRC32(&timestamp, sizeof(timestamp));
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, timestamp);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, crc);
}

3. 检测 LSE 停振

利用 RCC 中断或轮询 LSERDY 标志,并配合 BKP 中的校验值判断时间是否有效。

uint8_t Check_LSE_Status(void) {
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
        // LSE 停振
        return 1;
    }
    return 0;
}

4. 无感恢复与时间校准

当检测到停振时,执行以下步骤:

  1. 读取 BKP 中的最近时间戳和校验值,验证有效性。
  2. 将 RTC 时钟源切换为 LSI(内部 RC),并重新初始化 RTC 为 1Hz 计数。
  3. 计算 LSI 的漂移:利用 RTC 的同步预分频器,通过测量 LSI 的实际频率(例如,使用定时器或系统时钟对比),得到补偿系数。
  4. 将保存的时间戳加上补偿量,写入 RTC 计数器。
void RTC_Recover(void) {
    // 1. 读取备份域中的时间戳
    uint32_t saved_ts = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
    uint32_t saved_crc = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
    if (CRC32(&saved_ts, sizeof(saved_ts)) != saved_crc) {
        // 校验失败,时间无效,需重新设置
        saved_ts = 0; // 或使用默认时间
    }

    // 2. 切换到 LSI
    __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
    __HAL_RCC_RTC_ENABLE();

    // 3. 重新初始化 RTC(使用 LSI)
    RTC_HandleTypeDef hrtc_lsi = {0};
    hrtc_lsi.Instance = RTC;
    hrtc_lsi.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc_lsi.Init.AsynchPrediv = 127; // LSI 约 32kHz,分频后 1Hz
    hrtc_lsi.Init.SynchPrediv = 255;
    HAL_RTC_Init(&hrtc_lsi);

    // 4. 计算 LSI 漂移补偿(示例:假设 LSI 实际频率为 32000 Hz,预分频后 1Hz 误差 0.1%)
    // 实际应用中,可通过测量 LSI 频率(如使用 TIM 捕获)得到精确值
    uint32_t compensation = (uint32_t)(saved_ts * 0.001); // 0.1% 补偿
    uint32_t new_ts = saved_ts + compensation;

    // 5. 设置 RTC 时间
    HAL_RTC_SetTime(&hrtc_lsi, &new_ts, RTC_FORMAT_BIN);
    HAL_RTC_SetDate(&hrtc_lsi, &new_date, RTC_FORMAT_BIN);

    // 6. 更新备份域中的时间戳
    Save_TimeStamp();
}

5. 主循环监控

在主循环中定期检查 LSE 状态,若停振则调用恢复函数,并记录事件。

int main(void) {
    HAL_Init();
    RTC_Init();
    Save_TimeStamp();

    while (1) {
        if (Check_LSE_Status()) {
            RTC_Recover();
            // 可添加 LED 指示或日志
        }
        HAL_Delay(1000);
    }
}

四、完整代码示例(关键部分)

// 自定义获取时间戳函数(简化)
uint32_t HAL_RTC_GetTimestamp(RTC_HandleTypeDef *hrtc) {
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
    // 转换为 Unix 时间戳(需实现日期转换)
    return ConvertToUnix(sDate.Year, sDate.Month, sDate.Date, sTime.Hours, sTime.Minutes, sTime.Seconds);
}

// CRC32 实现(略)
uint32_t CRC32(uint32_t *data, uint32_t len);

五、注意事项

  • 备份域访问:必须使能 PWR 和 BKP 时钟,并调用 HAL_PWR_EnableBkUpAccess(),否则读写 BKP 会失败。
  • LSE 启动超时:LSE 起振时间可能长达几秒,需设置合理超时,避免死等。
  • LSI 精度:LSI 频率误差较大(±5%),补偿算法需基于实际测量,否则时间漂移严重。
  • 中断处理:建议使用 RTC 秒中断定期保存时间戳,但注意写入 BKP 时需关闭写保护(HAL_RTCEx_BKUPWrite 内部处理)。
  • 电源管理:VBAT 必须接电池或电容,否则备份域数据在掉电时丢失。
  • 恢复时机:若 LSE 停振后系统立即掉电,备份域中的时间戳可能未更新,需考虑在掉电前保存(如使用 PVD 中断)。

六、总结

通过备份域寄存器和 RTC 时钟源切换,STM32F4 可以在 LSE 停振后无缝切换到 LSI,并利用保存的时间戳进行校准,避免系统复位和时间丢失。该方法适用于对时间连续性要求高的工业设备,但需注意 LSI 的精度补偿和备份域电源设计。实际项目中,建议结合外部高精度 RTC 芯片或网络时间同步,进一步提升可靠性。