# 引言 STM32F4 系列的 RTC 通常依赖外部低速晶振(LSE,32.768kHz)以保持低功耗高精度计时。然而,LSE 在恶劣环境(如高低温冲击、振动)或晶振老化下可能停振,导致 RTC 时间停滞,进而影响系统日志、通信协议或定时任务。传统方案是直接切换至内部低速 RC(LSI),但 LSI 精度差(±5%),且切换过程可能造成时间跳变。本文提出一种“无痕热切换”策略:利用备份域寄存器保存时间上下文,在 LSE 停振瞬间无缝切换至 LSI,并通过软件补偿维持时间精度,实现系统无感知的时钟接管。 # 原理剖析 ## 1. 备份域结构 STM32F4 的备份域(Backup Domain)包含: - **RTC 寄存器**:实时时钟核心,支持 LSE 或 LSI 作为时钟源。 - **备份寄存器(BKP)**:20 个 32 位寄存器(BKP0R~BKP19R),在 VBAT 供电下可保持数据。 - **备份域控制寄存器**:控制 RTC 使能、时钟源选择及复位。 关键点:备份域独立于主电源,由 VBAT 引脚供电。当 VDD 掉电时,备份域仍可工作,但 LSE 和 LSI 均可能失效(取决于供电设计)。 ## 2. LSE 停振检测 RTC 提供时钟安全系统(CSS)功能,专用于 LSE 监测。当 LSE 停振时,CSS 会触发 RTC_CSS 中断(或事件),并自动将 RTC 时钟源切换至 LSI。但默认切换后,RTC 计数会基于 LSI 继续运行,但时间精度下降,且无补偿机制。 ## 3. 热切换策略 我们的策略分为三步: 1. **预存时间戳**:在系统正常运行时,周期性将当前时间(如 Unix 时间戳)写入备份寄存器(例如 BKP0R)。 2. **检测与切换**:使能 CSS 中断,在中断服务函数中记录切换时刻,并立即读取 RTC 当前计数(基于 LSI)作为基准。 3. **软件补偿**:切换后,利用备份寄存器中的时间戳和切换时刻的 RTC 计数,通过软件定时器(如 SysTick)周期性校准时间,补偿 LSI 的频率偏差。 # 配置步骤 ## 1. 使能备份域时钟与访问 ```c // 使能 PWR 和 RCC 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE); // 若使用备份SRAM // 允许访问备份域 PWR_BackupAccessCmd(ENABLE); // 复位备份域(可选,首次配置时) RCC_BackupResetCmd(ENABLE); RCC_BackupResetCmd(DISABLE); ``` ## 2. 配置 LSE 并启用 CSS ```c RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待就绪 // 选择 LSE 作为 RTC 时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 使能 RTC 时钟 RCC_RTCCLKCmd(ENABLE); // 等待 RTC 寄存器同步 RTC_WaitForSynchro(); // 使能 CSS 中断(在 NVIC 中配置) RTC_CSSConfig(ENABLE); ``` ## 3. 初始化 RTC 并设置时间 ```c RTC_InitTypeDef RTC_InitStruct; RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStruct.RTC_AsynchPrediv = 0x7F; // 异步分频 128 RTC_InitStruct.RTC_SynchPrediv = 0xFF; // 同步分频 256,得到 1Hz RTC_Init(RTC_InitStruct); // 设置时间(示例: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); // 保存初始时间戳到备份寄存器 uint32_t timestamp = get_unix_timestamp(); // 自定义函数 BKP_WriteBackupRegister(BKP_DR1, timestamp); ``` ## 4. 编写 CSS 中断服务函数 ```c void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_CSS) != RESET) { RTC_ClearITPendingBit(RTC_IT_CSS); // 记录切换时刻的 RTC 计数(基于 LSI) uint32_t switch_sec = RTC_GetCounter(); // 若使用日历模式,则读取时间并转换 // 保存切换标志和时间到备份寄存器 BKP_WriteBackupRegister(BKP_DR2, switch_sec); BKP_WriteBackupRegister(BKP_DR3, 0x5A5A); // 标志 // 可选:切换至 LSI 并重新配置分频器(CSS 已自动切换,但需确认) // 通常 CSS 已自动切换,无需手动操作 // 启动软件补偿定时器(如 SysTick) SysTick_Config(SystemCoreClock / 1000); // 1ms 中断 } } ``` ## 5. 软件补偿实现 在 SysTick 中断中,每 1 秒(或 10 秒)校准一次时间: ```c volatile uint32_t tick_count = 0; volatile uint32_t last_sec = 0; void SysTick_Handler(void) { tick_count++; if (tick_count >= 1000) { // 1 秒 tick_count = 0; // 读取当前 RTC 计数(基于 LSI) uint32_t current_sec = RTC_GetCounter(); // 计算偏差:假设 LSI 实际频率为 f_lsi,理想为 32kHz,但实际可能偏差 // 通过备份寄存器中的基准时间戳和切换时刻,计算补偿值 uint32_t base_ts = BKP_ReadBackupRegister(BKP_DR1); uint32_t switch_sec = BKP_ReadBackupRegister(BKP_DR2); uint32_t elapsed = current_sec - switch_sec; // 经过的秒数(基于LSI) uint32_t real_elapsed = (tick_count / 1000) + (base_ts - switch_sec); // 简化,实际需用系统时钟 // 这里仅演示,实际应使用系统时钟(如 TIM)来测量真实时间 // 若偏差超过阈值,则调整 RTC 计数(需谨慎) // 更稳健的方法:使用 RTC 同步分频器校准,但需要重新初始化 } } ``` 实际工程中,建议使用一个硬件定时器(如 TIM2)作为高精度时间基准,在切换后持续校准 RTC。 # 完整代码示例 以下为关键部分整合(省略初始化细节): ```c // main.c int main(void) { // 系统时钟配置(略) // 使能备份域访问 PWR_BackupAccessCmd(ENABLE); // 检查备份寄存器标志,判断是否发生过切换 if (BKP_ReadBackupRegister(BKP_DR3) == 0x5A5A) { // 已切换至 LSI,执行恢复流程 // 读取备份时间戳,并设置 RTC 时间 uint32_t saved_ts = BKP_ReadBackupRegister(BKP_DR1); // 转换为 RTC 时间并设置(略) } else { // 正常启动,配置 LSE 和 RTC // ...(见上文配置步骤) } // 使能 CSS 中断 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); while (1) { // 主循环 } } ``` # 注意事项 - **备份域供电**:确保 VBAT 引脚连接到电池或超级电容,否则掉电后备份寄存器数据丢失,热切换失效。 - **CSS 中断优先级**:应设置为高优先级,确保及时响应。 - **LSI 校准**:LSI 频率随温度变化,建议定期(如每小时)通过外部高精度时钟(如 GPS 或网络)校准一次。 - **时间戳保存频率**:不要过于频繁写入备份寄存器(寿命有限),建议每分钟或每分钟一次。 - **切换后的 RTC 配置**:CSS 自动切换后,RTC 的分频器仍按 LSE 参数配置,但 LSI 频率不同,会导致时间快慢。因此,切换后需重新配置分频器(例如,LSI 典型 32kHz,但实际可能 30-34kHz,需测量)。 - **软件补偿的复杂度**:若对时间精度要求极高(如 <1ppm),建议使用外部高精度时钟源,或采用双 RTC 方案。 # 总结 通过备份域寄存器和 CSS 中断,STM32F4 可以在 LSE 停振时无缝切换至 LSI,并利用预存时间戳和软件补偿保持时间连续性。该方案成本低、实现简单,适用于大多数工业场景。开发者应根据实际需求调整补偿策略,并充分测试 LSI 频率特性。