4、链路层(LL)核心机制:设备状态机、空中包格式、CRC校验
链路层,也就是我们常说的LL层,是蓝牙协议栈里最底层的核心。说白了,它负责两件事:设备怎么切换状态,以及数据怎么在空中飞。今天我们就来拆解这两个核心机制。
我个人习惯把LL层比作一个状态机驱动的收发器。你想想看,一个蓝牙设备不可能一直处于同一个状态——它得知道什么时候该广播、什么时候该扫描、什么时候该连接。这些状态切换,就是LL层状态机要干的事。
核心要点:LL层状态机定义了5种状态——Standby、Advertising、Scanning、Initiating、Connection。设备在任何时刻只能处于其中一种状态。
4.1 设备状态机:五种状态的切换逻辑
先看一张图,这是我用SVG画的LL层状态机流转图。你盯着看30秒,基本就能理解整个逻辑。
这张图里,Standby是初始状态,也是所有状态的归宿。设备上电后默认进入Standby,啥也不干,功耗最低。然后根据上层指令,跳转到Advertising、Scanning或Initiating。
嗯,这里要注意:Advertising和Scanning是两种完全不同的角色。Advertising是广播者,它主动往外发数据包;Scanning是扫描者,它被动监听广播包。Initiating呢?它其实是Scanning的升级版——扫描到特定设备后,主动发起连接请求。
我的经验:我在做智能手环项目时,遇到过一个问题——设备在Advertising状态下功耗居高不下。后来发现是广播间隔设置得太短了。BLE协议允许广播间隔在20ms到10.24s之间,我建议你根据实际场景灵活调整。比如手环在配对阶段用100ms间隔,连接后切到1s间隔,能省不少电。
4.2 空中包格式:LL层数据包长什么样
空中包格式,说白了就是蓝牙设备之间通信的语言。BLE的空中包分为两种:广播包和数据包。它们的结构略有不同,但核心框架是一样的。
先看广播包的结构:
广播包格式(PDU):
| 前导码(1B) | 访问地址(4B) | PDU头(2B) | 有效载荷(0-37B) | CRC(3B) |
PDU头细节:
| PDU类型(4bit) | RFU(1bit) | 信道选择(1bit) | TxAdd(1bit) | RxAdd(1bit) | 长度(8bit) | RFU(8bit) |
数据包格式呢?其实差不多,只是PDU头里多了个LLID字段,用来区分是数据包还是控制包。
数据包格式(PDU):
| 前导码(1B) | 访问地址(4B) | PDU头(2B) | 有效载荷(0-251B) | MIC(4B, 可选) | CRC(3B) |
PDU头细节:
| LLID(2bit) | NESN(1bit) | SN(1bit) | MD(1bit) | RFU(3bit) | 长度(8bit) |
这里有个关键点:访问地址。广播包的访问地址是固定的0x8E89BED6,而数据包的访问地址是在连接建立时由发起方随机生成的。我曾经在调试时发现两个设备连不上,查了半天,结果是访问地址被意外覆盖了——这种坑,你遇到了就知道有多头疼。
避坑指南:我曾经在开发一款蓝牙耳机时,发现数据包偶尔会丢包。排查后发现是PDU头里的长度字段写错了——多写了1个字节,导致接收方解析时CRC校验失败。记住:长度字段必须与实际有效载荷一致,多一个字节都不行。
4.3 CRC校验:保证数据完整性的最后一道防线
CRC校验,全称是循环冗余校验。BLE协议用的是24位CRC,生成多项式是0x100065B。说白了,就是发送方对数据包算出一个24位的校验值,接收方收到后重新算一遍,如果对不上,说明数据在传输过程中被破坏了。
CRC的计算过程是这样的:
BLE CRC生成多项式:x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1
对应二进制:0x100065B
计算步骤:
1. 初始化CRC寄存器为0x555555
2. 对PDU头 + 有效载荷(+ MIC,如果有)逐字节处理
3. 每字节从LSB开始移位计算
4. 最终结果取反,得到24位CRC值
你可能会问:为什么初始化值是0x555555而不是全0?嗯,这是BLE协议的规定,目的是防止全0的数据包产生全0的CRC,增加随机性。
核心要点:CRC校验覆盖的范围是PDU头 + 有效载荷,不包含前导码和访问地址。如果数据包启用了加密(即包含MIC字段),CRC也要覆盖MIC。
我在实际项目中,曾经遇到过CRC校验一直失败的情况。排查后发现是字节序问题——BLE协议规定CRC计算时按小端序处理,但我的代码里用了大端序。这种细节,文档里写得很清楚,但写代码时一不留神就踩坑了。
4.4 状态机与空中包的联动:一个完整的连接建立过程
光讲理论没意思,我们来看一个实际场景:智能手环与手机配对。这个过程完美展示了状态机与空中包的联动。
- 手环进入Advertising状态:手环上电后,从Standby跳转到Advertising。它开始在3个广播信道(37、38、39)上轮流发送广播包。广播包里包含设备名称、服务UUID等信息。
- 手机进入Scanning状态:手机打开蓝牙后,进入Scanning状态。它在广播信道上监听,收到手环的广播包后,解析出设备信息。
- 手机进入Initiating状态:用户点击配对后,手机从Scanning切换到Initiating。它向手环发送一个
CONNECT_REQ包,这个包里包含了连接参数(如连接间隔、延迟等)。 - 双方进入Connection状态:手环收到
CONNECT_REQ后,解析并确认参数,然后双方同时进入Connection状态。从此,它们在数据信道上通信,使用数据包格式。 - 断开连接:当用户取消配对或手环远离时,任意一方发送断开连接指令,双方回到Standby状态。
你看,整个过程就是状态机在驱动,每个状态切换都伴随着特定格式的空中包交换。CRC校验则贯穿始终,确保每个包都完整无误。
我的建议:如果你刚开始接触BLE开发,我建议你先用抓包工具(比如Ellisys或nRF Sniffer)抓一次完整的连接建立过程。亲眼看到广播包、CONNECT_REQ包、数据包在空中的样子,比看十遍文档都管用。我自己就是这么入门的。
4.5 总结:LL层核心机制的三板斧
好了,我们来捋一捋今天讲的内容。LL层核心机制,说白了就是三件事:
- 状态机:Standby、Advertising、Scanning、Initiating、Connection五种状态,设备只能处于其中一种。状态切换由上层指令或收到特定包触发。
- 空中包格式:广播包和数据包两种格式,核心结构是前导码 + 访问地址 + PDU头 + 有效载荷 + CRC。广播包访问地址固定,数据包访问地址随机生成。
- CRC校验:24位CRC,生成多项式0x100065B,初始化值0x555555。覆盖PDU头和有效载荷,是数据完整性的最后一道防线。
我个人觉得,LL层是蓝牙协议栈里最值得花时间啃的部分。你把它搞懂了,上层协议(比如GAP、GATT)的理解就会水到渠成。嗯,今天就到这里,下一章我们聊聊连接参数的具体配置——那个东西调不好,功耗和延迟都会出问题。
公众号:蓝海资料掘金营,微信deep3321