# 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 初始化备份域访问 ```c void BKP_Enable(void) { __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 解除备份域写保护 } ``` ### 4.2 LSE 检测与切换逻辑 ```c 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 正常时调用) ```c 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 为例) ```c 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 中断服务函数 ```c 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 检测或外部高精度时钟源。