引言

在嵌入式系统中,RTC(实时时钟)常依赖外部 32.768kHz 晶振(LSE)提供精准时基。然而,LSE 可能因温度漂移、振动或老化而停振。STM32F4 系列在 LSE 失效时,RTC 会切换至 LSI(约 32kHz,精度差),导致时间误差累积。更严重的是,若系统复位,RTC 时间可能丢失。本文提出一种基于备份寄存器(Backup Registers)的无感恢复策略:在 LSE 停振瞬间,记录当前时间与补偿值,并在恢复后自动校准,确保时间连续性与准确性。

原理剖析

1. RTC 时钟源与备份域

STM32F4 的 RTC 由备份域供电(VBAT),其时钟源可选 LSE、LSI 或 HSE 分频。备份寄存器(BKP)位于备份域,共 20 个 32 位寄存器,可在 VDD 断电时由 VBAT 保持数据。当 LSE 停振时,RTC 时钟自动切换到 LSI(若已使能),但 LSI 频率误差大(典型 ±1.5%),导致时间漂移。

2. 无感恢复的核心思想

  • 检测停振:通过 RTC 中断(RTC_Alarm 或 RTC_WakeUp)或轮询 RTC_ISR 的 LSERDY 标志位。
  • 保存时间戳:在检测到停振时,立即将当前 RTC 时间(基于 LSI 的粗略值)和 LSE 停振前的精确时间差值存入备份寄存器。
  • 补偿机制:恢复 LSE 后,读取备份寄存器,计算停振期间的时间偏差,并调整 RTC 计数器。

3. 时间补偿算法

假设 LSE 停振前精确时间为 T0,停振后 LSI 运行,RTC 计数器继续累加。在恢复 LSE 时,读取当前 RTC 时间 T1(基于 LSI),并记录 LSI 运行时长 ΔT = T1 - T0。由于 LSI 频率偏差,实际时间偏差为 ΔT * (1 - f_lsi/f_lse)。通过备份寄存器存储 T0 和 ΔT,恢复后计算补偿值并写入 RTC 的 SubSecond 寄存器或直接调整时间。

配置步骤

1. 使能备份域访问与 LSE 监视

void RTC_Backup_Init(void) {
    // 使能 PWR 和 BKP 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE); // 允许访问备份域
    
    // 使能 LSE 并等待就绪
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    
    // 配置 RTC 时钟源为 LSE
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForSynchro();
}

2. 配置 RTC 中断检测 LSE 停振

void RTC_LSE_Monitor_Init(void) {
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能 RTC 全局中断(用于检测 LSE 就绪状态变化)
    RTC_ITConfig(RTC_IT_LSERDY, ENABLE);
    
    // 配置 EXTI 线 17(RTC 闹钟)或线 19(RTC 侵入检测),此处用线 17 演示
    EXTI_ClearITPendingBit(EXTI_Line17);
    EXTI_InitStructure.EXTI_Line = EXTI_Line17;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 检测 LSE 状态变化
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

3. 在中断服务函数中保存时间戳

void RTC_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_LSERDY) != RESET) {
        RTC_ClearITPendingBit(RTC_IT_LSERDY);
        
        // 检查 LSE 是否就绪(若为 RESET,说明停振)
        if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
            // 保存当前时间(基于 LSI)到备份寄存器 0
            uint32_t current_time = RTC_GetCounter();
            BKP_WriteBackupRegister(BKP_DR1, current_time);
            // 保存一个标志位,表示停振发生
            BKP_WriteBackupRegister(BKP_DR2, 0xA5A5);
        } else {
            // LSE 恢复,执行补偿
            if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) {
                uint32_t saved_time = BKP_ReadBackupRegister(BKP_DR1);
                uint32_t current_time = RTC_GetCounter();
                uint32_t elapsed = current_time - saved_time; // 停振期间 LSI 计数
                
                // 计算补偿值(假设 LSI 频率为 32kHz,LSE 为 32768Hz,偏差约 2.4%)
                // 实际应用中需根据 LSI 校准值调整
                uint32_t compensation = (uint32_t)(elapsed * 0.024); // 粗略补偿
                RTC_SetCounter(current_time - compensation); // 回拨时间
                
                // 清除标志
                BKP_WriteBackupRegister(BKP_DR2, 0x0000);
            }
        }
    }
}

4. 主程序中的无感恢复逻辑

int main(void) {
    // 初始化系统时钟、RTC 等
    RTC_Backup_Init();
    RTC_LSE_Monitor_Init();
    
    // 检查是否有停振恢复标志
    if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) {
        // 说明上次运行中发生了停振,但未恢复,可在此处理
        // 例如:记录日志或强制校准
    }
    
    while (1) {
        // 正常业务逻辑
        // 可定期检查 LSE 状态,若停振则执行保存操作
        if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
            // 立即保存当前时间,防止后续复位丢失
            BKP_WriteBackupRegister(BKP_DR1, RTC_GetCounter());
            BKP_WriteBackupRegister(BKP_DR2, 0xA5A5);
        }
        delay(1000);
    }
}

完整代码示例

以下为整合后的关键代码片段(基于标准外设库):

// 初始化备份域和 RTC
void RTC_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);
    
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForSynchro();
    
    // 设置 RTC 预分频,得到 1Hz 计数
    RTC_SetPrescaler(32767); // LSE 32768Hz,分频后 1Hz
    RTC_SetCounter(0); // 初始时间
    
    // 使能 LSE 就绪中断
    RTC_ITConfig(RTC_IT_LSERDY, ENABLE);
    // 配置 EXTI 线 17 和 NVIC(略)
}

// 中断处理中实现保存与补偿
void RTC_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_LSERDY) != RESET) {
        RTC_ClearITPendingBit(RTC_IT_LSERDY);
        if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
            // 停振:保存时间戳
            BKP_WriteBackupRegister(BKP_DR1, RTC_GetCounter());
            BKP_WriteBackupRegister(BKP_DR2, 0xA5A5);
        } else {
            // 恢复:补偿
            if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) {
                uint32_t saved = BKP_ReadBackupRegister(BKP_DR1);
                uint32_t now = RTC_GetCounter();
                uint32_t diff = now - saved;
                // 补偿系数:根据 LSI 实际频率调整,此处假设 2.4% 偏差
                uint32_t comp = (uint32_t)(diff * 0.024);
                RTC_SetCounter(now - comp);
                BKP_WriteBackupRegister(BKP_DR2, 0x0000);
            }
        }
    }
}

注意事项

  • 备份寄存器数量有限:仅 20 个,合理规划使用,避免冲突。
  • 补偿系数需实测:LSI 频率因芯片个体差异,建议在量产前校准,可存储校准值于备份寄存器。
  • 中断优先级:RTC 中断应设为较高优先级,确保及时响应停振事件。
  • 备份域复位:若 VBAT 掉电,备份寄存器数据丢失,需考虑外部 EEPROM 或 Flash 作为冗余。
  • 时间精度:本方案为线性补偿,适用于短时停振;长时间停振建议使用外部高精度 RTC 芯片。

结语

通过备份寄存器与 RTC 中断机制,STM32F4 能够在 LSE 停振时保存关键时间信息,并在恢复后自动补偿,实现无感恢复。该方案成本低、实现简单,适用于对时间连续性要求较高的嵌入式系统。开发者可根据实际需求调整补偿算法,进一步提升可靠性。