# STM32F4 无外部晶振秒级唤醒:LSE 停振后硬件 RTC 备份域实战指南 ## 1. 问题背景与解决方案概述 在电池供电的嵌入式产品中,LSE(32.768kHz 低速外部晶振)常用于 RTC 计时,但其起振慢、易受温湿度影响,一旦停振,RTC 将失去时钟源。传统做法是切换至 LSI(内部低速 RC 振荡器,约 32kHz),但 LSI 精度差(误差可达 ±5%),且默认不备份域供电。 本文方案:**利用 STM32F4 的备份域(Backup Domain)特性,在 LSE 停振后自动切换至 LSI,并通过 RTC 闹钟实现秒级唤醒**。核心思路是: - 备份域由 VBAT 供电,RTC 寄存器与备份寄存器独立于主电源。 - 配置 RTC 时钟源为 LSI,并启用闹钟中断。 - 通过软件校准 LSI 频率,提高计时精度。 ## 2. 硬件 RTC 备份域原理 STM32F4 的 RTC 模块位于备份域中,其供电引脚为 VBAT(通常接电池或大电容)。备份域包含: - RTC 计数器、闹钟寄存器、预分频器。 - 20 个 32 位备份寄存器(BKP)。 - 复位控制:备份域复位(BDRST)不影响主域。 **关键点**: - 当主电源 VDD 掉电时,备份域由 VBAT 维持,RTC 继续运行。 - LSE 和 LSI 均可作为 RTC 时钟源,但 LSE 失效时需手动切换。 - 切换时钟源前,必须进入备份域复位状态,否则写操作无效。 ## 3. 配置步骤(以 STM32F407 为例) ### 3.1 使能电源与备份域时钟 ```c // 使能 PWR 和 RCC 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); // 允许访问备份域 PWR_BackupAccessCmd(ENABLE); ``` ### 3.2 检测 LSE 状态并切换至 LSI ```c // 尝试启动 LSE,等待超时 RCC_LSEConfig(RCC_LSE_ON); uint32_t timeout = 100000; while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout--)); if (timeout == 0) { // LSE 停振,切换至 LSI RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 备份域复位,重新配置 RTC RCC_BackupResetCmd(ENABLE); RCC_BackupResetCmd(DISABLE); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); // 初始化 RTC RTC_InitTypeDef RTC_InitStruct; RTC_StructInit(&RTC_InitStruct); RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStruct.RTC_AsynchPrediv = 0x7F; // 128 分频 RTC_InitStruct.RTC_SynchPrediv = 0x00; // 1 分频,得到 1Hz RTC_Init(&RTC_InitStruct); } else { // LSE 正常,使用 LSE RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); // 正常初始化 RTC... } ``` ### 3.3 配置 RTC 闹钟中断 ```c // 设置闹钟时间为 1 秒后 RTC_AlarmTypeDef RTC_AlarmStruct; RTC_AlarmStruct.RTC_AlarmTime.RTC_H12 = RTC_H12_AM; RTC_AlarmStruct.RTC_AlarmTime.RTC_Hours = 0; RTC_AlarmStruct.RTC_AlarmTime.RTC_Minutes = 0; RTC_AlarmStruct.RTC_AlarmTime.RTC_Seconds = 1; RTC_AlarmStruct.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay; // 仅匹配时间 RTC_AlarmStruct.RTC_AlarmDateWeekDaySel = RTC_AlarmDateWeekDaySel_Date; RTC_AlarmStruct.RTC_AlarmDateWeekDay = 1; RTC_SetAlarm(RTC_Alarm_A, &RTC_AlarmStruct); // 使能闹钟中断 RTC_ITConfig(RTC_IT_ALRA, ENABLE); // 配置 EXTI 线 17(RTC 闹钟) EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line17; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); // 使能 NVIC 中断 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = RTC_Alarm_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); ``` ### 3.4 进入低功耗模式并等待唤醒 ```c // 进入 STOP 模式,RTC 闹钟可唤醒 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新配置系统时钟(因为 STOP 模式会关闭 HSI/HSE) SystemInit(); ``` ## 4. 完整代码示例(关键部分) ```c // 主函数示例 int main(void) { // 初始化时钟、GPIO、串口等... // 配置 RTC 备份域唤醒 RTC_BackupInit(); while (1) { // 进入 STOP 模式,等待闹钟唤醒 __WFI(); // 唤醒后处理任务 printf("Wake up!\n"); } } void RTC_Alarm_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_ALRA) != RESET) { RTC_ClearITPendingBit(RTC_IT_ALRA); // 重新设置闹钟为 1 秒后 RTC_SetAlarm(RTC_Alarm_A, &alarmStruct); // 可在此翻转 LED 等 } } ``` ## 5. 注意事项与调试技巧 - **备份域复位**:切换时钟源前必须执行 `RCC_BackupResetCmd(ENABLE)`,否则 RTC 配置无效。 - **LSI 校准**:LSI 频率随温度变化,可在运行时测量 LSI 频率(通过 TIM5 捕获),并调整异步预分频值来补偿。例如,若测得 LSI 为 32.2kHz,则设置 `RTC_AsynchPrediv = 0x7D`(约 125 分频)得到 1Hz。 - **唤醒后时钟恢复**:从 STOP 模式唤醒后,系统时钟默认使用 HSI,需重新配置 PLL 等,否则外设可能异常。 - **VBAT 供电**:确保 VBAT 引脚连接电池或大电容(如 1F 超级电容),否则备份域数据丢失。 - **调试时**:建议先用串口打印 RTC 当前时间,验证秒级递增是否正常。 - **低功耗电流**:使用 LSI 时功耗略高于 LSE,但可接受;若需更低功耗,可考虑外部 32.768kHz 晶振并做好抗干扰设计。 ## 6. 总结 通过备份域与 LSI 的配合,STM32F4 在 LSE 停振后仍能实现可靠的秒级唤醒。本方案无需外部晶振,降低了 BOM 成本,同时利用备份域保持 RTC 计时,适合对成本敏感且环境恶劣的嵌入式应用。关键在于正确配置备份域复位、时钟切换和闹钟中断,并注意唤醒后的时钟恢复。