4. 广播与扫描的功耗优化
链路层,也就是咱们常说的 LL 层,是 BLE 协议栈里最核心的调度中心。我刚开始啃 BLE 协议时,觉得这层就是一堆状态切来切去,后来做项目才发现——不理解 LL 状态机,你连设备为什么连不上都查不出原因。
说白了,链路层负责两件事:控制设备的收发状态,以及定义空中传输的数据长什么样。咱们一个一个拆开讲。
4.1 链路层状态机
BLE 设备在任何时刻都处于以下五种状态之一:Standby、Advertising、Scanning、Initiating、Connection。状态之间按严格规则跳转,不能乱来。
核心原则:同一时刻,一个设备只能处于一种状态。但不同状态可以快速切换,比如广播完一包立刻切到扫描。
4.1.1 Standby(待机状态)
这是所有状态的起点和终点。设备不发送也不接收任何数据,射频模块关闭,功耗最低。我习惯把 Standby 看作「宿舍熄灯后」——啥也不干,省电。
- 从 Standby 可以进入 Advertising、Scanning 或 Initiating。
- 从其他任何状态都可以回到 Standby。
4.1.2 Advertising(广播状态)
设备在广播信道上周期性发送广播包。广播者(Advertiser)告诉周围:「我在这儿,快来连我!」
- 广播事件:每个广播事件包含一次或多次广播包发送,中间可能有监听窗口。
- 广播信道:37/38/39 三个专用信道,避开 Wi-Fi 干扰。
- 广播类型:可连接广播、不可连接广播、可扫描广播、定向广播。
避坑指南:我曾经在广播间隔设得太短(20ms),导致设备功耗飙升。后来改成 100ms,连接成功率没降多少,电池续航翻了一倍。广播间隔不是越短越好。
4.1.3 Scanning(扫描状态)
扫描者(Scanner)在广播信道上监听广播包。它不发送数据,只接收。
- 被动扫描:只收不发,不回复任何东西。
- 主动扫描:收到广播包后,发送扫描请求(SCAN_REQ),广播者回复扫描响应(SCAN_RSP)。
- 扫描窗口与扫描间隔:窗口决定每次监听多久,间隔决定多久监听一次。
你想想看,主动扫描就像你看到传单后主动问一句「还有更多信息吗?」——广播者会再给你一份详细版。
4.1.4 Initiating(发起连接状态)
这是连接前的最后一步。发起者(Initiator)在广播信道上监听特定设备的可连接广播包,收到后立即发送连接请求(CONNECT_IND)。
- 发起者只能连接一个设备。
- 连接请求包含后续通信的参数:连接间隔、跳频映射、CRC 初始值等。
- 一旦 CONNECT_IND 被确认,双方同时进入 Connection 状态。
注意:Initiating 状态和 Scanning 状态不能同时存在。发起者不能一边扫描一边发起连接——至少标准协议不允许。有些芯片厂商做了扩展,但那是私有实现。
4.1.5 Connection(连接状态)
连接建立后,设备角色变为 Master(主设备)和 Slave(从设备)。双方在数据信道上按约定的连接间隔定期收发数据。
- 主设备:负责发起连接事件,每次事件开始时发送数据包。
- 从设备:在连接事件中监听主设备的数据,收到后回复。
- 连接事件:每个连接间隔发生一次,主从交替收发。
我记得第一次调连接参数时,把连接间隔设成 7.5ms,结果两个设备疯狂丢包。后来查手册才知道,连接间隔最小值受芯片处理能力限制,不是想设多小都行。
4.1.6 状态跳转图
下面这张 SVG 图把五个状态和它们之间的跳转关系画清楚了。我建议你把它存下来,调试时对照着看。
4.2 链路层数据包格式
链路层的数据包,也叫 PDU(Protocol Data Unit),是空中传输的基本单位。我当年看协议文档时,被那一堆字段搞得头晕。后来自己画了个表格,才理清楚。
4.2.1 数据包整体结构
每个 LL 数据包由三部分组成:
| 前导码(Preamble) | 访问地址(Access Address) | PDU | CRC |
|---|---|---|---|
| 1 字节 | 4 字节 | 2~257 字节 | 3 字节 |
| 01010101 或 10101010 | 广播信道固定为 0x8E89BED6 | 包含头部和有效载荷 | 24 位 CRC 校验 |
小技巧:访问地址在广播信道是固定的,但在数据信道由连接请求中的 InitA 和 AdvA 计算生成。我曾经在抓包时看到访问地址不对,第一反应是硬件坏了,后来才发现是连接参数没配对。
4.2.2 PDU 头部格式
PDU 头部只有 2 字节,但信息量很大:
| 字段 | 位宽 | 说明 |
|---|---|---|
| PDU Type | 4 bits | 标识包类型:广播包、扫描请求、连接请求、数据包等 |
| RFU | 2 bits | 保留位,必须为 0 |
| ChSel | 1 bit | 信道选择算法版本 |
| TxAdd | 1 bit | 发送地址类型:0=公共地址,1=随机地址 |
| RxAdd | 1 bit | 接收地址类型 |
| Length | 8 bits | 有效载荷长度,单位字节 |
4.2.3 广播信道 PDU 类型
广播信道上的 PDU 类型有 7 种,我挑几个常用的说说:
- ADV_IND:可连接无定向广播,最常见的广播包。
- ADV_DIRECT_IND:定向广播,指定目标设备,连接速度快。
- ADV_NONCONN_IND:不可连接广播,用于 Beacon 等只发不收的场景。
- SCAN_REQ / SCAN_RSP:扫描请求与响应,主动扫描时使用。
- CONNECT_IND:连接请求,包含连接参数。
注意:ADV_DIRECT_IND 广播包不能包含任何数据,只能携带目标设备的地址。我见过有人想在定向广播里塞自定义数据,结果设备根本不响应——白费功夫。
4.2.4 数据信道 PDU 类型
连接建立后,数据信道上的 PDU 分为两类:
| 类型 | 说明 | 关键字段 |
|---|---|---|
| LL Data PDU | 承载应用层数据(L2CAP 数据) | LLID(标识数据起始/续传/控制)、SN(序列号)、NESN(下一期望序列号) |
| LL Control PDU | 链路层控制命令 | Opcode(命令码),如 LL_VERSION_IND、LL_CHANNEL_MAP_REQ |
数据包里的 SN 和 NESN 是实现可靠传输的关键。简单说:发送方每发一包,SN 翻转一次;接收方回复时用 NESN 告诉对方「我收到了哪一包」。如果 NESN 没变,发送方就知道要重传。
4.2.5 一个完整的广播包示例
咱们看一个实际抓包数据,我手写了一段伪代码来模拟:
// 一个典型的 ADV_IND 广播包
Preamble: 0xAA
Access Addr: 0x8E89BED6
PDU Header: 0x00 0x1E // PDU Type=0000 (ADV_IND), Length=30
AdvA: 0x112233445566 // 广播者 MAC 地址
AdvData: 0x02 0x01 0x06 // Flags: LE General Discoverable
0x03 0x03 0x0F 0x18 // 16-bit UUID: 0x180F (电池服务)
0x05 0x09 0x42 0x4C 0x45 0x00 // 设备名称: "BLE\0"
CRC: 0x123456 // 由 PDU 内容计算得出
嗯,这里要注意:广播包的有效载荷最大 31 字节,别想着塞太多数据。我有个项目想广播 40 字节的传感器数据,最后只能拆成两包发——但接收方得自己拼,麻烦得很。
4.3 链路层的关键机制
4.3.1 白名单(White List)
白名单是 LL 层的一个过滤机制。设备可以配置只响应或扫描特定 MAC 地址的设备。我习惯在量产时用白名单,防止隔壁工位的开发板干扰测试。
- 广播者:只接受白名单内设备的扫描请求或连接请求。
- 扫描者:只报告白名单内设备的广播包。
- 发起者:只连接白名单内的设备。
4.3.2 信道选择算法
BLE 使用跳频技术,在 37 个数据信道上跳来跳去,避免干扰。信道选择算法有两种:
- Channel Selection Algorithm #1:基于连接事件计数和跳频映射,简单但跳跃性差。
- Channel Selection Algorithm #2:基于伪随机数生成器,跳跃更均匀,抗干扰更好。
建议:如果你的设备工作在 Wi-Fi 密集环境(比如办公室),尽量用 Algorithm #2。我做过对比测试,在 2.4GHz 全频段干扰下,Algorithm #2 的丢包率比 #1 低 30% 左右。
4.3.3 连接参数更新
连接建立后,主从设备可以协商调整连接间隔、延迟和超时时间。这个过程通过 LL_CONNECTION_PARAM_REQ 和 LL_CONNECTION_PARAM_RSP 完成。
我遇到过一个问题:从设备请求把连接间隔从 50ms 改成 500ms,主设备直接拒绝了。后来发现是主设备的协议栈有最小连接间隔限制——改驱动才解决。
4.4 本章小结
链路层是 BLE 的调度核心,搞懂状态机就能理解设备「什么时候在干什么」。数据包格式虽然琐碎,但抓包调试时每个字段都可能成为突破口。我个人建议:
- 用逻辑分析仪或抓包工具(如 Ellisys、nRF Sniffer)看一次真实的广播包和连接包。
- 调试连接问题时,先确认双方是否处于正确的状态。
- 白名单和信道选择算法是优化功耗和抗干扰的利器,别忽略。