3. 低功耗蓝牙Mesh节点架构
好,咱们进入第三讲。前两章我们把低功耗的整体框架和节点类型聊清楚了。这一章,我打算聚焦在LPN(低功耗节点)最核心的一个行为上——周期性唤醒。
说白了,LPN为什么能省电?就是因为它大部分时间都在睡觉。但睡了总得醒,醒了得跟朋友(Friend节点)打个招呼,问问有没有人找我。这个“醒多久、隔多久醒一次”的节奏,就是由PollTimeout来控制的。
核心概念一句话:PollTimeout是LPN两次连续“拉取”操作之间的最大时间间隔。它直接决定了节点的功耗水平和响应延迟。
3.1 PollTimeout到底是什么?
我刚开始接触蓝牙Mesh时,看到“PollTimeout”这个词,第一反应是“超时重传”。其实不是。在Mesh低功耗场景里,PollTimeout指的是LPN主动去Friend节点那里“取消息”的周期。
你想想看,LPN睡着的时候,Friend节点会帮它缓存所有发往LPN的消息。LPN醒来后,会发送一个Poll消息给Friend,Friend收到后就把缓存的消息一股脑发给LPN。这个Poll消息的发送间隔,就是PollTimeout。
嗯,这里要注意:PollTimeout不是一个固定值,而是一个范围。协议栈里通常用三个参数来描述它:
| 参数 | 含义 | 我的理解 |
|---|---|---|
| PollTimeout最小值 | 两次Poll之间最短间隔 | 决定了最快响应速度 |
| PollTimeout最大值 | 两次Poll之间最长间隔 | 决定了最差响应延迟 |
| 实际PollTimeout | 设备当前采用的间隔 | 可以在范围内动态调整 |
我在一个智能照明项目里就踩过这个坑。当时把PollTimeout设成了固定的10秒,结果用户按开关后,灯要等10秒才反应。用户体验极差。后来改成动态调整,有人活动时缩短到1秒,没人时拉长到30秒,问题才解决。
3.2 周期性唤醒的完整流程
一个完整的唤醒-拉取-睡眠周期,其实包含好几个步骤。我习惯把它画成一张流程图,这样大家理解起来更直观。
这张图我画得比较细。大家注意几个关键点:
- 睡眠状态:MCU和射频都关掉,只留一个极低功耗的定时器在跑。这个定时器就是PollTimeout的“闹钟”。
- 唤醒阶段:闹钟响了,MCU起来,初始化射频、同步时钟。这个阶段耗时一般在1-3毫秒,取决于芯片。
- Poll消息:LPN发送一个空包给Friend,告诉它“我醒了,有消息给我吗?”
- 接收响应:Friend回复,要么是“没有消息”,要么是“有,接好了”。
- 循环接收:如果有消息,LPN会继续接收,直到Friend说“没了”。
- 再次睡眠:所有消息处理完,LPN关射频、关外设,再次进入睡眠。
我的经验:整个唤醒-拉取-睡眠周期,理想情况下应该控制在5-10毫秒内。如果超过20毫秒,说明你的协议栈配置或者硬件初始化有问题,需要排查。
3.3 PollTimeout的取值策略
PollTimeout设多少合适?这个问题没有标准答案。它取决于你的应用场景。我给大家整理了一个参考表:
| 应用场景 | 推荐PollTimeout | 平均功耗(估算) | 响应延迟 |
|---|---|---|---|
| 智能灯控(有人场景) | 100ms - 500ms | ~500μA | < 200ms |
| 传感器数据上报 | 1s - 5s | ~100μA | 1-5s |
| 门锁/窗磁 | 5s - 30s | ~20μA | 5-30s |
| 温湿度传感器(低频) | 60s - 600s | < 5μA | 1-10分钟 |
看到没?PollTimeout从100毫秒到10分钟都可以。但功耗差了整整两个数量级。我个人习惯是:先满足响应需求,再优化功耗。不要一上来就追求极致省电,结果产品用起来卡得要死。
3.4 动态PollTimeout调整
固定PollTimeout虽然简单,但不够灵活。我在实际项目中更常用的是动态调整策略。说白了,就是让LPN根据当前场景自动调整唤醒频率。
举个例子:
// 伪代码:动态PollTimeout调整逻辑
void lpn_poll_timeout_adjust(void)
{
if (is_user_active()) {
// 用户正在操作,需要快速响应
set_poll_timeout(200); // 200ms
} else if (has_pending_data()) {
// 有数据等待处理,中等频率
set_poll_timeout(1000); // 1s
} else {
// 空闲状态,低频轮询
set_poll_timeout(5000); // 5s
}
}
这段代码看着简单,但实际落地时有很多细节。我曾经在一个项目中,动态调整时忘了考虑“状态切换的平滑性”。结果PollTimeout在200ms和5s之间来回跳,导致Friend节点那边频繁建立和释放连接,功耗反而更高了。
避坑指南:动态调整PollTimeout时,建议加一个“滞回区间”。比如从高频切到低频时,先切到1s过渡一下,再切到5s。不要一步到位,否则容易引起协议栈状态机混乱。
3.5 PollTimeout与Friend节点的配合
LPN的PollTimeout不是自己说了算的。它需要和Friend节点协商。在建立Friendship时,LPN会告诉Friend自己的PollTimeout范围。Friend会根据自身能力,给出一个双方都能接受的值。
这里有个关键点:Friend节点也有自己的资源限制。它需要为每个LPN缓存消息,如果LPN的PollTimeout太长,Friend就要缓存更久,占用的内存就更多。所以有些Friend节点会强制要求LPN的PollTimeout不能超过某个上限。
我记得在一个项目中,Friend节点用的是资源受限的MCU,RAM只有16KB。结果接了20个LPN,每个PollTimeout设成10分钟。Friend节点直接内存溢出,系统崩溃。后来把PollTimeout上限设为30秒,问题才解决。
3.6 实际项目中的调优建议
最后,我给大家几条实战建议:
- 先测基线功耗:在固定PollTimeout下,测出LPN的睡眠电流、唤醒电流、平均电流。有了基线,才知道优化空间有多大。
- 关注唤醒时间占比:如果唤醒时间占整个周期的比例超过5%,说明你的唤醒过程太长了。检查一下射频初始化、时钟稳定这些环节。
- 善用“跳过”机制:如果LPN知道自己没有数据要发,可以跳过某些Poll周期。协议栈支持这个功能,但很多开发者不知道。
- 不要忽略Friend的负载:LPN的PollTimeout越短,Friend的负载越重。在大规模部署时,要平衡好两边的资源。
总结一下:PollTimeout是LPN低功耗设计的核心参数。它决定了功耗和延迟的平衡点。动态调整策略可以让设备在不同场景下自动切换,但要注意平滑过渡和Friend节点的承受能力。下一章我们会深入聊Friend节点的缓存机制,看看Friend是怎么帮LPN“看门”的。