ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WiFi PSM低功耗实战:AP6256待机电流优化与调参指南

WiFi PSM低功耗实战:AP6256待机电流优化与调参指南

做电池供电的联网设备,最怕的不是模块发热,而是设备什么都不干的时候电在悄悄跑。我之前接手过一款采用AP6256 WiFi/BT combo模块的设备,整机待机电流怎么都降不下来,后来一路查到WiFi端才发现,模块一直处于常接收状态,根本没进低功耗模式。真正把待机电流里的这部分拽出来的,就是本文要讲的低功耗模式PSM(Power Save Mode)。PSM的核心理念很朴素:让WiFi模块在没有业务数据时进入睡眠,由AP端暂存发往设备的帧,等约定的唤醒时间到了再统一接收。这篇文章不只讲命令怎么敲,还会把机制、实测和踩坑都摊开说,适合正在调低功耗设备,或者想搞懂“模块为什么睡了还掉电”的开发者。

1. 低功耗模式不是“关掉无线”,而是“错峰接收”

1.1 WiFi的空闲功耗为什么居高不下

很多人第一次看到AP6256的电流数据时都会问一个问题:明明设备上没有任何数据在传,为什么模块还是吃掉了几十毫安?原因在于WiFi接收链路只要保持连接就必须全时工作。射频前端要常开着去听信道上的前导码,ADC要持续采样,基带要不停解码,MAC层还要维护各种定时器。即便一个广播帧都不收,只要没有进入省电状态,模块就处在常接收模式下,接收机始终满负荷运转。

这和蓝牙低功耗不太一样。蓝牙在没有连接事件时可以把接收机完全关闭,WiFi则要面对AP随时可能下发的数据,同时又不能错过beacon,所以省电机制只能靠“与AP协商好了时间再睡”来实现。AP6256的PSM本质上就是这套协议栈在模块固件中的落地:模块告诉AP“我要睡了”,AP把发给它的数据先存起来,等beacon里出现TIM标志时模块再醒来领取。

WiFi空闲功耗高还有一个隐蔽来源:广播和组播流量。在办公环境或智能家居场景里,AP持续下发ARP、mDNS、SSDP这类广播包,每来一个包接收机就要解调一次。很多设备在家庭网络里根本不知道自己是被人频繁唤醒的。PSM打开之后,这些广播包不再逐包唤醒设备,而只在DTIM周期统一处理,这就是功耗能降下来的直接原因。

1.2 AP6256把省电做进固件,但决定权不在它

AP6256是WiFi和蓝牙二合一模块,SDIO接口连接主控SoC。模块固件本身已经实现了802.11协议规定的省电状态机,也集成了电源管理相关的硬件逻辑,比如可编程的睡眠时钟、可以关闭的射频前级,以及用于和蓝牙共存调度的控制通道。

但模块支持PSM,不代表设备出厂就是省电的。终端侧的完整链路至少包括三部分:AP路由器是否在beacon中正确处理TIM和缓存、主控SoC的WiFi驱动是否正确向cfg80211上报省电能力、以及应用层有没有在空闲时主动把模块放进省电状态。换句话说,AP6256的能力是“上限”,系统能把设备用到哪一档省电,决定权在驱动配置和应用设计上。

我见过不少项目,模块手册里明明写着支持低功耗,可系统默认没打开PSM,于是整机功耗高了一倍。问题不在模块,而在默认配置。所以后面所有操作的第一步,都是先确认自己当前到底处于哪种模式。

1.3 为什么不能用“断开重连”替代PSM

有些刚接触低功耗的开发者会想:既然无线空载也耗电,那我空闲时直接断开关联,需要时再连接不就行了?从功耗数字上看,断开后模块确实能回到很低的待机电流,省电效果比PSM更激进。但这个方案的问题在于“重新连接”的时间和连接可用性。

重新关联要经历扫描信道、认证、关联、DHCP或重新获取IP等流程,顺利的话也要几百毫秒,如果AP忙或信道干扰严重,上秒到几秒都很常见。对于每30秒上报一次心跳的传感器,这可以接受;对于需要随时能被云端唤醒的摄像头、门锁或者语音入口设备,这就完全不可接受。用户发出一个指令后,设备要花两秒钟才上线,体验会非常差。

PSM的好处是连接一直在,模块只是暂时休眠。AP侧认为设备仍然在线,TCP/UDP会话保持,应用层的心跳周期也不用改。代价是数据会在AP缓存一会儿,延迟会略微升高,但不会断连。这是“断开重连”替代不了的。

2. PSM的三种状态与一次完整唤醒协商

2.1 CAM、PS-Poll、UAPSD三档省电模式

802.11协议族里,省电不是单指一个开关,而是分了好几档能力。理解这三档,后面调参数才有方向。

模式模块状态数据接收方式适用场景
CAM(常接收)永远清醒随时接收,延迟最低视频通话、高吞吐传输
PS-Poll(省电轮询)周期性睡眠醒来后用PS-Poll向AP逐帧索要缓存数据低频上报、待机保活
UAPSD(WMM省电)按接入类别调度客户端发触发帧,AP一次性下发该队列数据语音等对延迟有要求的小流量场景

CAM不需要解释,开着就是现在的状态。PS-Poll是WiFi最传统的省电方式:模块睡了,AP把发给它的单播帧缓存起来,然后在beacon帧的TIM字段里标记“这个设备有数据”。模块醒来看到标记,发送一个PS-Poll控制帧,AP就把缓存里的一帧数据发下来;如果还有缓存,模块继续发PS-Poll直到取完。

UAPSD可以理解成“升级版PS-Poll”。它把流量按QoS类别分成VO、VI、BE、BK四个队列,让设备在某一类队列有触发帧时统一下行,而不是一帧一帧地轮询。对于语音这类周期性小包,UAPSD能明显减少唤醒次数和握手开销。AP6256这类同时支持WiFi和蓝牙的模块,UAPSD的调度还会和蓝牙时隙做协调,拿到低延迟的同时不至于让蓝牙断流。

2.2 beacon和TIM/DTIM是整套机制的“闹钟”

PSM要正常工作,模块必须知道什么时候醒来。这个“闹钟”就是AP周期性广播的beacon帧。AP默认每100ms发送一次beacon,里面携带一个叫TIM(Traffic Indication Map)的字段,用bitmap方式记录哪些关联设备在AP侧有缓存数据。设备睡眠时登记自己的AID位,醒来收到beacon后检查自己对应的bit是否为1。

DTIM是TIM的一种特殊周期。每隔几个beacon,AP会发送一个DTIM beacon,除了普通TIM,还专门指示广播和组播有没有被缓存。因为广播/组播没有单一目标地址,不能像单播那样通过PS-Poll单帧索取,只能统一在DTIM后的时隙广播下发。设备想收到组播,就必须在DTIM beacon时刻醒来,错过了这一轮就不再补发。

这也直接解释了一个常见现象:开启PSM后,网络里的mDNS和ARP消息经常延迟几百毫秒才到。它们都跟着DTIM周期走。如果AP的DTIM间隔是3,设备大约每300ms才醒来一次,延迟自然就上去了。

2.3 PS-Poll的完整数据领取流程

把一次“睡眠状态下收到数据”的过程串起来,更容易理解为什么ping值会变化。假设设备已经进入PS-Poll省电模式,某时刻手机向设备发送一条TCP数据:

  1. 数据帧到达AP,AP发现目标设备正在睡眠,于是把帧放入缓存队列,并不立即发送。
  2. 到了下一个beacon发送时间,AP在beacon帧的TIM字段中把该设备的AID bit位置1。
  3. 设备在自己的唤醒时间醒来,收到这个beacon,发现自己有缓存帧。
  4. 设备发送PS-Poll控制帧,主动向AP索取帧。
  5. AP收到PS-Poll后,把缓存队列中的第一帧发给设备。
  6. 如果队列还有剩余,AP会继续在TIM字段置位,设备继续发送PS-Poll,直到清空。
  7. 清空后设备重新进入省电睡眠状态,等待下一个beacon周期。

这个过程中,“数据到达AP”和“设备真正收到数据”之间至少隔了一个beacon间隔。如果beacon是100ms一个,那ping延迟就会增加约100ms;如果在深睡眠时listen interval设得更大,等好几个beacon才醒一次,延迟会更高。这不是网络故障,是省电机制本身的取舍方向。

3. 在Linux系统里打开PSM的完整操作链路

3.1 前置检查:固件和驱动已经按预期工作

网上的教程会让你直接敲iw dev wlan0 set power_save on,但如果你连驱动固件都没加载好,这个命令只会返回错误,或者干脆“执行成功但不生效”。所以动手前先做一次基础检查,确认整条链路是通的。

在Linux终端里执行:

dmesg | grep -E "brcmfmac|mmc|wlan"

重点看几行:无线芯片是否被mmc子系统成功枚举、固件是否加载完成、有没有失败信息。如果驱动是内核自带的,通常能看到类似“Firmware version”的打印。

接着查看网络接口是否创建,以及硬件能力枚举情况。对于cfg80211时代的驱动,可以这样看:

iw list | grep -A20 "Device supports"

这里会列出硬件支持的特性。只要接口能正常连接AP,PSM相关的cfg80211开关就具备前提条件。如果固件加载都有问题,那就不是本文能解决的了,得先回到驱动移植和固件配套上。

3.2 使用cfg80211标准接口控制PSM开关

AP6256在Linux下有标准cfg80211/mac80211或者厂商私有驱动。大多数场合,用户态不需要直接操作固件寄存器,用标准工具就够了。查看当前模式:

iw dev wlan0 get power_save

返回结果通常是:

Power save: on

或者“Power save: off”。如果是off,就按下面打开:

sudo iw dev wlan0 set power_save on

再查看一次确认生效。这条命令的本质是把“允许硬件进入省电模式”这一位配置到驱动内部,驱动再通过固件命令下发给AP6256固件。协议细节并不需要用户关心。

关闭是同样的命令。执行下面的命令后,模块回到CAM状态,后续数据不再走AP缓存,延迟会立竿见影降回来,但功耗也会马上抬到之前的水平。只有确认业务允许“随时保持全速接收”时,才建议这样做:

sudo iw dev wlan0 set power_save off

有一点需要注意:iw只是临时设置,重启网络接口或者系统后可能会回到默认值,需要配合开机脚本使用。

3.3 重启后依然生效的配置方法

对于产品化项目,临时命令显然不够。我常用的做法是写一个脚本,在网络接口就绪后自动设置。如果系统里有网络管理服务,可以在连接事件里挂钩子;没有的话,用系统服务方式也能满足大部分需求。

一个最小化的系统服务单元示例:

[Unit] Description=Set WiFi Power Save Mode After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/wifi_psm.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target

脚本内容可以写得很简单:

#!/bin/bash IFACE="wlan0" sleep 2 iw dev $IFACE set power_save on

把服务enable后,可以实现开机自动进入省电模式。如果项目里有应用层想吃这个开关,我更推荐在应用初始化阶段直接调用socket或ioctl,而不是依赖shell脚本,因为shell脚本对依赖顺序更敏感,而且每次切换还要fork一个进程。

3.4 私有驱动参数和固件工具的边界

除了标准iw,厂家SDK里通常还会提供私有工具,用来查询、设置信令、射频参数、省电模式细节等。切到这类工具时要非常小心。它们往往直接操作固件私有ioctl,不会对参数做太多合法性校验,一旦写错值,轻则功能异常,重则需要重新加载固件才能恢复。

在AP6256的平台上,我自己更倾向于“能用标准接口就不用私有工具”的原则。省电开关用iw,需要细调的地方,比如监听间隔、UAPSD能力位,再对照原厂固件文档去改。驱动开发者通常会在代码里预留模块参数,相关配置可以通过模块加载参数或设备树节点传入。不同SDK差异很大,没有统一命令,所以不在这里写具体参数名,动手前务必先读驱动源码。

4. 实测对比:省下来的电流和付出的代价

4.1 测试环境与测量方法

实测这块我用的是一个电池供电的ARM开发板,上面挂着AP6256模块,路由器选择普通家用双频AP,信道固定在2.4G频段的6号信道,避免自动信道切换干扰数据。测量功耗时用电流探头记录模块供电轨上的电流波形,软件层面用ping看延迟变化,用iperf3看吞吐。

测试分成三组场景:纯待机(不产生任何应用流量,但保持关联)、低流量心跳(每30秒一次TCP小包)、持续吞吐(iperf3上行)。每组先在PSM off状态下跑10分钟,再用同样方法在PSM on状态下跑10分钟,然后对比平均值。

需要说明的是,这套数据只代表我手头这一套系统和模块的表现。不同主控的电源设计、固件版本、路由器型号都会让绝对值漂移,但相对趋势基本一致。

4.2 待机、低流量、全速三种场景的功耗数据

场景PSM关闭平均电流PSM开启平均电流PSM关闭pingPSM开启ping
纯待机约75mA约18mA2-3ms30-80ms
30秒心跳约72mA约26mA2-3ms15-40ms
持续上行吞吐约180mA约175mA2-3ms3-5ms

最明显的变化在纯待机场景:电流直接降到了一半以下。这就是PSM真正打开的价值。心跳场景因为每30秒就有一个UDP小包把模块唤醒一次,睡眠窗口被打碎,省电效果被削弱,但仍然有显著收益。

持续吞吐场景几乎看不出区别。这一点也好理解:全速传输时模块本来就没有睡眠间隙,PSM开关对平均电流基本没有影响。所以不要指望靠PSM在高负载场景省电。

延迟方面,打开PSM后ping确实从毫秒级别涨到了几十毫秒,但这是“加了缓存转发”的必然结果,不是故障。如果业务能容忍几十毫秒的闲置延迟,PSM就是值得开的。

4.3 开启PSM后延迟增加的物理过程

有人看到ping 80ms就会在群里喊“模块有问题”。其实不是模块的问题,而是睡眠周期和beacon周期叠加出的排队时延。前面已经分析过,数据在AP侧先被缓存,等待beacon中的TIM标记,再等待设备醒来发送PS-Poll,之后才开始传输。这里有两个等待环节。

第一个等待是beacon周期。如果设备睡眠时把自己的listen interval设成3,那么它每3个beacon才醒一次,也就是300ms左右。外界发来的包到达AP后,最坏情况下要等300ms才能在TIM里被标记。第二个等待是数据取回过程。如果AP缓存队列里还有别的帧,或者PS-Poll丢失重发,还要再叠加一些毫秒级重传延迟。

所以看到几十毫秒到一百多毫秒的ping波动,不要急着换模块,先去查beacon间隔和DTIM间隔。AP参数不变,模块再怎么优化都绕不过睡眠唤醒周期。

4.4 一个反直觉现象:小流量频繁唤醒反而更耗电

我在项目里遇到过一种奇怪情况:开启PSM之后,整机电流不仅没降,反而比关闭时高出十几毫安。后来抓波形才明白,是设备上运行的某个App每500ms就发送一次统计数据,WiFi模块刚刚睡下去,就被这个包唤醒,醒来后做完PS-Poll、确认没有更多数据,又再次睡下。

睡眠和唤醒的动作本身有开销:关闭接收机、切换到睡眠时钟、再唤醒锁相环、扫描缓存、接收beacon。这个转换过程的功耗可能是一瞬间的大电流尖峰。如果唤醒频率太高,平均功耗就会被一大堆尖峰拉起来,反而高于一直保持CAM模式。

这个现象提醒我们:PSM适合的是“大部分时间真的没事可做”的业务。后台任务如果每几百毫秒就来一次心跳,就得先把业务频率降下来,或者把一些小包合并发送,否则低功耗模式只是把功耗从“平稳消耗”换成了“脉冲式消耗”。

5. 踩坑复盘:PSM落地时遇到的四个典型问题

5.1 问题一:ping延迟突然飙到几百毫秒

最早我开启PSM后,设备表现为被远端ping时延迟在200ms到500ms之间跳动,偶尔还会丢一个包。第一反应是信道频率是不是调错了,但AP和信道都没动过。查了一遍流程,才意识到问题出在路由器把DTIM间隔设成5,beacon周期100ms,意味着广播/组播每500ms才集中下发一次。

我做的第一步是从设备端ping网关,排除AP上联问题。第二步是看路由器管理页面里的beacon间隔和DTIM间隔。确认DTIM=5后,把DTIM改成1,延迟立刻从几百毫秒降到30-50ms左右。虽然还是比关闭PSM高,但已经满足业务要求。

如果遇到延迟高,先别急着把PSM关掉。先查AP的DTIM和beacon参数,再查驱动的listen interval设置。这三者的乘积基本决定了闲置延迟上限。AP和驱动都不动,单靠模块永远无法绕过这个上限。

5.2 问题二:TCP长连接睡死了,被动唤醒失效

另一个典型问题:设备进入PSM睡眠后,云端下发一条控制指令,设备却迟迟没有反应。检查网络发现TCP连接还在,但数据一直没到达应用层,像连接“睡死”了一样。

排查链路从AP侧开始:在AP上抓包,发现云端数据确实到AP了,AP也发了beacon置位,但设备始终没有回复PS-Poll。再往设备端看,发现主控SoC进入低功耗状态后,睡眠时钟没有正确驱动WiFi模块的定时器唤醒,或者驱动的中断没有把主控唤起来。

这类问题在组合模块上尤其值得注意。因为系统级睡眠不止涉及WiFi模块,还涉及主控的电源域切换。如果模块的中断引脚没有接到能唤醒主控的GPIO上,模块醒来也没人知道。解决方向是确认中断唤醒配置、核对设备树的中断属性,以及在应用层保留一个较低频率的心跳作为保底机制,比如每30秒发一次TCP keepalive。这样即使一次被动唤醒失败,主动心跳也能把连接“戳”活。

5.3 问题三:组播和局域网发现协议经常失效

我发现设备在PSM开启后,局域网里的mDNS名字解析时好时坏,用语音设备控制它时经常第一次没反应,第二次就正常。问题的根源是mDNS使用组播发现,而组播在DTIM周期之外不缓存、不下发,错过就要等下一个DTIM周期。DTIM间隔拉开后,首次收到组播查询的概率就降低了。

解决思路有两个方向。应用层改成主动查询,不要等组播推送。比如设备主动向控制端发送单播的发现响应,或者每隔几秒自己发一个单播UDP包来“保活”。AP侧如果有参数,把DTIM间隔调小,或者开启“组播转单播”功能。这各有利弊,组播转单播会放大重复包数量,需要评估AP端的处理能力。

对于使用物联网协议的开发,尽量把控制信令放在TCP或UDP单播上,不要把关键寻址步骤寄托在组播上,这是PSM时代最常见的教训之一。

5.4 问题四:蓝牙一开,WiFi功耗曲线立刻抬升

AP6256是WiFi/蓝牙二合一模块,同时跑WiFi和蓝牙时,共存的调度会影响PSM效果。我在测试中发现,蓝牙耳机连接并播放音频时,WiFi的电流曲线不再干净地进入睡眠低电平,而是一直有小幅脉冲存在。原因是蓝牙在音频播放时需要频繁执行连接事件,协调逻辑需要安排WiFi的休眠时隙,避免两者的收发冲突。

解决办法不是关蓝牙,而是调整蓝牙侧的工作参数。把蓝牙的主动扫描频率降低、减少非必要的广播事件,可以让模块有更多连续睡眠窗口。WiFi侧能看到功耗脉冲变稀,蓝牙侧的音质也没有明显下降。

这类问题很难通过WiFi软件单独解决,需要在蓝牙协议栈里也做配合。所以做二合一模块的低功耗,不能只盯WiFi的PSM,蓝牙的广播周期和连接参数同样要纳入功耗预算。

6. 针对业务特征做PSM参数调整

6.1 先给业务画像,再决定用哪档省电

没有一套PSM参数能通吃所有业务,因为不同业务对延迟、吞吐、功耗的优先级完全不同。我在调参前习惯先把业务归一下类:

业务类型数据特征推荐省电策略
实时音视频连续双向流量,延迟敏感关闭PSM或使用UAPSD,保障时延
云端控制低频单包,偶尔唤醒开启PSM,listen interval适中
周期上报定时小包,可以等待开启PSM,把心跳频率降到合理值
纯待机保活长时间无数据开启PSM,拉大listen interval,配合DTIM

从表里能看到,省电不是全有全无,而是分档收窄。对延迟敏感的场景,UAPSD比PS-Poll更合适,因为它的触发机制更平滑,不会出现“每包都多等一个beacon”的尴尬。

6.2 可调的三个关键参数

第一个是beacon interval,通常在AP侧配置,默认100ms。设备侧无法单独修改,需要登录路由器管理页面调整,但很多路由器不开放这个参数。如果你能控制AP,建议保持100ms或更小,给PSM设备一个够用的唤醒频率。

第二个是listen interval,在STA关联请求中通告给AP,单位是beacon周期数。设备侧可以通过驱动或固件私有接口设置。该值越大,睡眠时间越长,功耗越低,但唤醒后的首包延迟也越大。经验值在3到10之间,太极端会导致AP主动踢掉设备。

第三个是UAPSD能力位。需要驱动在关联请求中启用WMM省电能力,并且AP也支持UAPSD。你可以通过抓关联请求帧查看能力位是否置位。通常默认由驱动决定,不建议在用户态强行修改。如果某台AP不支持UAPSD,设备会自动回退到PS-Poll或CAM。

6.3 运行时动态切换PSM策略的思路

最理想的方案是让PSM跟着业务节奏走,而不是固定一个值。我在实际项目里通常默认开启PSM,但保留一个运行时接口给应用层,业务进入实时交互时关闭PSM,交互结束后再打开。

用一个简单的bash包装函数:

function psm_set() { if [ "$1" = "on" ]; then iw dev wlan0 set power_save on else iw dev wlan0 set power_save off fi } # 音视频会议开始时调用 psm_set off # 音视频会议结束后调用 psm_set on

如果希望应用层更高效,可以直接在C代码里发起netlink消息调用cfg80211接口,效果和iw命令等价。这种方式避免反复fork外部进程,在频繁切换的场景下更稳定。

还可以根据信号强度做切换:信号弱的时候,模块本来就容易重传,如果再睡,重传概率更高,用户体验会更差。这种时候宁可牺牲功耗,也要先把链路保住。把类似策略放进自动化判断里,会比人肉调参靠谱很多。

最后说一点个人体会。很多人把PSM当成“一个开关”,打开了就省电,却忽略了它在协议、驱动、AP、业务节奏四个层面之间的耦合。我在实际项目里的习惯是默认打开PSM,同时留一个运行时接口,让应用层在实时业务时临时切回常接收模式。这样做既拿到了待机功耗的大头,又不会在关键交互时被几十毫秒的延迟坑到。AP6256这类组合模块的PSM潜力是够用的,真正决定设备续航的,往往是对业务节奏的把握和调参时的耐心。希望这篇实操记录能让你少踩几个我踩过的坑。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进