引言

在电池供电的 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 和备份域时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 允许访问备份域
RCC_BackupResetCmd(ENABLE);  // 复位备份域(可选,用于干净配置)
RCC_BackupResetCmd(DISABLE);

步骤 2:检测 LSE 状态并切换时钟源

// 尝试启动 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 并配置唤醒定时器

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 中断

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:进入低功耗模式并处理唤醒

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 标准外设库):

#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 校准机制,并充分测试低功耗模式下的稳定性,以确保长期运行的可靠性。