# 引言 在工业控制、物联网设备等场景中,RTC(实时时钟)是系统时间基准的核心。STM32F4 系列通常采用 32.768kHz 的 LSE 晶振来驱动 RTC,以获得高精度。然而,LSE 可能因晶振老化、外部干扰或焊接问题而停振。一旦停振,RTC 将失去时钟源,导致时间停滞,若未处理,复位后时间将重置为默认值,造成严重后果。 本文提出一种基于备份域寄存器的“无感恢复”方案:当检测到 LSE 停振时,自动切换至内部 LSI(约 32kHz)继续计数,同时将当前时间与补偿偏移量保存至备份寄存器;待 LSE 恢复后,利用保存的参数重新校准时间,实现系统无感知的时间连续性。 # 原理剖析 ## 1. RTC 时钟源与备份域 STM32F4 的 RTC 支持三种时钟源:LSE、LSI、HSE(分频)。其中,LSE 和 LSI 均位于备份域(VBAT 供电区域),即使主电源断电,只要 VBAT 有电,RTC 仍可运行。备份域包含 RTC 寄存器、备份寄存器(BKP)以及相关的控制逻辑。 - **LSE**:外部低速晶振,频率 32.768kHz,精度高(典型 ±20ppm)。 - **LSI**:内部低速 RC 振荡器,频率约 32kHz,精度低(典型 ±5%),但无需外部元件。 ## 2. LSE 停振检测 STM32F4 的 RTC 提供时钟安全系统(CSS)功能,专门用于监测 LSE。当 LSE 停振时,CSS 会产生一个中断(RTC_CSS_IRQn),通知软件立即处理。在中断服务函数中,我们可以读取 RTC->ISR 的 CSSF 标志位确认事件。 ## 3. 无感恢复策略 核心思想: - **停振期间**:切换到 LSI 作为 RTC 时钟源,保证计数不中断。 - **时间补偿**:由于 LSI 频率偏差大,在切换时记录当前时间戳(基于 LSE),并计算一个补偿系数(例如,每秒钟 LSI 计数与理想 32768 的差值)。 - **恢复后校准**:当 LSE 恢复(通过定时检测或外部事件),利用备份寄存器中的时间戳和补偿系数,重新计算实际时间,并切换回 LSE。 # 配置步骤 ## 1. 使能备份域访问 ```c // 使能 PWR 和 RCC 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE); // 若使用备份SRAM // 使能备份域写访问 PWR_BackupAccessCmd(ENABLE); ``` ## 2. 配置 RTC 时钟源与中断 ```c // 选择 LSE 作为 RTC 时钟源 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待就绪 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); // 使能 CSS 中断 RTC_CSSCmd(ENABLE); RTC_ITConfig(RTC_IT_CSS, ENABLE); // 配置 NVIC NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_CSS_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); ``` ## 3. 初始化 RTC 时间 ```c RTC_InitTypeDef RTC_InitStructure; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStructure.RTC_AsynchPrediv = 0x7F; // 异步分频 127 RTC_InitStructure.RTC_SynchPrediv = 0xFF; // 同步分频 255 RTC_Init(&RTC_InitStructure); // 设置时间(例如 2025-01-01 00:00:00) RTC_TimeTypeDef time = {0}; time.RTC_Hours = 0; time.RTC_Minutes = 0; time.RTC_Seconds = 0; RTC_SetTime(RTC_Format_BIN, &time); ``` ## 4. 备份寄存器读写函数 ```c // 写备份寄存器(32位) void BKP_WriteReg(uint32_t reg, uint32_t value) { PWR_BackupAccessCmd(ENABLE); BKP_WriteBackupRegister(reg, value); PWR_BackupAccessCmd(DISABLE); } // 读备份寄存器 uint32_t BKP_ReadReg(uint32_t reg) { return BKP_ReadBackupRegister(reg); } ``` # 完整代码示例 ```c // 全局变量 volatile uint8_t lse_failure = 0; volatile uint32_t lse_stop_tick = 0; // 停振时的 RTC 计数(基于 LSE) volatile uint32_t lsi_compensation = 0; // 补偿系数 // RTC CSS 中断服务函数 void RTC_CSS_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_CSS) != RESET) { RTC_ClearITPendingBit(RTC_IT_CSS); // 1. 记录当前时间(基于 LSE 的计数) lse_stop_tick = RTC_GetCounter(); // 2. 切换到 LSI 时钟源 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); // 3. 等待 LSI 就绪并重新初始化 RTC 分频(LSI 频率约 32kHz) while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); RTC_InitTypeDef RTC_InitStructure; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStructure.RTC_AsynchPrediv = 0x7F; RTC_InitStructure.RTC_SynchPrediv = 0xFF; RTC_Init(&RTC_InitStructure); // 4. 设置当前时间为停振时刻(保持连续) RTC_SetCounter(lse_stop_tick); // 5. 计算补偿系数:理想情况下 1 秒 = 32768 个 LSI 周期,但实际可能偏差 // 这里假设 LSI 实际频率为 32000 Hz,则补偿系数 = 32768 - 32000 = 768 // 实际应用中可通过测量 LSI 频率获得,此处示例固定值 lsi_compensation = 768; // 每秒钟需要补偿的计数 // 6. 保存到备份寄存器 BKP_WriteReg(BKP_DR1, lse_stop_tick); BKP_WriteReg(BKP_DR2, lsi_compensation); lse_failure = 1; } } // 主循环中定期检查 LSE 是否恢复(例如每 10ms 检查一次) void Check_LSE_Recovery(void) { if (lse_failure) { // 尝试重新使能 LSE RCC_LSEConfig(RCC_LSE_ON); uint32_t timeout = 0xFFFF; while (timeout-- && RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET) { // LSE 恢复,进行时间补偿 uint32_t current_lsi_tick = RTC_GetCounter(); uint32_t elapsed_ticks = current_lsi_tick - lse_stop_tick; uint32_t compensated_ticks = elapsed_ticks + (elapsed_ticks * lsi_compensation) / 32768; // 切换回 LSE 时钟源 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 重新初始化 RTC 分频(LSE 频率 32768) RTC_InitTypeDef RTC_InitStructure; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStructure.RTC_AsynchPrediv = 0x7F; RTC_InitStructure.RTC_SynchPrediv = 0xFF; RTC_Init(&RTC_InitStructure); // 设置补偿后的时间 RTC_SetCounter(lse_stop_tick + compensated_ticks); // 清除标志 lse_failure = 0; BKP_WriteReg(BKP_DR1, 0); // 清除备份寄存器 BKP_WriteReg(BKP_DR2, 0); } } } int main(void) { // 系统初始化... // 读取备份寄存器,判断是否有未完成的补偿 uint32_t saved_tick = BKP_ReadReg(BKP_DR1); uint32_t saved_comp = BKP_ReadReg(BKP_DR2); if (saved_tick != 0 && saved_comp != 0) { // 上次异常掉电,恢复时进行补偿 lse_stop_tick = saved_tick; lsi_compensation = saved_comp; lse_failure = 1; // 强制切换到 LSI 并继续,等待 LSE 恢复 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); RTC_InitTypeDef RTC_InitStructure; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStructure.RTC_AsynchPrediv = 0x7F; RTC_InitStructure.RTC_SynchPrediv = 0xFF; RTC_Init(&RTC_InitStructure); RTC_SetCounter(saved_tick); } while (1) { Check_LSE_Recovery(); // 其他任务 } } ``` # 注意事项 - **备份域电源**:确保 VBAT 引脚连接到电池或大电容,否则备份域寄存器在掉电后丢失,无法实现恢复。 - **LSI 频率校准**:补偿系数需根据实际 LSI 频率测量,可在系统启动时通过定时器测量 LSI 周期,动态计算,提高精度。 - **中断优先级**:CSS 中断应设置为高优先级,确保及时响应,避免时间误差积累。 - **时钟切换时机**:切换时钟源时,RTC 会短暂停止,需在切换前记录当前计数,切换后恢复,以保证连续性。 - **备份寄存器数量**:STM32F4 有 20 个 32 位备份寄存器,可存储更多状态,如时间戳、补偿系数、校验和等。 - **测试建议**:在实际硬件上模拟 LSE 停振(如断开晶振),验证恢复逻辑,并观察时间误差。 # 结语 通过上述方案,STM32F4 在 LSE 停振时能够无缝切换至 LSI,并利用备份域寄存器保存关键参数,实现时间补偿与无感恢复。该方法不仅提高了系统可靠性,也为其他类似时钟问题提供了参考思路。开发者可根据实际需求调整补偿算法,例如使用卡尔曼滤波或温度补偿,进一步提升精度。