唤醒源与中断机制:外部中断、RTC闹钟、定时器唤醒、串口唤醒

各位做低功耗的朋友,咱们今天聊聊唤醒源。说白了,就是怎么把睡着的设备叫醒。

我刚开始做低功耗项目时,踩过一个坑——设备休眠后死活唤不醒。后来发现是中断配置错了。嗯,从那以后我对唤醒源这块就特别较真。

唤醒源的四种基本类型

低功耗设备常用的唤醒源,我归纳为四类。每类都有自己的脾气。

唤醒源类型 触发方式 典型功耗 响应速度
外部中断 GPIO电平变化 nA级 μs级
RTC闹钟 定时触发 μA级 ms级
定时器唤醒 内部计时 μA级 μs级
串口唤醒 数据接收 μA级 ms级

外部中断:最直接的唤醒方式

外部中断是唤醒的「急先锋」。按键按下、传感器触发、通信请求,都能用它。

我个人习惯把外部中断引脚配置为下降沿触发。为什么?因为上升沿容易受噪声干扰。我在项目中遇到过,用上升沿触发时,设备莫名其妙被唤醒,一查是电源纹波在作怪。

关键配置要点:

  • 必须选择支持唤醒的中断引脚(不是所有GPIO都能唤醒)
  • 使能GPIO中断,并设置为唤醒源
  • 配置内部上拉/下拉,防止浮空误触发
// 外部中断唤醒配置示例(STM32L0系列)
void EXTI_WakeUp_Config(void)
{
    // 使能GPIO时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置PA0为输入,上拉
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 使能EXTI中断
    HAL_NVIC_SetPriority(EXTI0_1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_1_IRQn);
    
    // 设置为唤醒源
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
}

我曾经踩过的坑:外部中断唤醒后,一定要在中断服务函数里清除中断标志位。否则设备会陷入「唤醒→进中断→不清标志→再次唤醒」的死循环。电池半天就耗光了。

RTC闹钟:定时唤醒的王者

RTC闹钟适合做周期性任务。比如传感器每10分钟采集一次数据,或者设备每天凌晨上报一次状态。

你想想看,RTC在休眠时还在跑,功耗只有几百纳安。这性价比,没谁了。

我的经验:RTC闹钟可以设置多个匹配条件——年、月、日、时、分、秒。但别设太复杂,匹配条件越多,功耗越高。我一般只用「分+秒」匹配,够用了。

// RTC闹钟唤醒配置
void RTC_Alarm_Config(void)
{
    RTC_AlarmTypeDef sAlarm = {0};
    
    // 设置闹钟时间为 30秒后
    sAlarm.AlarmTime.Hours = 0;
    sAlarm.AlarmTime.Minutes = 0;
    sAlarm.AlarmTime.Seconds = 30;
    sAlarm.AlarmTime.SubSeconds = 0;
    
    // 匹配模式:只匹配秒
    sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY |
                       RTC_ALARMMASK_HOURS |
                       RTC_ALARMMASK_MINUTES;
    
    // 使能闹钟
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
    
    // 配置中断优先级
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

定时器唤醒:灵活但费电

定时器唤醒和RTC闹钟有点像,但有个关键区别——定时器在休眠时通常不工作。除非你用的是低功耗定时器(LPTIM)。

为什么我这么说?因为普通定时器在深度睡眠时会停摆。只有LPTIM这类专门为低功耗设计的定时器,才能在休眠时继续计数。

适用场景对比:

  • RTC闹钟:长时间间隔(秒级到天级),功耗极低
  • LPTIM:短时间间隔(微秒到秒级),精度更高
  • 普通定时器:只能在浅睡眠模式下使用

串口唤醒:通信驱动的唤醒

串口唤醒是个好东西。设备休眠时,串口线上有数据过来,自动唤醒。

我在项目中做过一个无线传感器节点,平时深度睡眠,主机通过串口发一个字节就能唤醒它。功耗从mA级降到μA级,电池寿命从3个月延长到2年。

但要注意,串口唤醒有个前提——串口接收引脚必须支持唤醒功能。不是所有MCU的UART都能做到。

// 串口唤醒配置(以USART为例)
void USART_WakeUp_Config(void)
{
    // 使能USART时钟
    __HAL_RCC_USART1_CLK_ENABLE();
    
    // 配置USART引脚
    // ...(GPIO配置略)
    
    // 使能USART唤醒功能
    USART_WakeUpTypeDef WakeUpStruct;
    WakeUpStruct.WakeUpEvent = USART_WAKEUP_ON_START_BIT;
    HAL_USARTEx_WakeUpConfig(&husart1, &WakeUpStruct);
    
    // 使能唤醒中断
    HAL_NVIC_SetPriority(USART1_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
    
    // 进入休眠前使能唤醒
    HAL_USARTEx_EnableWakeUp(&husart1);
}

注意:串口唤醒的代价是,接收引脚需要保持供电。有些MCU在深度睡眠时会关掉IO电源,这时候串口唤醒就失效了。选型时一定要看数据手册的「Wake-up from Stop mode」章节。

中断优先级:别让优先级坑了你

中断优先级配置,看着简单,其实门道很多。

我见过一个案例:同事把RTC闹钟中断优先级设得比外部中断还高。结果外部中断来了,RTC闹钟正好也触发,高优先级的RTC把外部中断堵住了。设备该响应的没响应,用户投诉说按键失灵。

我的建议是:

  • 紧急事件(外部中断):优先级最高(数值最小)
  • 定时任务(RTC/LPTIM):中等优先级
  • 通信唤醒(串口):较低优先级

为什么这么排?因为外部中断通常是用户交互或紧急告警,不能丢。定时任务晚几十毫秒没关系。串口唤醒嘛,数据来了先收着,处理可以稍后。

我个人习惯:在Cortex-M系列MCU上,我会用4位抢占优先级,0位子优先级。这样优先级分组清晰,不容易乱。配置一次,所有项目通用。

多唤醒源协同:别打架

实际项目中,很少只用一种唤醒源。往往是外部中断+RTC闹钟+串口唤醒一起上。

这时候就要注意了——多个唤醒源同时触发怎么办?

我的做法是:在中断服务函数里设置标志位,主循环里统一处理。不要在中断里做复杂操作。

// 多唤醒源标志位处理
volatile uint32_t wakeup_flags = 0;

void EXTI0_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
    {
        wakeup_flags |= WAKEUP_EXTI;
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
    }
}

void RTC_Alarm_IRQHandler(void)
{
    wakeup_flags |= WAKEUP_RTC;
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

void USART1_IRQHandler(void)
{
    wakeup_flags |= WAKEUP_UART;
    HAL_USART_IRQHandler(&husart1);
}

// 主循环中处理
while(1)
{
    if(wakeup_flags & WAKEUP_EXTI)
    {
        // 处理外部中断事件
        wakeup_flags &= ~WAKEUP_EXTI;
    }
    if(wakeup_flags & WAKEUP_RTC)
    {
        // 处理定时任务
        wakeup_flags &= ~WAKEUP_RTC;
    }
    // ... 进入休眠
}

核心原则:中断只做最轻量级的工作——清标志、设标志。真正的业务逻辑放到主循环里。这样既不会丢失中断,也不会让中断嵌套搞乱优先级。

知识体系总览

下面这张图,是我梳理的唤醒源与中断机制的整体框架。你可以把它当作设计时的检查清单。

唤醒源与中断机制知识体系 唤醒源与中断机制知识体系 唤醒源管理 外部中断 RTC闹钟 定时器唤醒 串口唤醒 GPIO电平变化触发 下降沿/上升沿配置 内部上拉/下拉防抖 定时匹配触发 年/月/日/时/分/秒 nA级待机功耗 LPTIM低功耗定时器 μs级高精度 浅睡眠可用 起始位/数据触发 需IO保持供电 通信驱动唤醒 中断优先级配置原则 外部中断:最高优先级 RTC/定时器:中等 串口唤醒:较低优先级 提示:中断服务函数只做标志位设置,业务逻辑放在主循环

这张图把唤醒源分成了四个分支,每个分支下面有它的核心特点。最下面是中断优先级配置原则。你设计的时候,对着这张图捋一遍,基本不会漏东西。

好了,唤醒源和中断机制就聊到这儿。记住一句话:唤醒源选对了,功耗就降了一半;中断优先级配好了,系统就稳了一半。剩下的,就是动手调了。