7、Wakeup源实战案例:GPIO按键唤醒、RTC定时唤醒、网络数据包唤醒
好,咱们今天聊点实在的。
前面讲了那么多wakeup源的理论,什么irq、abort、wakeup event,说实话,光看概念容易晕。我个人习惯是,直接上手调几个典型场景,比看十遍文档都管用。
这一章,我就拿三个最常见的唤醒源来拆解:GPIO按键唤醒、RTC定时唤醒、网络数据包唤醒。这三个场景覆盖了90%的嵌入式Android设备需求。你想想看,手机、平板、IoT盒子,哪个不需要按键开机?哪个不需要定时上报?哪个不需要网络保持连接?
7.1 GPIO按键唤醒:最基础的物理唤醒
先讲最简单的。GPIO按键唤醒,说白了就是按一下物理按键,把系统从suspend状态拉回来。
我在项目中遇到过一个问题:按键能唤醒,但唤醒后触摸屏没反应。查了半天,发现是GPIO的wakeup能力没配好,中断虽然触发了,但对应的power domain没起来。嗯,这里要注意。
7.1.1 硬件层面:GPIO必须支持唤醒
不是所有GPIO都能当wakeup源。你得确认这颗GPIO在SoC的wakeup controller里。比如高通平台,只有TLMM_GPIO里标记了WAKEUP属性的引脚才行。
我曾经踩过一个坑:选了个普通GPIO,结果suspend后电平变化根本触发不了中断。后来查datasheet才发现,这颗引脚没有wakeup能力。
7.1.2 软件配置:三步走
- 设备树里配好:
wakeup-source属性必须加。 - 驱动里注册irq时设flag:
IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND。 - 用户空间验证:读
/sys/kernel/debug/wakeup_sources确认。
核心代码示例(设备树)
gpio_keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&key_pwr_default>;
power_key {
label = "Power Key";
gpios = <&tlmm 42 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
gpio-key,wakeup; // 关键:标记为wakeup源
debounce-interval = <10>;
};
};
避坑指南:我曾经在某个项目里忘了加gpio-key,wakeup属性,结果按键能响应,但suspend后按了没反应。查了两天才发现是设备树漏了。记住,设备树里必须显式声明。
7.2 RTC定时唤醒:让设备自己醒来
RTC唤醒,说白了就是定个闹钟。系统进入suspend后,RTC硬件还在跑,到点就触发中断,把系统拉起来。
这个场景在IoT设备里特别常见。比如智能门锁,每天凌晨3点上报一次状态,然后继续睡。你想想看,如果不用RTC唤醒,就得让CPU一直跑着,功耗直接翻倍。
7.2.1 RTC驱动要点
Android里RTC驱动一般走alarmtimer框架。用户空间通过/dev/alarm或者timerfd来设置唤醒时间。
我建议你直接看drivers/rtc/rtc-pm8xxx.c这个驱动,高通平台的RTC实现很典型。
7.2.2 用户空间设置示例
// 使用alarmtimer设置10秒后唤醒
#include <linux/alarmtimer.h>
struct itimerspec spec;
spec.it_value.tv_sec = 10;
spec.it_value.tv_nsec = 0;
spec.it_interval.tv_sec = 0;
spec.it_interval.tv_nsec = 0;
timerfd_settime(fd, TFD_TIMER_ABSTIME, &spec, NULL);
注意:RTC唤醒时间不能设得太短。我见过有人设了1秒唤醒,结果系统刚suspend就被唤醒,根本睡不下去。建议至少设5秒以上,给系统留足进入suspend的时间。
7.3 网络数据包唤醒:最难搞的唤醒源
网络唤醒,这是三个里面最复杂的。为什么?因为网络协议栈太深了,从物理层到应用层,每一层都可能丢包或者误唤醒。
我记得有一次做智能音箱项目,要求WiFi待机时能收到唤醒包。结果发现,路由器发的ARP包都能把设备唤醒,一晚上被唤醒了上百次。这功耗,直接崩了。
7.3.1 WoWLAN(Wake on Wireless LAN)
WiFi芯片一般支持WoWLAN功能。说白了,就是网卡在suspend时还保持部分电路工作,收到特定模式的数据包就触发中断。
配置要点:
- 网卡驱动必须支持:
iw phy0 wowlan enable magic-packet - Android框架层:
WifiManager.setWifiEnabled(true)并保持WIFI_MODE_FULL_HIGH_PERF - 内核配置:
CONFIG_PM_WAKELOCKS和CONFIG_WLAN要打开
7.3.2 实战:只允许特定UDP包唤醒
我建议的做法是,在网卡驱动里注册一个ndo_set_wakeup_filter回调,只匹配特定端口和IP的数据包。
static int my_ndo_set_wakeup_filter(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
// 只允许UDP 1234端口的数据包唤醒
if (wol->wolopts & WAKE_UCAST) {
// 设置硬件过滤规则
set_wakeup_filter(dev, UDP_PORT, 1234);
}
return 0;
}
核心原则:网络唤醒的过滤规则越严格越好。我曾经见过一个产品,因为过滤规则太宽,被广播风暴活活唤醒到没电。最后改成只匹配特定MAC+特定端口,才解决问题。
7.4 三种唤醒源对比
| 唤醒源 | 延迟 | 功耗影响 | 调试难度 | 典型场景 |
|---|---|---|---|---|
| GPIO按键 | <10ms | 极低(uA级) | 低 | 物理按键、传感器触发 |
| RTC定时 | <50ms | 低(uA级) | 中 | 定时上报、闹钟 |
| 网络数据包 | 100ms~1s | 中(mA级) | 高 | 远程唤醒、推送服务 |
7.5 调试工具与技巧
最后,分享几个我常用的调试命令。这些命令能帮你快速定位唤醒源问题。
- 查看当前wakeup源统计:
cat /sys/kernel/debug/wakeup_sources - 查看最后一次唤醒原因:
cat /sys/power/wakeup_last_time - 查看suspend失败原因:
dmesg | grep "PM: suspend" - 查看RTC唤醒设置:
cat /proc/driver/rtc
个人经验:我调试网络唤醒时,最喜欢用tcpdump抓包配合wakeup_sources一起看。先确认有没有收到包,再确认包有没有触发唤醒。很多时候,问题出在驱动过滤规则上,而不是硬件。
好了,这三种唤醒源,你只要动手调一遍,基本就能摸清Android电源管理的门道。GPIO最简单,RTC最稳定,网络最折腾。我个人建议,先从GPIO入手,再试RTC,最后啃网络唤醒。这样循序渐进,不容易被劝退。