4. 广播数据包格式:AD Structure、AD Type

广播包说白了就是一堆小数据块拼起来的。每个小数据块叫 AD Structure,它们一个接一个地塞在广播信道里。我刚开始啃协议栈的时候,总觉得这玩意儿很神秘,后来拆了几个包才发现——其实挺简单的。

4.1 AD Structure 的基本结构

每个 AD Structure 就三样东西:长度、类型、数据。你想想看,这不就跟我们写简历一样,先写标题再写内容嘛。

字段 长度 说明
Length 1 字节 表示 Type + Data 的总长度(不包含 Length 自身)
Type 1 字节 AD Type,告诉接收方这段数据是什么
Data 0~29 字节 具体的数据内容

举个例子:02 01 06 这个三字节序列,Length=02,Type=01(Flags),Data=06。嗯,这里要注意,Length 不包含自己那个字节,只算 Type 和 Data。

核心原则:广播包的总长度不能超过 31 字节(AdvData 上限)。所有 AD Structure 加起来必须 ≤ 31 字节。我见过不少新手把数据塞爆了,结果手机死活扫不到。

4.2 常见的 AD Type

协议栈里定义了上百种 AD Type,但实际项目中常用的就那么几个。我挑几个最关键的说说。

4.2.1 Flags (Type = 0x01)

Flags 几乎是每个广播包都会带的。它告诉接收方:我这个设备支持什么模式?能不能被连接?

Bit 含义 说明
Bit 0 LE Limited Discoverable Mode 有限可发现模式(只持续一段时间)
Bit 1 LE General Discoverable Mode 通用可发现模式(一直可被发现)
Bit 2 BR/EDR Not Supported 不支持经典蓝牙(纯 BLE 设备必须置 1)
Bit 3 Simultaneous LE and BR/EDR 同时支持 BLE 和经典蓝牙
Bit 4 Simultaneous LE and BR/EDR (Host) 主机端同时支持

最常见的值是 0x06(Bit 1 + Bit 2),表示通用可发现 + 纯 BLE。我习惯在调试时先确认 Flags 对不对,因为很多扫描不到的问题都是 Flags 设错了。

避坑:我曾经遇到一个案子,设备广播了但手机就是搜不到。查了半天,发现 Flags 里 Bit 2 没置 1,手机以为它是经典蓝牙设备,直接忽略了。所以纯 BLE 设备,Bit 2 必须为 1。

4.2.2 TX Power Level (Type = 0x0A)

这个字段告诉接收方:我的发射功率是多少 dBm。扫描端拿到 RSSI 后,结合这个值就能估算距离。

格式很简单:1 字节有符号整数,单位 dBm。比如 0x14 表示 +20 dBm,0xF6 表示 -10 dBm。

小技巧:如果你在做室内定位或接近检测,TX Power Level 一定要带上。没有它,RSSI 就是个相对值,没法换算成距离。我一般在广播包里固定写 0x00(0 dBm),方便调试。

4.2.3 Local Name (Type = 0x08 / 0x09)

设备名字。Type 0x08 是短名字,0x09 是完整名字。扫描端拿到这个名字,就能在界面上显示给用户看。

名字长度有限制——广播包总共才 31 字节,名字太长就塞不下了。我建议名字控制在 8 个字符以内,留点空间给其他 AD Structure。

// 示例:广播包包含名字 "MyTag"
// 0x06 0x09 4D 79 54 61 67
// Length=06, Type=0x09 (Complete Local Name), Data="MyTag"

4.2.4 UUID (Type = 0x02 / 0x03 / 0x04 / 0x05 / 0x06 / 0x07)

UUID 用来标识设备支持的服务。比如你的设备是个心率计,就广播心率服务的 UUID。扫描端看到这个 UUID,就知道该不该连你。

Type 含义 UUID 长度
0x02 Incomplete List of 16-bit UUIDs 2 字节
0x03 Complete List of 16-bit UUIDs 2 字节
0x04 Incomplete List of 32-bit UUIDs 4 字节
0x05 Complete List of 32-bit UUIDs 4 字节
0x06 Incomplete List of 128-bit UUIDs 16 字节
0x07 Complete List of 128-bit UUIDs 16 字节

16-bit UUID 是蓝牙 SIG 定义的,比如心率服务是 0x180D。128-bit UUID 是自定义的,自己随便写。我个人建议能用 16-bit 就别用 128-bit,省空间。

4.3 广播包结构总览

下面这张图把广播包的完整结构画出来了。从 PDU 到 AD Structure,一层层拆开看,你就知道数据是怎么组织的了。

广播数据包结构总览 BLE 广播信道 PDU (Adv PDU) Header (2B) + AdvA (6B) + AdvData (0~31B) AdvData (广播数据) 由多个 AD Structure 拼接而成,总长 ≤ 31 字节 AD Structure 列表 AD Structure 1 Length | Type | Data AD Structure 2 Length | Type | Data AD Structure 3 Length | Type | Data AD Structure 内部细节 Length Type Data (0~29 字节) 1 字节 1 字节 N 字节 注意:Length 只包含 Type + Data,不包含自身。所有 AD Structure 总长 ≤ 31 字节。

4.4 实战:拼一个完整的广播包

假设我们要做一个温度标签,名字叫 "Temp",广播温度服务的 UUID(0x1809),发射功率 0 dBm。广播包长这样:

// 广播数据 (AdvData) 共 14 字节
// Flags: 02 01 06
// TX Power: 02 0A 00
// UUID: 03 03 09 18
// Name: 05 09 54 65 6D 70

// 完整广播包:
// 02 01 06  02 0A 00  03 03 09 18  05 09 54 65 6D 70

你想想看,总共 14 字节,离 31 字节的上限还远着呢。如果以后要加自定义数据,空间完全够用。

调试建议:用 nRF Connect 或 LightBlue 抓包,直接看广播数据的十六进制。我习惯先看 Flags,再看 UUID,最后看名字。顺序对了,问题就少一半。

4.5 常见问题与避坑

  • 广播包超长:31 字节是硬上限,超了设备直接不广播。我曾经被坑过一次,名字写了 20 个字符,结果广播包死活发不出来。
  • AD Structure 顺序:协议没规定顺序,但业界习惯把 Flags 放第一个。扫描端解析时一般按顺序处理,所以重要的放前面。
  • Type 重复:同一个 Type 可以出现多次,但接收方通常只取第一个。所以别重复放,浪费空间。
  • 名字截断:如果名字太长放不下,协议允许只放短名字(Type 0x08)。扫描端看到短名字,知道不完整,但至少能显示。

嗯,广播包格式其实不复杂。你只要记住:每个 AD Structure 都是 Length + Type + Data,拼起来不超过 31 字节,就够用了。剩下的,多抓几次包自然就熟了。