4. BLE协议栈架构与空口包结构

做低功耗蓝牙开发,我见过不少工程师一上来就调应用层代码,遇到连不上、断连、丢包就抓瞎。说白了,你不懂协议栈怎么分层、空口包长什么样,抓包数据摆在你面前也看不懂。这一章,我们就从底层往上捋一遍。

4.1 BLE协议栈分层

BLE协议栈分三层:Controller、Host、Application。Controller跑在MCU的射频内核里,Host跑在主核或OS上,Application就是你的业务代码。我习惯把Controller比作「邮局」,Host比作「信件分拣中心」,Application就是「写信的人」。

层级 核心子层 我的理解
Controller PHY、LL 负责发比特、收比特、状态机、重传
Host L2CAP、ATT、GATT、SM 负责拆包、组包、属性读写、安全配对
Application Profiles、Services 你的业务逻辑,比如心率、温湿度

这里重点说几个我经常在抓包时关注的子层。

4.1.1 LL(Link Layer)

LL是BLE的「心脏」。它管理五个状态:Standby、Advertising、Scanning、Initiating、Connection。我在项目中遇到过最典型的坑:设备在Advertising状态待太久,功耗下不来。后来发现是LL的Advertising间隔设得太短,导致射频一直开着。你想想看,间隔20ms和100ms,平均电流差好几倍。

4.1.2 L2CAP(Logical Link Control and Adaptation Protocol)

L2CAP干的事很简单:把上层的大包拆成LL能发的小包,或者把LL收上来的小包拼回去。它有一个关键参数——MTU。默认是23字节,但很多芯片支持协商到247甚至更大。我建议你在抓包时留意L2CAP层的Length字段,如果发现数据被频繁分段,说明MTU太小,可以尝试协商大MTU来提升吞吐。

4.1.3 ATT(Attribute Protocol)与GATT(Generic Attribute Profile)

ATT定义了「属性」的读写规则,GATT定义了这些属性怎么组织成Service和Characteristic。说白了,ATT是「怎么读怎么写」,GATT是「读什么写什么」。抓包时,你看到的Read Request、Write Command、Notification,都是ATT层的PDU。

我的习惯: 抓包时先看ATT Opcode,能快速判断当前是读、写还是通知。如果是Write Command(0x52),对端不需要回复,丢了你也不知道;如果是Write Request(0x12),对端必须回Write Response,否则就是异常。

4.2 空口包格式

BLE空口包分四种:Advertising包、Data包、LL Control包、Periodic包。但不管哪种,基本结构都一样。我画了一张图,你一看就明白。

BLE空口包基本结构 Preamble 1字节 Access Address 4字节 PDU 2~257字节 CRC 3字节 各字段说明: • Preamble:固定为 0xAA 或 0x55,用于接收端做频率同步和比特对齐。 • Access Address:广播包固定为 0x8E89BED6;数据包是随机生成的,用于连接标识。 • PDU:包含 Header + Payload,具体格式取决于包类型(广播/数据/LL控制)。 • CRC:24位循环冗余校验,用多项式 0x100065B 计算。

4.2.1 Preamble

Preamble就1个字节,广播包是0xAA,数据包是0x55。它的作用就是让接收端的射频电路锁定频率和相位。嗯,这里要注意:如果你抓包时发现Preamble不对,大概率是抓包工具离设备太远,信号太弱。

4.2.2 Access Address

Access Address(AA)是BLE包的「身份证」。广播包的AA是固定的——0x8E89BED6。数据包的AA在连接建立时由LL随机生成。我曾经在调试一个多连接场景时,发现两个连接的AA竟然一样,导致数据串扰。后来查了芯片手册,发现是随机数种子初始化没做好。从那以后,我每次抓包都会先确认AA是否唯一。

4.2.3 PDU

PDU是包的核心,里面装着真正的数据。广播PDU和Data PDU的格式不一样,我列个表对比一下。

PDU类型 Header长度 Payload最大长度 典型用途
广播PDU 2字节 37字节 广播、扫描、发起连接
数据PDU 2字节 255字节 连接后的数据收发
LL Control PDU 2字节 可变 连接参数更新、加密、调频
避坑指南: 我曾经遇到一个设备,广播PDU的Payload长度超过37字节,结果抓包工具显示CRC错误。后来发现是芯片的广播包扩展功能没配置对,导致PDU截断。记住:广播PDU最大37字节,超过就要用扩展广播(LE Extended Advertising)。

4.2.4 CRC

CRC是3字节的校验码,用多项式0x100065B计算。接收端收到包后会重新算一遍CRC,如果对不上就丢弃。我习惯在抓包时看CRC状态:如果CRC错误率超过5%,说明射频环境很差,需要检查天线匹配或降低发射功率。

4.3 抓包视角下的连接建立过程

连接建立是BLE最核心的流程,也是抓包调试的高频场景。我拆成三步来讲。

4.3.1 第一步:广播与扫描

外设(Peripheral)发ADV_IND包,里面包含设备地址和广播数据。主机(Central)发SCAN_REQ,外设回SCAN_RSP。这一步我经常用来排查「设备搜不到」的问题。如果抓包看不到ADV_IND,说明外设没在广播;如果看到ADV_IND但没看到SCAN_REQ,说明主机没在扫描。

4.3.2 第二步:发起连接

主机发CONNECT_IND包,里面包含连接参数:Connection Interval、Slave Latency、Supervision Timeout。外设收到后回复一个空包,连接就算建立了。我建议你在抓包时重点关注CONNECT_IND里的Interval字段,它直接决定了功耗和延迟。

4.3.3 第三步:数据交互

连接建立后,双方按Connection Interval定时交换数据。每个Interval里,主机先发,外设再回。如果外设没数据,就回一个空包。我在项目中遇到过一个问题:外设偶尔不回复空包,导致主机认为连接断了。后来发现是外设的LL状态机在某个中断里卡住了,没来得及发空包。

抓包小技巧: 用抓包工具看连接建立过程时,我习惯先过滤出CONNECT_IND包,然后看它前后的广播包。如果CONNECT_IND之后没有数据包,说明连接没建立成功。这时候检查CONNECT_IND里的Access Address和CRC,大概率能找到原因。

好了,这一章的内容就这些。协议栈分层和空口包结构是抓包调试的基础,你把这些搞清楚了,后面分析具体问题就会轻松很多。