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而夸你,但会因为一天一充而骂你。