STM32F4 无晶振秒中断:LSE 停振后利用 RTC 备份域寄存器的可靠实现

1. 问题背景与挑战

在工业控制或低功耗设备中,STM32F4 常依赖 LSE(32.768kHz 晶振)驱动 RTC 产生秒中断。然而,LSE 可能因焊接不良、环境振动或温度漂移而停振。一旦停振,RTC 时钟源失效,秒中断消失,系统时间基准崩溃。传统方案是直接切换至 LSI(内部低速 RC,约 32kHz),但 LSI 精度差(典型 ±5%),导致秒中断误差累积。更棘手的是,RTC 的时钟源选择寄存器(RCC_BDCR)位于备份域,一旦 LSE 停振,写操作可能被锁定。

本文提出一种策略:利用 RTC 备份域寄存器(BKPxR)保存 LSI 校准参数,并在 LSE 停振后自动切换至 LSI,同时通过备份寄存器记录切换状态,实现无外部晶振的可靠秒中断。该方案无需额外硬件,仅依赖 STM32F4 内置资源。

2. 原理剖析:RTC 备份域与时钟源管理

2.1 备份域架构

STM32F4 的备份域包括 RTC、备份寄存器(20 个 32 位 BKPxR)和备份 SRAM(4KB,仅 F4 系列部分型号)。这些资源由 VBAT 供电,当主电源掉电时仍保持数据。RTC 时钟源可选 HSE/128、LSI 或 LSE,通过 RCC_BDCR 寄存器的 RTCSEL[1:0] 位配置。

2.2 LSE 停振检测

LSE 停振可通过 RCC_BDCR 的 LSERDY 位监测。当该位为 0 时,表示 LSE 未就绪。但仅靠轮询不够,因为 LSE 可能瞬间抖动。更可靠的方法是启用 LSE 的旁路模式(LSEBYP=1),将 LSE 引脚配置为外部时钟输入,但本文聚焦无外部晶振场景,故采用软件检测:连续多次读取 LSERDY,若持续为 0 则判定停振。

2.3 关键寄存器操作

  • RCC_BDCR:备份域控制寄存器,需先解除写保护(PWR_CR 的 DBP 位)。
  • RTC_CR:RTC 控制寄存器,包含时钟源选择(通过 RTCSEL 在 RCC_BDCR 中)和中断使能。
  • BKPxR:备份寄存器,用于存储校准值和状态标志。

3. 实现方案设计

3.1 总体流程

  1. 系统初始化时,尝试启用 LSE,并等待就绪。
  2. 若 LSE 就绪,则使用 LSE 作为 RTC 时钟,并周期性测量 LSI 频率(通过定时器或 RTC 校准),将校准系数存入 BKP0R。
  3. 若 LSE 未就绪或运行中停振,则切换 RTC 时钟源至 LSI,并从 BKP0R 读取校准系数,用于软件补偿秒中断周期。
  4. 在备份寄存器 BKP1R 中记录当前时钟源状态(0=LSE,1=LSI)。

3.2 校准原理

LSI 频率不精确,但可通过测量其相对 LSE 的偏差来校准。当 LSE 正常时,利用 RTC 的亚秒寄存器或外部定时器,测量 LSI 在 1 秒内的实际计数,得到校准因子。例如,若 LSI 实际频率为 32.1kHz,则 1 秒计数为 32100,而理想为 32000,校准因子为 32100/32000=1.003125。切换至 LSI 后,每次秒中断产生时,软件调整下一次中断的预分频值。

4. 配置步骤与代码实现

4.1 初始化备份域访问

void BKP_Enable(void) {
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess(); // 解除备份域写保护
}

4.2 LSE 检测与切换逻辑

uint8_t CheckAndSwitchRTCClock(void) {
    // 检查 LSE 状态
    if (READ_BIT(RCC->BDCR, RCC_BDCR_LSERDY) == 0) {
        // LSE 停振,切换至 LSI
        RCC->BDCR &= ~RCC_BDCR_RTCSEL; // 清除选择位
        RCC->BDCR |= RCC_BDCR_RTCSEL_1; // 选择 LSI (RTCSEL=10)
        
        // 读取校准系数(若存在)
        uint32_t calib = BKP->BKP0R;
        if (calib != 0xFFFFFFFF) {
            // 应用校准系数到 RTC 预分频器
            RTC->PRER = (RTC->PRER & ~RTC_PRER_PREDIV_S) | ((calib & 0xFFFF) << 16);
        }
        
        // 记录状态
        BKP->BKP1R = 1;
        return 1;
    } else {
        // LSE 正常,使用 LSE,并校准 LSI
        CalibrateLSI();
        BKP->BKP1R = 0;
        return 0;
    }
}

4.3 LSI 校准函数(在 LSE 正常时调用)

void CalibrateLSI(void) {
    // 使用 TIM5 测量 LSI 频率(假设 TIM5 时钟来自 LSE,或使用 RTC 亚秒)
    // 简化示例:利用 RTC 的 1Hz 中断,计数 LSI 的脉冲数
    // 实际实现需配置 TIM 输入捕获或使用 LSI 作为 TIM 时钟
    // 此处仅示意,假设测得频率为 32000 + offset
    uint32_t measured_freq = 32100; // 示例值
    uint32_t prediv_s = (measured_freq / 2) - 1; // 异步预分频,产生 1Hz
    BKP->BKP0R = prediv_s; // 保存校准值
}

4.4 秒中断配置(以 LSI 为例)

void RTC_Init_With_LSI(void) {
    // 使能 PWR 和备份域
    BKP_Enable();
    
    // 使能 LSI
    RCC->CSR |= RCC_CSR_LSION;
    while ((RCC->CSR & RCC_CSR_LSIRDY) == 0);
    
    // 选择 LSI 作为 RTC 时钟
    RCC->BDCR |= RCC_BDCR_RTCSEL_1; // RTCSEL=10
    RCC->BDCR |= RCC_BDCR_RTCEN; // 使能 RTC
    
    // 等待 RTC 同步
    RTC->ISR |= RTC_ISR_INIT;
    while ((RTC->ISR & RTC_ISR_INITF) == 0);
    
    // 配置预分频:LSI 约 32kHz,异步预分频 127,同步预分频 255,得到 1Hz
    RTC->PRER = (127 << 16) | 255;
    
    // 退出初始化模式
    RTC->ISR &= ~RTC_ISR_INIT;
    
    // 使能秒中断
    RTC->CR |= RTC_CR_TSIE;
    NVIC_EnableIRQ(RTC_Alarm_IRQn); // 或 RTC_WKUP_IRQn,取决于配置
}

4.5 中断服务函数

void RTC_Alarm_IRQHandler(void) {
    if (RTC->ISR & RTC_ISR_TSF) {
        RTC->ISR &= ~RTC_ISR_TSF; // 清除标志
        // 秒中断处理:可读取 BKP1R 判断时钟源,并做时间补偿
        uint8_t src = BKP->BKP1R;
        if (src == 1) {
            // LSI 模式,可进行软件补偿(例如调整下一次中断时间)
        }
        // 用户代码:翻转 LED 等
    }
}

5. 注意事项与优化建议

  • 备份域写保护:任何对 RCC_BDCR 或 BKPxR 的写操作前,必须设置 PWR_CR 的 DBP=1,否则写无效。
  • LSE 抖动处理:检测 LSE 停振时,建议连续读取 LSERDY 至少 10 次,每次间隔 1ms,避免误判。
  • 校准系数更新:在 LSE 正常时,定期(如每小时)重新校准 LSI,并更新 BKP0R,以应对温度漂移。
  • 备份寄存器数量:BKP0R 和 BKP1R 是 32 位,但只有低 16 位有效(部分型号),注意掩码。
  • 低功耗模式:若进入待机模式,备份域保持供电,但 LSI 可能关闭,需在唤醒后重新初始化。
  • 调试建议:使用逻辑分析仪观察秒中断引脚,对比 LSE 和 LSI 模式下的周期误差。

6. 总结

本文通过利用 STM32F4 的备份域寄存器,实现了 LSE 停振后无缝切换至 LSI 的秒中断方案。核心在于提前校准 LSI 并存储参数,切换时快速恢复精度。该方法无需外部晶振,成本低且可靠性高,适用于对时间精度要求不极端(误差 <1%)的嵌入式应用。开发者可根据实际需求扩展校准算法,例如使用 RTC 的 tamper 检测或外部高精度时钟源。