STM32F4 LSE 停振后 RTC 备份域寄存器秒中断补偿方案

一、问题背景与原理分析

STM32F4 的 RTC(实时时钟)通常依赖 LSE(32.768kHz 外部晶振)提供精准时基。但 LSE 对 PCB 布局、环境温湿度敏感,在振动或低温下可能停振。一旦 LSE 失效,RTC 将失去时钟源,导致时间停滞或系统崩溃。

核心思路:利用 RTC 备份域寄存器(BKP)在 LSE 停振前保存校准参数(如秒中断累计误差),停振后切换至 LSI(内部低速 RC,约 32kHz,精度 ±5%),并通过软件补偿秒脉冲的漂移。BKP 寄存器由 VBAT 供电,即使主电源断电也能保持数据,因此可跨复位保存校准信息。

关键点

  • LSE 与 LSI 频率差异导致秒中断周期偏差(LSI 典型 32.768kHz,但实际在 30-34kHz 范围)。
  • 补偿策略:在 LSE 正常时,周期性测量 LSI 的实际频率,并将偏差值写入 BKP 寄存器;停振后,RTC 使用 LSI 作为时钟源,同时读取 BKP 中的偏差值,动态调整秒中断的重装载值。

二、硬件与软件准备

  • 硬件:STM32F407 开发板(或其他 F4 系列),无外部 32.768kHz 晶振(或人为断开)。
  • 软件:STM32CubeIDE(或 Keil MDK),HAL 库。
  • 关键外设:RTC、BKP、PWR(电源控制)、NVIC。

三、配置步骤

1. 使能 PWR 和 BKP 时钟,并解锁备份域访问

__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();  // 允许访问备份域寄存器

2. 配置 RTC 时钟源为 LSE,并初始化 RTC

RTC_HandleTypeDef hrtc;
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;  // 异步分频,产生 1Hz 秒中断
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
HAL_RTC_Init(&hrtc);

注意:HAL_RTC_Init 内部会尝试启用 LSE,若 LSE 停振,初始化可能失败。需在初始化前检测 LSE 状态。

3. 检测 LSE 状态,并处理停振情况

// 尝试启动 LSE,超时 1s
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    // LSE 启动失败,切换至 LSI
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
    RCC_OscInitStruct.LSIState = RCC_LSI_ON;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    // 重新配置 RTC 时钟源为 LSI
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
}

4. 校准参数保存到 BKP 寄存器

在 LSE 正常工作时,定期(如每秒)测量 LSI 频率,计算秒中断误差,并写入 BKP。

// 假设通过定时器测量 LSI 实际频率(例如 32000 Hz)
uint32_t lsi_freq = 32000;  // 实际测量值
uint32_t compensation = (32768 - lsi_freq) / 32;  // 每 32 秒补偿 1 秒的误差
// 写入 BKP 寄存器(例如 BKP_DR1)
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, compensation);

5. 秒中断处理与动态补偿

在 RTC 秒中断回调中,读取 BKP 中的补偿值,并调整下一个秒中断的预分频重装载值。

void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) {
    uint32_t comp = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1);
    // 调整异步预分频(仅当使用 LSI 时)
    if (rtc_clock_source == RCC_RTCCLKSOURCE_LSI) {
        // 修改异步预分频值,实现微调
        __HAL_RTC_ASYNC_PRESCALER_CONFIG(hrtc, 127 - comp);
    }
}

四、完整代码示例(基于 HAL 库)

/* main.c 核心部分 */
RTC_HandleTypeDef hrtc;
uint8_t rtc_clock_source;

void SystemClock_Config(void);
void RTC_Init(void);
void RTC_Calibration(void);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    RTC_Init();
    // 使能秒中断
    HAL_RTCEx_SetSecond_IT(&hrtc);
    while (1) {
        // 主循环
    }
}

void RTC_Init(void) {
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();

    // 尝试 LSE
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    RCC_OscInitStruct.LSEState = RCC_LSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_OK) {
        __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
        rtc_clock_source = RCC_RTCCLKSOURCE_LSE;
    } else {
        // LSE 失败,切换 LSI
        RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
        RCC_OscInitStruct.LSIState = RCC_LSI_ON;
        HAL_RCC_OscConfig(&RCC_OscInitStruct);
        __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
        rtc_clock_source = RCC_RTCCLKSOURCE_LSI;
    }

    // 使能 RTC 时钟
    __HAL_RCC_RTC_ENABLE();

    // 初始化 RTC
    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);

    // 如果使用 LSI,则进行校准(此处简化,实际需测量 LSI 频率)
    if (rtc_clock_source == RCC_RTCCLKSOURCE_LSI) {
        uint32_t lsi_freq = 32000; // 假设测量值
        uint32_t comp = (32768 - lsi_freq) / 32;
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, comp);
    }
}

void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) {
    if (rtc_clock_source == RCC_RTCCLKSOURCE_LSI) {
        uint32_t comp = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1);
        __HAL_RTC_ASYNC_PRESCALER_CONFIG(hrtc, 127 - comp);
    }
    // 其他秒中断处理(如更新时间显示)
}

五、注意事项

  • BKP 寄存器数量有限:F4 系列有 20 个 32 位备份寄存器,可存储多个校准参数,但需规划使用。
  • LSI 频率漂移:LSI 受温度影响较大,建议在 LSE 正常时周期性更新 BKP 中的补偿值,而非一次性写入。
  • 预分频调整范围:异步预分频值范围 0-127,补偿值需限制在合理范围(如 ±10),避免溢出。
  • 备份域复位:若 VBAT 掉电,BKP 数据丢失,需重新校准。可在上电时检查 BKP 有效性标志。
  • 中断优先级:RTC 秒中断优先级应设置合理,避免阻塞其他关键任务。
  • 调试建议:在 LSE 停振模拟时,可人为断开晶振或使用软件强制切换,便于验证补偿逻辑。

六、总结

通过 RTC 备份域寄存器保存校准参数,在 LSE 停振后切换至 LSI 并进行动态补偿,可有效维持秒中断的精度,避免系统时间漂移。该方法无需额外硬件,成本低,适用于对可靠性要求较高的嵌入式应用。开发者可根据实际场景扩展校准算法,如使用温度传感器修正 LSI 漂移,进一步提升精度。