引言

在嵌入式系统中,STM32F4 系列微控制器凭借高性能和丰富外设广受欢迎。RTC(实时时钟)通常依赖外部 32.768kHz 晶振(LSE)保持时间,但 LSE 可能因焊接不良、温漂或干扰而停振。若系统启动时强制等待 LSE 就绪,将导致死锁或启动失败。本文介绍一种策略:利用 RTC 备份寄存器(BKP)存储启动标志,在 LSE 停振时自动切换到内部低速时钟(LSI),实现无外部晶振的可靠启动。

原理分析

LSE 与 LSI 的差异

  • LSE:外部低速晶振,频率 32.768kHz,精度高(±20ppm),但需要外部电路,可能失效。
  • LSI:内部低速 RC 振荡器,频率约 32kHz(STM32F4 典型值),精度较低(±5%),但无需外部元件,始终可用。

RTC 备份寄存器(BKP)

  • STM32F4 的 RTC 域包含 20 个 32 位备份寄存器(BKP0~BKP19),位于 VDD 备份域,由 VBAT 供电。
  • 这些寄存器在系统复位、电源复位或从待机模式唤醒时保持内容,可用于存储关键标志。
  • 访问 BKP 需使能 PWR 和 RTC 时钟,并设置备份域写保护。

启动策略

  • 系统启动时,先检查 BKP 中的标志(如 0xA5A5)。
  • 若标志有效,说明上次运行正常,直接使用 LSI 启动 RTC,跳过 LSE 等待。
  • 若标志无效(首次启动或备份域丢失),尝试启用 LSE,若 LSE 超时未就绪,则切换至 LSI,并在 BKP 中写入有效标志。
  • 这样即使 LSE 永久失效,系统也能快速启动,且 RTC 仍能运行(精度略降)。

配置步骤

  1. 使能时钟:启用 PWR 和 RTC 时钟,并设置备份域访问。
  2. 选择 RTC 时钟源:在 RTC 初始化前,通过 RCC 选择 LSE 或 LSI。
  3. 初始化 RTC:配置预分频器、中断等。
  4. 操作 BKP:写入/读取标志,注意写保护。
  5. 启动逻辑:根据标志和 LSE 状态决定时钟源。

完整代码示例

以下代码基于 STM32F4xx HAL 库,演示核心逻辑。

#include "stm32f4xx_hal.h"

#define BKP_FLAG_ADDR 0x0000  // 使用 BKP0 寄存器
#define BKP_FLAG_VALUE 0xA5A5

void SystemClock_Config(void);
void RTC_Init(void);
void RTC_Backup_Write(uint32_t reg, uint32_t data);
uint32_t RTC_Backup_Read(uint32_t reg);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    // 使能 PWR 和 RTC 时钟
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_RTC_ENABLE();
    
    // 允许访问备份域
    HAL_PWR_EnableBkUpAccess();
    
    // 检查 BKP 标志
    uint32_t flag = RTC_Backup_Read(BKP_FLAG_ADDR);
    
    if (flag != BKP_FLAG_VALUE) {
        // 首次启动或备份域丢失,尝试 LSE
        RCC_OscInitTypeDef osc_init = {0};
        osc_init.OscillatorType = RCC_OSCILLATORTYPE_LSE;
        osc_init.LSEState = RCC_LSE_ON;
        osc_init.PLL.PLLState = RCC_PLL_NONE;
        
        if (HAL_RCC_OscConfig(&osc_init) != HAL_OK) {
            // LSE 启动失败,切换到 LSI
            osc_init.OscillatorType = RCC_OSCILLATORTYPE_LSI;
            osc_init.LSIState = RCC_LSI_ON;
            if (HAL_RCC_OscConfig(&osc_init) != HAL_OK) {
                Error_Handler();
            }
            __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
        } else {
            __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
        }
        
        // 初始化 RTC
        RTC_Init();
        
        // 写入有效标志
        RTC_Backup_Write(BKP_FLAG_ADDR, BKP_FLAG_VALUE);
    } else {
        // 标志有效,直接使用 LSI(假设 LSE 可能失效)
        RCC_OscInitTypeDef osc_init = {0};
        osc_init.OscillatorType = RCC_OSCILLATORTYPE_LSI;
        osc_init.LSIState = RCC_LSI_ON;
        if (HAL_RCC_OscConfig(&osc_init) != HAL_OK) {
            Error_Handler();
        }
        __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
        RTC_Init();
    }
    
    // 主循环
    while (1) {
        // 应用逻辑
    }
}

void RTC_Init(void) {
    RTC_TimeTypeDef sTime = {0};
    RTC_DateTypeDef sDate = {0};
    
    __HAL_RCC_RTC_ENABLE();
    
    RTC_HandleTypeDef hrtc = {0};
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 0x7F;  // 根据时钟源调整
    hrtc.Init.SynchPrediv = 0xFF;   // 例如 LSI 32kHz: 128*256=32768
    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();
    }
    
    // 设置时间(示例)
    sTime.Hours = 12;
    sTime.Minutes = 0;
    sTime.Seconds = 0;
    if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
        Error_Handler();
    }
}

void RTC_Backup_Write(uint32_t reg, uint32_t data) {
    // 确保备份域可写
    HAL_PWR_EnableBkUpAccess();
    // 写 BKP 寄存器(注意:STM32F4 的 BKP 寄存器映射到 RTC 域)
    // 使用 HAL 库函数或直接操作寄存器
    // 例如:RTC->BKP0R = data; 但需确认具体寄存器
    // 这里用宏定义简化
    *(__IO uint32_t *)(RTC_BASE + 0x50 + reg * 4) = data;
}

uint32_t RTC_Backup_Read(uint32_t reg) {
    return *(__IO uint32_t *)(RTC_BASE + 0x50 + reg * 4);
}

void Error_Handler(void) {
    while (1) {
        // 错误处理
    }
}

注意事项

  • 备份域写保护:操作 BKP 前必须调用 HAL_PWR_EnableBkUpAccess(),否则写入无效。
  • 时钟源切换:切换 RTC 时钟源前,需确保 RTC 已禁用(__HAL_RTC_DISABLE),否则可能导致不可预知行为。
  • 预分频器配置:LSI 频率不精确,需根据实际频率调整预分频值,否则 RTC 时间误差较大。
  • VBAT 供电:BKP 寄存器依赖 VBAT 供电,若 VBAT 断开,标志会丢失,系统将重新尝试 LSE。
  • 启动超时:LSE 启动超时时间建议设置为 1~2 秒,避免长时间阻塞。
  • 调试:在调试时,可通过 RTC 中断或状态寄存器确认时钟源是否就绪。

总结

通过 RTC 备份寄存器存储启动标志,STM32F4 可以在 LSE 停振时快速切换到 LSI,避免启动死锁,同时保持 RTC 功能。该方法简单可靠,适用于对时间精度要求不高的工业或消费类应用。开发者可根据实际需求调整标志位和时钟源策略,进一步提升系统鲁棒性。