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 周期性唤醒的完整流程

一个完整的唤醒-拉取-睡眠周期,其实包含好几个步骤。我习惯把它画成一张流程图,这样大家理解起来更直观。

LPN周期性唤醒流程图 LPN周期性唤醒-拉取-睡眠流程图 睡眠状态(Deep Sleep) PollTimeout到期 唤醒并初始化RF 发送Poll消息 接收Friend响应 有缓存消息? 是 接收缓存消息 (可能多条) 否 进入睡眠 等待下一个PollTimeout

这张图我画得比较细。大家注意几个关键点:

  • 睡眠状态: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 实际项目中的调优建议

最后,我给大家几条实战建议:

  1. 先测基线功耗:在固定PollTimeout下,测出LPN的睡眠电流、唤醒电流、平均电流。有了基线,才知道优化空间有多大。
  2. 关注唤醒时间占比:如果唤醒时间占整个周期的比例超过5%,说明你的唤醒过程太长了。检查一下射频初始化、时钟稳定这些环节。
  3. 善用“跳过”机制:如果LPN知道自己没有数据要发,可以跳过某些Poll周期。协议栈支持这个功能,但很多开发者不知道。
  4. 不要忽略Friend的负载:LPN的PollTimeout越短,Friend的负载越重。在大规模部署时,要平衡好两边的资源。

总结一下:PollTimeout是LPN低功耗设计的核心参数。它决定了功耗和延迟的平衡点。动态调整策略可以让设备在不同场景下自动切换,但要注意平滑过渡和Friend节点的承受能力。下一章我们会深入聊Friend节点的缓存机制,看看Friend是怎么帮LPN“看门”的。