STM32F4 无晶振秒级唤醒:LSE 停振后基于 RTC 备份域的硬件定时方案
👁 3 阅读 · 2026-08-27 · 嵌入式
在低功耗嵌入式设计中,LSE(32.768kHz 外部晶振)常因成本、空间或环境干扰而停振,导致 RTC 无法工作。本文深入剖析 STM32F4 系列在 LSE 失效后的替代方案:利用内部 LSI 时钟驱动 RTC,并通过备份域寄存器实现秒级唤醒。文章涵盖原理分析、寄存器配置、完整代码示例及工程注意事项,帮助开发者摆脱外部晶振依赖,构建可靠的定时唤醒系统。
# 引言
在电池供电的 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 校准机制,并充分测试低功耗模式下的稳定性,以确保长期运行的可靠性。