引言

在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(32.768kHz 晶振)保持时间基准。然而,晶振可能因环境干扰、老化或焊接问题而停振。一旦 LSE 失效,RTC 时间会丢失,若系统随后复位,默认时间将导致日志、通信协议或调度逻辑错乱。STM32F4 系列提供了备份寄存器(BKP)和 RTC 校准机制,我们可以利用它们实现“无感恢复”——即使 LSE 停振,系统重启后也能恢复到停振前的状态。

原理分析

1. LSE 停振检测

STM32F4 的 RTC 模块包含一个时钟安全系统(CSS)用于 LSE,但默认未启用。启用后,若 LSE 失效,RTC 会触发 CSS 中断,并将 RTC 时钟自动切换到 LSI(内部低速时钟)。但 LSI 精度远低于 LSE,时间会漂移。更关键的是,如果系统复位,RTC 会尝试重新使用 LSE,若 LSE 仍故障,则可能进入死循环或使用默认时间。

2. 备份寄存器(BKP)的作用

BKP 是 20 个 32 位寄存器(位于备份域),由 VBAT 供电,在系统复位或电源掉电时数据不丢失(前提是 VBAT 有电)。我们可以利用 BKP 存储:

  • RTC 在 LSE 停振前的秒数(或完整时间戳)
  • 停振标志(如 0xA5A5)
  • 其他系统状态(如任务计数、校准值)

这样,系统重启后,固件检查 BKP 中的标志,若有效,则用保存的时间初始化 RTC,并切换到 LSI 继续运行,实现无感恢复。

3. 恢复流程

  • 正常运行时,定期(如每秒)将 RTC 时间写入 BKP。
  • 检测到 LSE 停振(通过 CSS 中断或轮询 RTC_ISR 的 LSERDY 位),立即将当前时间写入 BKP,并设置标志。
  • 系统复位后,启动代码检查 BKP 标志,若有效,则用 BKP 中的时间初始化 RTC,并强制使用 LSI,跳过 LSE 启动等待。

配置步骤

1. 使能备份域访问和 BKP 时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 允许访问备份域
BKP_DeInit(); // 可选,但会清除所有备份数据

2. 配置 RTC 时钟源(LSE 或 LSI)

RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 优先使用 LSE
RCC_RTCCLKCmd(ENABLE);
RTC_WaitForSynchro(); // 等待同步

3. 启用 LSE CSS 中断(可选)

RTC_ITConfig(RTC_IT_CSS, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_Init(&NVIC_InitStructure);

4. 定期保存时间到 BKP

在 RTC 秒中断或主循环中调用 SaveTimeToBKP()

5. 启动时检查并恢复

main() 初始化 RTC 之前,先检查 BKP 标志。

完整代码示例

以下代码基于 STM32F4xx 标准外设库,演示核心逻辑。

#include "stm32f4xx.h"
#include <stdio.h>

#define BKP_FLAG_VALUE 0xA5A5
#define BKP_FLAG_REG  BKP_DR1
#define BKP_TIME_REG  BKP_DR2

// 保存当前时间到 BKP
void SaveTimeToBKP(void) {
    uint32_t seconds = RTC_GetCounter(); // 假设 RTC 计数为秒
    BKP_WriteBackupRegister(BKP_TIME_REG, seconds);
    BKP_WriteBackupRegister(BKP_FLAG_REG, BKP_FLAG_VALUE);
}

// 检查 BKP 标志是否有效
uint8_t IsBKPValid(void) {
    return (BKP_ReadBackupRegister(BKP_FLAG_REG) == BKP_FLAG_VALUE);
}

// 从 BKP 恢复时间
uint32_t RestoreTimeFromBKP(void) {
    return BKP_ReadBackupRegister(BKP_TIME_REG);
}

// 初始化 RTC 使用 LSE 或 LSI
void RTC_Init(uint8_t useLSE) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);

    if (useLSE) {
        RCC_LSEConfig(RCC_LSE_ON);
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待 LSE 就绪
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    } else {
        RCC_LSICmd(ENABLE);
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
    }

    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForSynchro();

    // 配置 RTC 为 1 秒中断(略)
}

int main(void) {
    // 初始化系统时钟等

    // 检查 BKP 标志
    if (IsBKPValid()) {
        // 有备份,使用 LSI 快速启动
        RTC_Init(0); // 使用 LSI
        uint32_t savedTime = RestoreTimeFromBKP();
        RTC_SetCounter(savedTime); // 恢复时间
        // 清除标志,避免下次重复恢复
        BKP_WriteBackupRegister(BKP_FLAG_REG, 0);
        printf("恢复时间: %lu\n", savedTime);
    } else {
        // 无备份,正常使用 LSE
        RTC_Init(1); // 使用 LSE
        // 启动后立即保存一次
        SaveTimeToBKP();
    }

    // 主循环中每秒保存时间
    while (1) {
        // 假设有秒中断或轮询
        // SaveTimeToBKP();
    }
}

// RTC 中断处理(CSS 检测)
void RTC_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_CSS) != RESET) {
        RTC_ClearITPendingBit(RTC_IT_CSS);
        // LSE 停振,立即保存时间,并切换到 LSI
        SaveTimeToBKP();
        // 重新配置 RTC 使用 LSI(需先关闭 RTC 时钟)
        RCC_RTCCLKCmd(DISABLE);
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        RCC_RTCCLKCmd(ENABLE);
        RTC_WaitForSynchro();
        // 注意:切换后 RTC 计数器会保留,但时间基准变化,需重新校准
    }
}

注意事项

  • 备份域电源:BKP 依赖 VBAT 供电,若 VBAT 也掉电,数据会丢失。确保 VBAT 接电池或大电容。
  • LSE 启动超时:在等待 LSE 就绪时,应加入超时机制,避免死循环。若超时,直接切换到 LSI。
  • 时间精度:LSI 精度差(约 ±1%),恢复后时间会漂移,建议在 LSE 恢复后重新校准或同步。
  • BKP 写入次数:BKP 寄存器没有写入次数限制,但频繁写入会增加功耗,建议每秒或每 10 秒一次。
  • 清除标志:恢复后清除 BKP 标志,防止下次复位重复恢复旧时间。
  • 中断优先级:CSS 中断应设为高优先级,确保及时处理。

总结

通过 RTC 备份寄存器,STM32F4 可以在 LSE 停振后保存关键时间信息,并在系统重启时快速恢复,实现无感衔接。此方法简单可靠,适用于对时间连续性要求高的场景。结合 LSI 切换和超时处理,可构建健壮的时钟系统。