3、低功耗唤醒源:唤醒源分类、RTC唤醒、按键唤醒、加速度计唤醒、触摸唤醒

好,咱们接着聊。上一章我们把睡眠模式讲透了,那睡下去之后怎么醒过来?这就是唤醒源要干的事。

说白了,唤醒源就是给MCU一个「该起床了」的信号。我刚开始做智能手表那会儿,觉得唤醒不就是按个键嘛,后来发现事情远没那么简单。不同的场景需要不同的唤醒方式,选错了,功耗就崩了。

3.1 唤醒源分类

智能手表里常见的唤醒源,我按使用频率排个序:

  • RTC唤醒:定时起床,比如每秒更新一次时间显示
  • 按键唤醒:用户主动操作,比如按表冠亮屏
  • 加速度计唤醒:抬手亮屏、运动检测
  • 触摸唤醒:触摸屏幕亮屏

你想想看,这些唤醒源其实对应着不同的使用场景。RTC是「被动定时」,按键是「主动触发」,加速度计是「感知动作」,触摸是「感知接触」。

核心原则:唤醒源的选择要跟功耗预算挂钩。每多一个唤醒源,待机电流就可能多几微安。我在项目中见过有人把所有唤醒源全开,结果待机电流直接飙到50μA,电池两天就扛不住了。

3.2 RTC唤醒

RTC唤醒是最基础的。它靠一个独立的32.768kHz晶振来计时,功耗极低,一般只有几百纳安。

我习惯把RTC唤醒分成两种模式:

  • 周期性唤醒:比如每1秒唤醒一次,刷新时间显示
  • 单次唤醒:比如设定10分钟后闹钟响

代码实现其实很简单,以STM32为例:

// 配置RTC唤醒,每1秒唤醒一次
void RTC_Wakeup_Config(void)
{
    // 使能RTC时钟
    RTC_ClockCmd(RTC_CLOCK_ENABLE);
    
    // 设置唤醒周期:1秒
    // 32.768kHz / 32768 = 1Hz
    RTC_SetWakeUpCounter(32767);
    RTC_WakeUpCmd(ENABLE);
    
    // 使能唤醒中断
    RTC_ITConfig(RTC_IT_WUT, ENABLE);
}

// 唤醒中断服务函数
void RTC_WKUP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_WUT) != RESET)
    {
        // 清除标志
        RTC_ClearITPendingBit(RTC_IT_WUT);
        
        // 更新时间显示
        Update_Time_Display();
        
        // 注意:这里要尽快处理,然后继续睡眠
        // 我一般控制在100μs以内
    }
}

我的经验:RTC唤醒的周期不要设得太短。我曾经为了追求时间显示的流畅度,设了100ms唤醒一次,结果功耗直接翻倍。后来改成500ms,人眼根本看不出区别,功耗却降了一半。

3.3 按键唤醒

按键唤醒是用户最直接的交互方式。但这里有个坑——按键抖动。

你想想看,机械按键按下的时候,信号会来回弹跳好几次。如果不做处理,MCU可能被误唤醒好几次。

我一般用两种方案:

  • 硬件消抖:加一个RC滤波电路,简单粗暴
  • 软件消抖:在中断里加延时判断,但要注意延时不能太长

看个实际代码:

// 按键唤醒配置
void Button_Wakeup_Config(void)
{
    // 配置GPIO为外部中断模式
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = BUTTON_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
    GPIO_InitStruct.Pull = GPIO_PULLUP;           // 内部上拉
    HAL_GPIO_Init(BUTTON_PORT, &GPIO_InitStruct);
    
    // 使能外部中断
    HAL_NVIC_SetPriority(EXTI_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI_IRQn);
}

// 按键中断处理
void EXTI_IRQHandler(void)
{
    // 先判断是不是我们的按键
    if(__HAL_GPIO_EXTI_GET_IT(BUTTON_PIN) != RESET)
    {
        // 延时消抖,我一般用10ms
        HAL_Delay(10);
        
        // 再次确认按键状态
        if(HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN) == GPIO_PIN_RESET)
        {
            // 有效按键,唤醒系统
            System_Wakeup();
        }
        
        // 清除中断标志
        __HAL_GPIO_EXTI_CLEAR_IT(BUTTON_PIN);
    }
}

注意:按键唤醒的功耗主要来自外部中断引脚的上下拉电阻。我建议用内部上拉,不要外接,能省几微安。另外,按键的GPIO要选支持唤醒的引脚,不是所有引脚都能在睡眠模式下唤醒MCU的。

3.4 加速度计唤醒

加速度计唤醒是智能手表的灵魂功能。抬手亮屏、翻腕切歌,全靠它。

但这里有个矛盾:加速度计本身要一直工作才能检测动作,功耗怎么控制?

我分享一个实际方案:

  • 加速度计自己带FIFO和中断:比如BMA423,它内部有运动检测引擎
  • 设置阈值:加速度变化超过某个值才触发中断
  • MCU深度睡眠:加速度计工作,MCU睡大觉

配置代码大概长这样:

// 加速度计唤醒配置(以BMA423为例)
void Accel_Wakeup_Config(void)
{
    // 初始化加速度计
    bma4_init(&bma);
    
    // 设置运动检测阈值
    // 我一般设0.1g,太灵敏容易误触发
    bma4_set_accel_enable(1, &bma);
    bma4_set_advance_power_save(0, &bma);
    
    // 配置中断引脚
    bma4_set_interrupt_pin_config(BMA4_INTR1_MAP,
                                  BMA4_INTR1_PIN_SELECT,
                                  &bma);
    
    // 使能任意运动中断
    bma4_set_any_motion_interrupt(1, &bma);
    
    // 设置加速度计输出数据速率
    // 25Hz就够了,太高费电
    bma4_set_output_data_rate(BMA4_OUTPUT_DATA_RATE_25HZ, &bma);
}

避坑指南:我曾经把加速度计的阈值设得太低,结果用户翻个身手表就亮了,一晚上耗电20%。后来改成0.15g,配合3秒的防误触延时,效果好了很多。

3.5 触摸唤醒

触摸唤醒在高端手表上用得比较多。用户轻点屏幕就能亮屏,体验很好。

但触摸唤醒有个硬伤——功耗。触摸芯片本身要一直扫描电容变化,功耗一般在10-50μA之间。

我一般这样优化:

  • 降低扫描频率:睡眠时从100Hz降到10Hz
  • 缩小感应区域:只扫描屏幕中心区域
  • 增加灵敏度阈值:避免水滴、汗液误触发

看个触摸芯片的配置示例:

// 触摸唤醒配置(以FT3267为例)
void Touch_Wakeup_Config(void)
{
    // 进入低功耗模式
    touch_write_reg(FT_REG_LP_MODE, 0x01);
    
    // 设置唤醒阈值
    // 数值越大越不灵敏,我一般设40
    touch_write_reg(FT_REG_WAKEUP_THRESHOLD, 40);
    
    // 设置扫描周期
    // 80ms扫描一次,功耗和响应速度的平衡点
    touch_write_reg(FT_REG_SCAN_PERIOD, 80);
    
    // 使能触摸唤醒中断
    touch_write_reg(FT_REG_INT_ENABLE, 0x01);
}

我的建议:触摸唤醒最好跟按键唤醒配合使用。比如用户触摸屏幕时先亮一个微弱的背光,如果3秒内没有操作再进入深度睡眠。这样既省电,体验又好。

3.6 多唤醒源协同

实际产品中,这些唤醒源是同时工作的。怎么协调它们?

我一般用优先级策略:

唤醒源 优先级 典型场景
按键唤醒 最高 用户主动操作,必须立即响应
触摸唤醒 触摸屏幕,快速亮屏
加速度计唤醒 抬手亮屏,可以容忍少量延迟
RTC唤醒 定时任务,可以排队处理

嗯,这里要注意:多个唤醒源同时触发时,要有一个仲裁机制。我习惯在中断服务函数里加一个标志位,记录是哪个源唤醒的,然后按优先级处理。

总结一下:唤醒源的选择不是越多越好,而是越精准越好。每个唤醒源都有它的功耗代价,你要做的就是在用户体验和电池续航之间找到那个平衡点。我做了这么多年手表,最深的体会就是——用户不会因为你省了1μA而夸你,但会因为一天一充而骂你。