# 引言 在嵌入式低功耗设计中,RTC(实时时钟)是定时唤醒的常用外设,而 STM32F4 的 RTC 通常依赖 LSE(32.768kHz 外部晶振)保证精度。然而,LSE 存在起振慢(典型 1-2 秒)、抗干扰差、在恶劣环境下易停振等问题。一旦 LSE 停振,RTC 无法工作,系统可能无法按计划唤醒,导致功能失效。 本文提出一种解决方案:利用 RTC 备份域寄存器(BKP)在 LSE 停振时自动切换至 LSI(内部低速 RC 振荡器),并借助备份寄存器保存唤醒标志,实现无外部晶振的可靠唤醒。该方法兼顾精度与可靠性,适用于对时间精度要求不苛刻但强调唤醒可靠性的场景。 # 原理剖析 ## 1. RTC 时钟源与备份域 - STM32F4 的 RTC 可选择 LSE、LSI 或 HSE 分频作为时钟源。LSE 精度高(±20ppm),但依赖外部晶振;LSI 精度低(典型 ±1.5%),但无需外部元件,启动快。 - 备份域(Backup Domain)包括 RTC、备份寄存器(BKP)和备份 SRAM,由 VBAT 供电,当主电源掉电时仍可保持数据。备份寄存器可用于存储唤醒标志、系统状态等关键信息。 - 当 LSE 停振时,RTC 会检测到时钟失效(通过 RTC_ISR 的 LSERDY 位),但默认不会自动切换时钟源。我们需要在软件中处理。 ## 2. 唤醒机制设计 - 目标:在 LSE 正常时使用 LSE 保证时间精度;当 LSE 停振时,自动切换至 LSI,并利用备份寄存器记录“LSE 失效”标志,同时继续维持 RTC 计数,确保唤醒中断按时触发。 - 唤醒流程:系统进入 STOP 模式前,设置 RTC 闹钟(Alarm A)为唤醒源。若 LSE 停振,RTC 仍以 LSI 运行,闹钟依然有效。唤醒后,通过备份寄存器判断是否发生了 LSE 失效,从而决定是否重新校准或采取补救措施。 # 配置步骤 ## 1. 使能电源和备份域时钟 - 必须使能 PWR 和 RCC 的备份域时钟,并允许访问备份域寄存器。 ```c void BKP_Config(void) { __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_BKP_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 允许访问备份域 } ``` ## 2. 配置 RTC 时钟源和闹钟 - 优先尝试 LSE,若 LSE 起振超时或运行中失效,则切换至 LSI。 ```c void RTC_Init(void) { RTC_HandleTypeDef hrtc; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 0x7F; // 128 分频 hrtc.Init.SynchPrediv = 0xFF; // 256 分频,产生 1Hz hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; // 尝试使能 LSE __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); uint32_t timeout = HAL_GetTick() + 2000; // 2 秒超时 while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) { if (HAL_GetTick() >= timeout) { // LSE 起振失败,切换至 LSI __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); __HAL_RCC_LSI_ENABLE(); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET); hrtc.Init.SynchPrediv = 0x1F; // 调整分频以适配 LSI 频率(约 32kHz) break; } } // 选择 RTC 时钟源 if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == SET) { __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); } else { __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI); // 记录 LSE 失效标志到备份寄存器 HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0xCAFE); // 自定义标志 } __HAL_RCC_RTC_ENABLE(); HAL_RTC_Init(&hrtc); // 配置闹钟 A(例如 10 秒后唤醒) RTC_AlarmTypeDef sAlarm = {0}; sAlarm.AlarmTime.Hours = 0; sAlarm.AlarmTime.Minutes = 0; sAlarm.AlarmTime.Seconds = 10; sAlarm.AlarmTime.SubSeconds = 0; sAlarm.AlarmMask = RTC_ALARMMASK_NONE; sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL; sAlarm.Alarm = RTC_ALARM_A; HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN); } ``` ## 3. 进入 STOP 模式并等待唤醒 - 在进入 STOP 前,确保 RTC 闹钟中断已使能,并配置 EXTI 线。 ```c void EnterStopMode(void) { // 使能 RTC 闹钟中断 HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn); // 进入 STOP 模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后重新配置系统时钟(因为 STOP 模式会关闭 HSI/HSE) SystemClock_Config(); } // 中断服务函数 void RTC_Alarm_IRQHandler(void) { HAL_RTC_AlarmIRQHandler(&hrtc); } void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { // 唤醒后检查备份寄存器 uint32_t flag = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR0); if (flag == 0xCAFE) { // LSE 失效,执行补救措施(如重新校准或记录日志) // 此处可清除标志 HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR0, 0x0000); } // 正常唤醒处理 } ``` ## 4. 完整示例代码 - 以下是一个简化但完整的示例,展示了从初始化到唤醒的完整流程。 ```c // main.c RTC_HandleTypeDef hrtc; int main(void) { HAL_Init(); SystemClock_Config(); BKP_Config(); RTC_Init(); while (1) { // 模拟低功耗运行 EnterStopMode(); // 唤醒后执行任务 HAL_Delay(100); } } void SystemClock_Config(void) { // 标准时钟配置,使用 HSI 或 HSE,此处省略 } ``` # 注意事项 - **备份寄存器访问**:必须先调用 `HAL_PWR_EnableBkUpAccess()`,否则写操作无效。 - **LSI 精度**:LSI 的精度较差,若需要精确时间,建议在 LSE 失效时定期校准或使用外部时间同步。 - **时钟切换时序**:在运行中检测到 LSE 失效时,切换时钟源需谨慎,确保 RTC 不会因时钟切换产生毛刺。建议在进入低功耗前完成切换。 - **备份寄存器掉电保持**:备份寄存器由 VBAT 供电,若 VBAT 也掉电,数据会丢失。设计时需考虑。 - **中断优先级**:RTC 闹钟中断优先级应高于其他低优先级中断,避免唤醒后响应延迟。 - **调试建议**:在开发阶段,可通过串口打印备份寄存器内容,验证 LSE 失效标志是否正常写入。 # 总结 通过利用 RTC 备份域寄存器,我们可以在 STM32F4 上实现 LSE 停振后的自动时钟源切换,并保存关键状态,从而保证系统在无外部晶振的情况下依然能够可靠唤醒。该方法不仅提高了系统的鲁棒性,还降低了对外部晶振的依赖,适用于工业控制、便携设备等对可靠性要求高的场景。希望本文能为你提供实用的参考。