STM32F4 系列 LSE 停振后的无感恢复与时间补偿:硬件 RTC 备份域实战指南

1. 引言

在嵌入式系统中,RTC(实时时钟)通常依赖 LSE(低速外部晶振,32.768kHz)提供精准计时。然而,LSE 可能因环境干扰、晶振老化或焊接问题而停振。一旦停振,RTC 时间将停止更新,导致系统时间错乱。STM32F4 系列提供了强大的备份域(BKP)机制,结合硬件 RTC 校准功能,可实现无感恢复与时间补偿。本文将深入讲解其原理,并给出完整实现方案。

2. 原理剖析

2.1 LSE 停振检测

STM32F4 的 RTC 模块内置 LSE 监测功能,通过 RTC_ISR 寄存器的 LSERDY 位可查询 LSE 就绪状态。当 LSE 停振时,LSERDY 变为 0。此外,RTC_ISRLSRMF(LSE 监测失败标志)可触发中断,实现实时检测。

2.2 备份域(BKP)的作用

备份域由 VBAT 引脚供电,包含 RTC 寄存器、备份寄存器(BKPxR)和 20 字节的备份 SRAM。即使主电源断电,只要 VBAT 有电,数据依然保留。利用备份寄存器,可保存 LSE 停振前的系统时间戳和补偿参数,为恢复提供依据。

2.3 时间补偿策略

LSE 停振期间,RTC 停止走时。恢复后,系统需计算停振时长并补偿。补偿分为两步:

  • 硬件补偿:利用 RTC 的同步预分频器和校准寄存器(RTC_CALR),调整 RTC 计数频率,减少晶振偏差。
  • 软件补偿:记录停振开始和结束的系统 tick(如 SysTick),计算实际停振时间,并在恢复后一次性加上。

3. 硬件配置步骤

3.1 使能备份域和 RTC 时钟

首先,使能 PWR 和 RTC 时钟,并允许访问备份域。

void RTC_Init(void) {
    // 1. 使能 PWR 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    // 2. 允许访问备份域
    PWR_BackupAccessCmd(ENABLE);
    // 3. 使能 LSE 并等待就绪
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    // 4. 选择 LSE 作为 RTC 时钟源
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    // 5. 使能 RTC 时钟
    RCC_RTCCLKCmd(ENABLE);
    // 6. 等待 RTC 同步
    RTC_WaitForSynchro();
}

3.2 配置 RTC 时间与日期

使用标准库配置 RTC 时间,并启用 LSE 监测中断。

void RTC_TimeConfig(void) {
    RTC_InitTypeDef RTC_InitStruct;
    RTC_TimeTypeDef RTC_TimeStruct;
    RTC_DateTypeDef RTC_DateStruct;

    // 使能 RTC 写保护解除
    RTC_WriteProtectionCmd(DISABLE);

    // 配置 RTC 分频:LSE 32.768kHz,异步分频 128,同步分频 256,得到 1Hz
    RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
    RTC_InitStruct.RTC_AsynchPrediv = 127;
    RTC_InitStruct.RTC_SynchPrediv = 255;
    RTC_Init(&RTC_InitStruct);

    // 设置时间 12:00:00
    RTC_TimeStruct.RTC_H12 = RTC_H12_AM;
    RTC_TimeStruct.RTC_Hour = 12;
    RTC_TimeStruct.RTC_Minute = 0;
    RTC_TimeStruct.RTC_Second = 0;
    RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct);

    // 设置日期 2025-01-01
    RTC_DateStruct.RTC_Year = 25;
    RTC_DateStruct.RTC_Month = 1;
    RTC_DateStruct.RTC_Date = 1;
    RTC_DateStruct.RTC_WeekDay = 3;
    RTC_SetDate(RTC_Format_BIN, &RTC_DateStruct);

    // 使能写保护
    RTC_WriteProtectionCmd(ENABLE);
}

3.3 配置 LSE 监测中断

在 NVIC 中使能 RTC 全局中断,并在 RTC 中断处理函数中检测 LSE 状态。

void RTC_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_LSE) != RESET) {
        // 清除中断标志
        RTC_ClearITPendingBit(RTC_IT_LSE);
        // 检测 LSE 是否停振
        if (RTC_GetFlagStatus(RTC_FLAG_LSERDY) == RESET) {
            // 记录停振开始时间(基于 SysTick)
            LSE_StopTick = GetSysTick();
            // 保存当前 RTC 时间到备份寄存器
            RTC_TimeTypeDef time;
            RTC_GetTime(RTC_Format_BIN, &time);
            BKP_WriteBackupRegister(BKP_DR1, time.RTC_Hour * 3600 + time.RTC_Minute * 60 + time.RTC_Second);
            // 设置停振标志
            LSE_Stopped = 1;
        }
    }
}

3.4 备份域数据保存

在 LSE 停振时,将当前时间和系统 tick 存入备份寄存器。

void SaveBackupData(uint32_t time, uint32_t tick) {
    BKP_WriteBackupRegister(BKP_DR1, time);
    BKP_WriteBackupRegister(BKP_DR2, tick);
}

4. 无感恢复与时间补偿实现

4.1 检测 LSE 恢复

在主循环或定时器中断中轮询 LSE 状态,一旦恢复,执行补偿。

void CheckLSE_Recovery(void) {
    if (LSE_Stopped && (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == SET)) {
        // 计算停振时长(基于 SysTick 差值)
        uint32_t stop_duration = GetSysTick() - LSE_StopTick;
        // 读取备份的时间戳
        uint32_t saved_time = BKP_ReadBackupRegister(BKP_DR1);
        // 计算补偿后的时间
        uint32_t compensated_time = saved_time + stop_duration / 1000; // 假设 SysTick 为 1ms
        // 设置 RTC 时间
        RTC_TimeTypeDef time;
        time.RTC_H12 = RTC_H12_AM;
        time.RTC_Hour = (compensated_time / 3600) % 24;
        time.RTC_Minute = (compensated_time % 3600) / 60;
        time.RTC_Second = compensated_time % 60;
        RTC_SetTime(RTC_Format_BIN, &time);
        // 清除标志
        LSE_Stopped = 0;
    }
}

4.2 硬件校准补偿

利用 RTC 校准寄存器(RTC_CALR)可微调 RTC 频率,减少长期漂移。

void RTC_Calibration(int16_t ppm) {
    // 计算校准值:每个 LSB 对应约 0.954 ppm
    uint16_t cal = (uint16_t)(ppm / 0.954);
    RTC_CalibOutputCmd(DISABLE);
    RTC_CalibCmd(ENABLE);
    RTC_SetCalibValue(cal);
}

5. 完整代码示例

以下是一个简化的综合示例,展示初始化、中断处理和恢复逻辑。

#include "stm32f4xx.h"

volatile uint32_t LSE_StopTick = 0;
volatile uint8_t LSE_Stopped = 0;

void SysTick_Init(void) {
    SysTick_Config(SystemCoreClock / 1000); // 1ms tick
}

uint32_t GetSysTick(void) {
    return SysTick->VAL; // 简化,实际应使用计数器
}

int main(void) {
    // 初始化时钟、GPIO 等
    RTC_Init();
    RTC_TimeConfig();
    SysTick_Init();
    // 使能 LSE 监测中断
    RTC_ITConfig(RTC_IT_LSE, ENABLE);
    NVIC_EnableIRQ(RTC_IRQn);

    while (1) {
        CheckLSE_Recovery();
        // 其他任务
    }
}

6. 注意事项

  • 备份域访问:操作备份寄存器前必须使能 PWR 时钟并调用 PWR_BackupAccessCmd(ENABLE),否则写操作无效。
  • LSE 监测中断:确保在 NVIC 中正确配置优先级,避免中断丢失。
  • 时间补偿精度:SysTick 的精度直接影响补偿结果,建议使用 1ms 或更高精度。
  • 校准值范围:RTC_CALR 寄存器为 9 位,最大校准范围约 ±487 ppm,超出需分段处理。
  • 电源管理:VBAT 必须接电池或超级电容,否则备份域数据在断电后丢失。

7. 总结

通过 STM32F4 的备份域和 RTC 硬件特性,我们实现了 LSE 停振后的无感恢复与时间补偿。该方法无需外部 RTC 芯片,仅依赖片内资源,有效提升了系统可靠性。在实际项目中,可根据需求调整补偿算法(如使用外部高精度时钟源)。希望本文能为你的嵌入式开发提供实用参考。