# 引言 在电池供电的 IoT 节点或工业传感器中,定时唤醒是降低功耗的核心手段。STM32F4 系列通常依赖 LSE(外部 32.768kHz 晶振)为 RTC 提供精准时钟,但 LSE 可能因 PCB 布局、温度漂移或晶振质量而停振。此时,若系统仍要求秒级唤醒,必须另辟蹊径。本文介绍一种实用方案:当 LSE 停振时,自动切换至 LSI(内部低速时钟,约 32kHz),并利用 RTC 备份域寄存器保存唤醒配置,实现无外部晶振的秒级唤醒。该方案兼顾硬件可靠性与软件灵活性,适合对成本敏感或环境苛刻的嵌入式应用。 # 原理剖析 ## 1. RTC 与备份域架构 STM32F4 的 RTC 单元位于备份域(Backup Domain),由 VBAT 引脚供电,当主电源 VDD 掉电时,RTC 仍可运行。备份域包含 RTC 寄存器、备份寄存器(BKP)以及 RTC 时钟源选择逻辑。RTC 时钟源可选 LSE、LSI 或 HSE 分频,通过 RCC_BDCR 寄存器的 RTCSEL 位域配置。 ## 2. LSE 停振检测与切换 LSE 停振时,RTC 时钟丢失,但硬件不会自动切换。软件需通过 LSION 和 LSERDY 位监测 LSE 状态。若检测到 LSE 未就绪,则启用 LSI 并切换 RTCSEL。LSI 精度较低(典型 32kHz,误差 ±5%),但足以满足秒级唤醒(误差可接受)。切换后,RTC 的预分频器需重新配置以匹配 LSI 频率。 ## 3. 备份域寄存器的作用 备份域寄存器(如 RTC_CR、RTC_PRER)在系统复位或从 STOP/STANDBY 模式唤醒后保留。通过设置 RTC_CR 的 WUTE 位,可启用唤醒定时器。唤醒定时器计数时钟可选 RTC 时钟的 1/16 或 1/1,配合重载值实现秒级中断。备份域还提供 RTC_ISR 的 WUTF 标志,用于检测唤醒事件。 # 硬件与软件配置步骤 ## 1. 硬件连接 - 无需外部晶振,VBAT 接 3V 电池或直接接 VDD(若不需要掉电保持)。 - 确保 VDD 供电稳定,复位电路正常。 ## 2. 软件配置流程 ### 步骤 1:使能 PWR 和备份域时钟 ```c RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_BackupAccessCmd(ENABLE); // 允许访问备份域 RCC_BackupResetCmd(ENABLE); // 复位备份域(可选,用于干净配置) RCC_BackupResetCmd(DISABLE); ``` ### 步骤 2:检测 LSE 状态并切换时钟源 ```c // 尝试启动 LSE RCC_LSEConfig(RCC_LSE_ON); // 等待超时(如 1ms) uint32_t timeout = 1000; while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout--)); if (timeout == 0) { // LSE 停振,切换至 LSI RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); } else { RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); } RCC_RTCCLKCmd(ENABLE); ``` ### 步骤 3:初始化 RTC 并配置唤醒定时器 ```c RTC_InitTypeDef RTC_InitStruct; uint32_t lsifreq = 32000; // 实际 LSI 频率可能略有偏差,可校准 RTC_StructInit(&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); // 使能唤醒定时器,时钟为 RTC 时钟/16,重载值 1 秒(假设 LSI=32kHz) RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits); // 实际为 1Hz 时钟 RTC_SetWakeUpCounter(1); // 1 秒后唤醒 RTC_ITConfig(RTC_IT_WUT, ENABLE); RTC_WakeUpCmd(ENABLE); ``` ### 步骤 4:配置 NVIC 中断 ```c NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = RTC_WKUP_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); ``` ### 步骤 5:进入低功耗模式并处理唤醒 ```c void RTC_WKUP_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_WUT) != RESET) { RTC_ClearITPendingBit(RTC_IT_WUT); // 执行唤醒任务,如读取传感器、发送数据 SystemWakeup(); } } // 主循环中进入 STOP 模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新配置系统时钟(如启用 HSI) SystemInit(); ``` # 完整代码示例 以下为集成上述步骤的简化示例(基于 STM32F4xx 标准外设库): ```c #include "stm32f4xx.h" void RTC_Init_NoLSE(void) { // 使能 PWR 和备份域 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_BackupAccessCmd(ENABLE); RCC_BackupResetCmd(ENABLE); RCC_BackupResetCmd(DISABLE); // 尝试 LSE,失败则用 LSI RCC_LSEConfig(RCC_LSE_ON); uint32_t timeout = 1000; while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (timeout--)); if (timeout == 0) { RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); } else { RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); } RCC_RTCCLKCmd(ENABLE); // RTC 初始化,预分频得到 1Hz RTC_InitTypeDef RTC_InitStruct; RTC_StructInit(&RTC_InitStruct); RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24; RTC_InitStruct.RTC_AsynchPrediv = 0x7F; RTC_InitStruct.RTC_SynchPrediv = 0xFF; RTC_Init(&RTC_InitStruct); // 唤醒定时器,1 秒周期 RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits); RTC_SetWakeUpCounter(1); RTC_ITConfig(RTC_IT_WUT, ENABLE); RTC_WakeUpCmd(ENABLE); // NVIC 配置 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = RTC_WKUP_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); } void RTC_WKUP_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_WUT) != RESET) { RTC_ClearITPendingBit(RTC_IT_WUT); // 唤醒后处理 GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // 示例:翻转 LED } } int main(void) { // 初始化时钟(HSI 或 HSE,但 HSE 可能无晶振,用 HSI) SystemInit(); // 使能 GPIO 时钟等 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOD, &GPIO_InitStruct); RTC_Init_NoLSE(); while (1) { // 进入 STOP 模式,等待唤醒 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新配置系统时钟(因为 STOP 模式会关闭 HSI) SystemInit(); } } ``` # 注意事项 - **LSI 频率校准**:LSI 精度有限,若需精确秒级定时,可在运行时测量 LSI 频率(通过 TIM 捕获)并调整预分频或重载值。 - **备份域复位**:在切换时钟源前,务必复位备份域,否则 RTC 配置可能残留。但注意复位会清除备份寄存器数据。 - **唤醒后时钟恢复**:从 STOP 模式唤醒后,系统时钟默认使用 HSI,需重新初始化 PLL 等,否则外设时钟异常。 - **中断优先级**:RTC 唤醒中断应设置为较高优先级,避免被其他中断阻塞,导致唤醒处理延迟。 - **VBAT 供电**:若 VBAT 未接电池,当 VDD 掉电时备份域数据丢失,唤醒配置将失效。确保 VBAT 连接至稳定的 3V 电源。 - **低功耗模式选择**:STOP 模式功耗较低,但唤醒时间较短;STANDBY 模式功耗更低,但唤醒后系统复位,需重新初始化。根据需求选择。 # 总结 通过利用 STM32F4 的备份域和 LSI 时钟,即使 LSE 停振,也能实现可靠的秒级唤醒。该方案无需外部晶振,降低了 BOM 成本和硬件复杂度,特别适合对成本敏感的批量产品。开发者只需掌握 RTC 时钟源切换和唤醒定时器配置,即可快速集成。在实际项目中,建议增加 LSI 校准机制,并充分测试低功耗模式下的稳定性,以确保长期运行的可靠性。