# 引言 在嵌入式系统中,RTC(实时时钟)常依赖外部 32.768kHz 晶振(LSE)提供精准时基。然而,LSE 可能因温度漂移、振动或老化而停振。STM32F4 系列在 LSE 失效时,RTC 会切换至 LSI(约 32kHz,精度差),导致时间误差累积。更严重的是,若系统复位,RTC 时间可能丢失。本文提出一种基于备份寄存器(Backup Registers)的无感恢复策略:在 LSE 停振瞬间,记录当前时间与补偿值,并在恢复后自动校准,确保时间连续性与准确性。 # 原理剖析 ## 1. RTC 时钟源与备份域 STM32F4 的 RTC 由备份域供电(VBAT),其时钟源可选 LSE、LSI 或 HSE 分频。备份寄存器(BKP)位于备份域,共 20 个 32 位寄存器,可在 VDD 断电时由 VBAT 保持数据。当 LSE 停振时,RTC 时钟自动切换到 LSI(若已使能),但 LSI 频率误差大(典型 ±1.5%),导致时间漂移。 ## 2. 无感恢复的核心思想 - **检测停振**:通过 RTC 中断(RTC_Alarm 或 RTC_WakeUp)或轮询 RTC_ISR 的 LSERDY 标志位。 - **保存时间戳**:在检测到停振时,立即将当前 RTC 时间(基于 LSI 的粗略值)和 LSE 停振前的精确时间差值存入备份寄存器。 - **补偿机制**:恢复 LSE 后,读取备份寄存器,计算停振期间的时间偏差,并调整 RTC 计数器。 ## 3. 时间补偿算法 假设 LSE 停振前精确时间为 T0,停振后 LSI 运行,RTC 计数器继续累加。在恢复 LSE 时,读取当前 RTC 时间 T1(基于 LSI),并记录 LSI 运行时长 ΔT = T1 - T0。由于 LSI 频率偏差,实际时间偏差为 ΔT * (1 - f_lsi/f_lse)。通过备份寄存器存储 T0 和 ΔT,恢复后计算补偿值并写入 RTC 的 SubSecond 寄存器或直接调整时间。 # 配置步骤 ## 1. 使能备份域访问与 LSE 监视 ```c void RTC_Backup_Init(void) { // 使能 PWR 和 BKP 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 允许访问备份域 // 使能 LSE 并等待就绪 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 配置 RTC 时钟源为 LSE RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); } ``` ## 2. 配置 RTC 中断检测 LSE 停振 ```c void RTC_LSE_Monitor_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能 RTC 全局中断(用于检测 LSE 就绪状态变化) RTC_ITConfig(RTC_IT_LSERDY, ENABLE); // 配置 EXTI 线 17(RTC 闹钟)或线 19(RTC 侵入检测),此处用线 17 演示 EXTI_ClearITPendingBit(EXTI_Line17); EXTI_InitStructure.EXTI_Line = EXTI_Line17; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 检测 LSE 状态变化 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } ``` ## 3. 在中断服务函数中保存时间戳 ```c void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_LSERDY) != RESET) { RTC_ClearITPendingBit(RTC_IT_LSERDY); // 检查 LSE 是否就绪(若为 RESET,说明停振) if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { // 保存当前时间(基于 LSI)到备份寄存器 0 uint32_t current_time = RTC_GetCounter(); BKP_WriteBackupRegister(BKP_DR1, current_time); // 保存一个标志位,表示停振发生 BKP_WriteBackupRegister(BKP_DR2, 0xA5A5); } else { // LSE 恢复,执行补偿 if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) { uint32_t saved_time = BKP_ReadBackupRegister(BKP_DR1); uint32_t current_time = RTC_GetCounter(); uint32_t elapsed = current_time - saved_time; // 停振期间 LSI 计数 // 计算补偿值(假设 LSI 频率为 32kHz,LSE 为 32768Hz,偏差约 2.4%) // 实际应用中需根据 LSI 校准值调整 uint32_t compensation = (uint32_t)(elapsed * 0.024); // 粗略补偿 RTC_SetCounter(current_time - compensation); // 回拨时间 // 清除标志 BKP_WriteBackupRegister(BKP_DR2, 0x0000); } } } } ``` ## 4. 主程序中的无感恢复逻辑 ```c int main(void) { // 初始化系统时钟、RTC 等 RTC_Backup_Init(); RTC_LSE_Monitor_Init(); // 检查是否有停振恢复标志 if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) { // 说明上次运行中发生了停振,但未恢复,可在此处理 // 例如:记录日志或强制校准 } while (1) { // 正常业务逻辑 // 可定期检查 LSE 状态,若停振则执行保存操作 if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { // 立即保存当前时间,防止后续复位丢失 BKP_WriteBackupRegister(BKP_DR1, RTC_GetCounter()); BKP_WriteBackupRegister(BKP_DR2, 0xA5A5); } delay(1000); } } ``` # 完整代码示例 以下为整合后的关键代码片段(基于标准外设库): ```c // 初始化备份域和 RTC void RTC_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); // 设置 RTC 预分频,得到 1Hz 计数 RTC_SetPrescaler(32767); // LSE 32768Hz,分频后 1Hz RTC_SetCounter(0); // 初始时间 // 使能 LSE 就绪中断 RTC_ITConfig(RTC_IT_LSERDY, ENABLE); // 配置 EXTI 线 17 和 NVIC(略) } // 中断处理中实现保存与补偿 void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_LSERDY) != RESET) { RTC_ClearITPendingBit(RTC_IT_LSERDY); if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { // 停振:保存时间戳 BKP_WriteBackupRegister(BKP_DR1, RTC_GetCounter()); BKP_WriteBackupRegister(BKP_DR2, 0xA5A5); } else { // 恢复:补偿 if (BKP_ReadBackupRegister(BKP_DR2) == 0xA5A5) { uint32_t saved = BKP_ReadBackupRegister(BKP_DR1); uint32_t now = RTC_GetCounter(); uint32_t diff = now - saved; // 补偿系数:根据 LSI 实际频率调整,此处假设 2.4% 偏差 uint32_t comp = (uint32_t)(diff * 0.024); RTC_SetCounter(now - comp); BKP_WriteBackupRegister(BKP_DR2, 0x0000); } } } } ``` # 注意事项 - **备份寄存器数量有限**:仅 20 个,合理规划使用,避免冲突。 - **补偿系数需实测**:LSI 频率因芯片个体差异,建议在量产前校准,可存储校准值于备份寄存器。 - **中断优先级**:RTC 中断应设为较高优先级,确保及时响应停振事件。 - **备份域复位**:若 VBAT 掉电,备份寄存器数据丢失,需考虑外部 EEPROM 或 Flash 作为冗余。 - **时间精度**:本方案为线性补偿,适用于短时停振;长时间停振建议使用外部高精度 RTC 芯片。 # 结语 通过备份寄存器与 RTC 中断机制,STM32F4 能够在 LSE 停振时保存关键时间信息,并在恢复后自动补偿,实现无感恢复。该方案成本低、实现简单,适用于对时间连续性要求较高的嵌入式系统。开发者可根据实际需求调整补偿算法,进一步提升可靠性。