3. 唤醒源与唤醒机制:外部中断、定时器唤醒、RTC唤醒、比较器唤醒

低功耗设计里,休眠只是手段,唤醒才是目的。你让芯片睡过去,总得有个东西把它叫醒。这个「叫醒」的动作,就是唤醒源和唤醒机制。

我见过不少工程师,功耗降不下去,不是因为休眠没做好,而是唤醒配置出了岔子。要么唤醒不了,要么频繁误唤醒。今天咱们就把四种最常用的唤醒方式掰开揉碎讲清楚。

核心观点:唤醒源的选择,直接决定了系统的响应速度和平均功耗。没有「最好」的唤醒源,只有「最合适」的。

唤醒源与唤醒机制架构 MCU 唤醒控制器 外部中断 定时器唤醒 RTC 唤醒 比较器唤醒 系统恢复运行 💡 不同唤醒源的唤醒延迟差异很大:外部中断最快(~几个时钟周期),RTC 最慢(~毫秒级)。 选择时需权衡响应速度与功耗。

3.1 外部中断唤醒

这是最直接、最常用的唤醒方式。说白了,就是给芯片一根「门铃线」—— 外部电平变化,芯片立刻醒来。

我习惯把外部中断唤醒分成两类:

  • 电平触发:高电平或低电平。优点是稳定,缺点是如果电平一直保持,会持续唤醒。
  • 边沿触发:上升沿、下降沿或双边沿。适合按键、传感器脉冲这类短暂信号。

我的经验:做产品时,按键唤醒我必用下降沿触发。为什么?因为按键按下瞬间电平跳变,松开时也有跳变。用下降沿只响应按下动作,避免一次按键触发两次唤醒。

代码示例(STM32L0 系列,进入 Stop 模式并配置外部中断唤醒):

// 配置 PA0 为外部中断,下降沿触发
void EXTI_WakeUp_Config(void)
{
    // 使能 GPIOA 时钟
    RCC->IOPENR |= RCC_IOPENR_GPIOAEN;

    // PA0 配置为输入
    GPIOA->MODER &= ~(GPIO_MODER_MODE0);

    // 选择 PA0 作为 EXTI0 输入
    SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;
    SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PA;

    // 下降沿触发
    EXTI->FTSR1 |= EXTI_FTSR1_FT0;

    // 不屏蔽中断线
    EXTI->IMR1 |= EXTI_IMR1_IM0;

    // 使能 NVIC 中断
    NVIC_EnableIRQ(EXTI0_IRQn);
}

// 进入 Stop 模式
void Enter_StopMode(void)
{
    // 设置 SLEEPDEEP 位
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    // 选择 Stop 模式(LPMSR 寄存器)
    PWR->CR1 |= PWR_CR1_LPMS_STOP0;

    // 执行 WFI 指令
    __WFI();
}

// 中断服务函数
void EXTI0_IRQHandler(void)
{
    if(EXTI->PR1 & EXTI_PR1_PIF0)
    {
        EXTI->PR1 = EXTI_PR1_PIF0;  // 清除标志位
        // 处理唤醒事件
    }
}

⚠️ 注意:进入休眠前,一定要确保中断标志位已清除。我曾经踩过这个坑 —— 休眠前没清标志,一睡下去立刻被同一个中断唤醒,根本进不了低功耗模式。

3.2 定时器唤醒

需要周期性执行任务的场景,定时器唤醒是首选。比如传感器每隔 100ms 采集一次数据。

定时器唤醒的核心思路:让一个低速定时器在休眠期间继续跑,溢出时产生中断把芯片叫醒。

常用的定时器选择:

定时器类型 时钟源 典型功耗 适用场景
LPTIM(低功耗定时器) LSI / LSE ~0.5 µA 长周期唤醒(秒级)
RTC 闹钟 LSE(32.768 kHz) ~0.3 µA 精确时间唤醒
基本定时器(TIM6/7) HSI / MSI ~2 µA 短周期唤醒(毫秒级)

我个人习惯:能用 LPTIM 就不用普通定时器。LPTIM 在休眠时依然可以依靠 LSI 时钟运行,功耗极低。普通定时器在 Stop 模式下时钟会被切断,根本跑不起来。

配置 LPTIM 唤醒的要点:

  • 选择 LSI 或 LSE 作为时钟源(LSE 更准,LSI 更省)
  • 设置合适的预分频和自动重装载值
  • 使能 LPTIM 中断并配置为唤醒源

避坑指南:我曾经用 LSI 做 1 秒定时唤醒,结果实测变成 1.3 秒。后来才发现 LSI 的频率误差能达到 ±10%。如果你的应用对时间精度有要求,务必用 LSE 或者做软件校准。

3.3 RTC 唤醒

RTC(实时时钟)唤醒,本质上也是一种定时唤醒,但它更「聪明」。RTC 可以设置闹钟、周期性唤醒、甚至支持「按星期几」唤醒。

RTC 唤醒的三种模式:

  1. 闹钟匹配唤醒:设定一个具体时间点(比如 08:30:00),到了就唤醒。
  2. 周期性唤醒:每隔 N 秒/分钟/小时唤醒一次,不需要指定具体时间。
  3. 时间戳唤醒:检测到外部事件(如 tamper 检测)时记录时间并唤醒。

实际项目中,我用的最多的是周期性唤醒。比如一个环境监测节点,每 10 分钟采集一次数据,用 RTC 的周期性唤醒最合适。

// 配置 RTC 每 10 秒唤醒一次
void RTC_WakeUp_Config(void)
{
    // 使能 RTC 时钟
    RCC->BDCR |= RCC_BDCR_RTCEN;

    // 等待 RTC 同步
    while(!(RTC->ISR & RTC_ISR_RSF));

    // 进入配置模式
    RTC->ISR |= RTC_ISR_INIT;
    while(!(RTC->ISR & RTC_ISR_INITF));

    // 设置唤醒定时器:10 秒
    // 时钟源 LSE (1 Hz),计数器值 = 10 - 1
    RTC->CR &= ~RTC_CR_WUCKSEL;
    RTC->CR |= RTC_CR_WUCKSEL_0;  // 选择 1 Hz 时钟
    RTC->WUTR = 9;                // 10 - 1

    // 退出配置模式
    RTC->ISR &= ~RTC_ISR_INIT;

    // 使能唤醒中断
    RTC->CR |= RTC_CR_WUTIE;
    NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

小技巧:RTC 唤醒后,记得在中断里重新配置下一次唤醒时间。如果你用的是「一次性闹钟」,不重新配置的话,下次就不会唤醒了。

3.4 比较器唤醒

比较器唤醒,是模拟世界和数字世界的桥梁。当模拟电压超过或低于某个阈值时,比较器翻转,产生唤醒信号。

这玩意儿在电池供电设备里特别有用。比如:

  • 电池电压监测:电压低于 3.0V 时唤醒,提示用户充电。
  • 传感器阈值检测:光照强度超过设定值,唤醒系统处理数据。
  • 过流保护:电流异常时立即唤醒,执行保护动作。

比较器唤醒的配置要点:

参数 说明 注意事项
输入源选择 选择正极和负极输入引脚 确保引脚在休眠时保持有效
阈值设置 内部参考电压或外部基准 内部参考精度有限,关键应用用外部基准
迟滞配置 防止噪声导致频繁翻转 迟滞太小会误唤醒,太大会延迟响应
输出极性 上升沿或下降沿触发唤醒 根据应用场景选择

⚠️ 重要提醒:比较器本身也会耗电!一个典型的片内比较器功耗在 1~5 µA 之间。如果你对功耗要求极其苛刻(比如 < 1 µA),可以考虑用外部比较器 + 中断引脚的方式,或者用 RTC 定时唤醒后轮询检测。

我记得有个项目,客户要求待机功耗 < 2 µA,但需要实时监测电池电压。用片内比较器的话,光比较器就吃掉 3 µA,直接超标。后来我改用 RTC 每 1 秒唤醒一次,用 ADC 快速采样电压,平均功耗降到了 1.2 µA。你看,有时候「实时」不一定非要「真实时」。

3.5 唤醒源的选择策略

讲了四种唤醒方式,到底怎么选?我总结了一个简单的决策流程:

  1. 需要立即响应外部事件? → 外部中断唤醒
  2. 需要周期性执行任务? → 定时器或 RTC 唤醒
  3. 需要监测模拟信号? → 比较器唤醒
  4. 需要精确时间点唤醒? → RTC 闹钟唤醒

但实际项目中,往往是多种唤醒源组合使用。比如一个智能门锁:

  • 外部中断(按键/刷卡)→ 立即唤醒开锁
  • RTC 周期性唤醒 → 每 30 秒检查一次网络心跳
  • 比较器唤醒 → 电池电压过低时报警

核心原则:唤醒源的功耗 + 唤醒后的工作时间 × 频率 = 平均功耗。别只盯着唤醒源本身的功耗,要把整个唤醒-工作-休眠周期一起算。

嗯,唤醒源这部分就讲到这里。记住一句话:唤醒不是目的,低功耗地完成工作是目的。选对唤醒源,你的低功耗设计就成功了一半。