引言

在 STM32F4 系列(如 STM32F407、F429)嵌入式设计中,RTC(实时时钟)通常依赖 LSE(32.768kHz 外部晶振)提供精准时基。然而,LSE 可能因晶振质量、PCB 布局或环境干扰而停振,导致 RTC 计时失效,甚至阻塞系统启动(若启动代码强制等待 LSE 就绪)。本文提出一种利用 RTC 备份域寄存器(BKP)实现无外部晶振可靠启动的软件策略,适用于对启动鲁棒性要求高的工业与物联网场景。

原理剖析

LSE 停振的影响

  • RTC 计时中断:LSE 停振后,RTC 无法产生秒脉冲,时间戳冻结。
  • 启动阻塞:若 HAL 库或初始化代码使用 HAL_RCC_OscConfig() 并设置 HSEStartUpErr 或 LSE 超时,系统可能陷入死循环。
  • 备份域数据丢失风险:LSE 失效可能导致备份域复位(若配置不当),BKP 寄存器内容被清除。

备份域寄存器(BKP)的作用

STM32F4 的备份域包含 20 个 32 位数据寄存器(BKP_DR1~BKP_DR20),由 VBAT 供电,当主电源掉电时仍能保持数据。利用 BKP 寄存器可存储:

  • 启动标志:标记上次运行状态(如 LSE 是否正常)。
  • 时钟源选择:记录当前使用的时钟源(LSE 或 LSI)。
  • 校准参数:保存 RTC 预分频值,便于切换后快速恢复。

自恢复策略

  1. 检测 LSE 状态:读取 RTC 的 RTC_ISR 寄存器中的 LSERDY 位(或通过 RCC_BDCRLSERDY)。
  2. 切换时钟源:若 LSE 未就绪,自动切换至 LSI(约 32kHz,精度较低但可靠)。
  3. 保存状态到 BKP:在切换前,将当前时钟源标识和 RTC 配置写入 BKP 寄存器。
  4. 启动时恢复:上电后先读取 BKP 标志,若上次使用 LSI,则跳过 LSE 等待,直接配置 LSI 时钟。

配置步骤

1. 使能备份域访问

// 使能 PWR 和 BKP 时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();

// 允许访问备份域
HAL_PWR_EnableBkUpAccess();

2. 检测 LSE 状态并切换时钟源

uint8_t CheckAndSwitchClock(void) {
    uint32_t bdcr = RCC->BDCR;
    
    // 检查 LSE 就绪标志
    if (bdcr & RCC_BDCR_LSERDY) {
        // LSE 正常,使用 LSE
        return 0; // 0 表示 LSE
    } else {
        // LSE 停振,切换至 LSI
        RCC->BDCR |= RCC_BDCR_LSION; // 使能 LSI
        while (!(RCC->BDCR & RCC_BDCR_LSIRDY)); // 等待 LSI 就绪
        
        // 配置 RTC 时钟源为 LSI
        RCC->BDCR |= RCC_BDCR_RTCSEL_1; // 选择 LSI (RTCSEL[1:0] = 10)
        RCC->BDCR |= RCC_BDCR_RTCEN;     // 使能 RTC
        
        // 保存状态到 BKP
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); // 自定义标志
        BKP_WriteBackupRegister(BKP_DR2, 1);      // 1 表示 LSI
        return 1; // 1 表示 LSI
    }
}

3. 启动时恢复时钟配置

void RestoreClockConfig(void) {
    // 读取 BKP 标志
    uint32_t flag = BKP_ReadBackupRegister(BKP_DR1);
    uint32_t clk_src = BKP_ReadBackupRegister(BKP_DR2);
    
    if (flag == 0xA5A5 && clk_src == 1) {
        // 上次使用 LSI,直接配置 LSI 时钟
        RCC->BDCR |= RCC_BDCR_LSION;
        while (!(RCC->BDCR & RCC_BDCR_LSIRDY));
        RCC->BDCR |= RCC_BDCR_RTCSEL_1; // LSI
        RCC->BDCR |= RCC_BDCR_RTCEN;
    } else {
        // 默认尝试 LSE,但设置超时
        RCC->BDCR |= RCC_BDCR_LSEON;
        uint32_t timeout = 100000;
        while (!(RCC->BDCR & RCC_BDCR_LSERDY)) {
            if (--timeout == 0) {
                // 超时,切换 LSI
                CheckAndSwitchClock();
                break;
            }
        }
    }
}

4. 完整初始化示例

void SystemClock_Config_NoLSE(void) {
    // 使能备份域访问
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    
    // 恢复或检测时钟
    RestoreClockConfig();
    
    // 配置 RTC 预分频(假设 32.768kHz 或 32kHz)
    RTC_HandleTypeDef hrtc;
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127; // 异步分频
    hrtc.Init.SynchPrediv = 255;  // 同步分频(配合 LSI 32kHz)
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    if (HAL_RTC_Init(&hrtc) != HAL_OK) {
        Error_Handler();
    }
    
    // 后续可设置时间等
}

注意事项

  • 备份域复位:在写入 BKP 前,必须确保备份域未被复位(如检测 RCC_BDCRBDRST 位),否则数据丢失。
  • LSI 精度:LSI 频率在 17~47kHz 之间(典型 32kHz),温漂较大,若需高精度计时,建议定期校准或使用外部 RTC 芯片。
  • 电源管理:备份域由 VBAT 供电,若 VBAT 未接电池,主电源掉电后 BKP 数据丢失,需考虑使用 Flash 存储关键参数。
  • HAL 库兼容:若使用 HAL 库,HAL_RCC_OscConfig() 中的 RCC_OscInitStruct.LSEState 可设置为 RCC_LSE_OFF 以跳过等待,但需手动管理时钟源切换。
  • 测试验证:在实际硬件上模拟 LSE 停振(如断开晶振),验证系统能自动切换并正常启动,且 BKP 标志正确更新。

总结

通过利用 STM32F4 的 RTC 备份域寄存器,我们实现了 LSE 停振后的自恢复启动机制。该方法避免了启动死锁,保证了系统在无外部晶振时的可用性,同时为后续维护提供了状态记录。对于嵌入式开发者而言,理解备份域特性并灵活运用,是提升系统鲁棒性的重要技能。在实际项目中,建议结合看门狗和错误日志,构建更完善的容错体系。