3. GAP广播与扫描:广播包格式解析、扫描响应数据、广播间隔设置、被动扫描与主动扫描
好,咱们进入GAP广播与扫描这个环节。说实话,这是蓝牙开发里最基础、也最容易踩坑的部分。我当年刚做血糖仪项目时,就因为在广播包格式上栽了个跟头,导致设备死活连不上手机。今天咱们就把这块彻底讲透。
3.1 广播包格式解析
先看广播包长什么样。BLE广播包,说白了就是设备在“喊话”:我在这儿,我是谁,我能干啥。它的结构其实挺简单,但细节很多。
一个完整的广播包,由这么几部分组成:
- 前导码(Preamble):1字节,固定0xAA或0x55,用于同步时钟。这个咱们不用管,芯片自动处理。
- 访问地址(Access Address):4字节,广播时固定为0x8E89BED6。嗯,记住这个值就行,调试时偶尔会用到。
- PDU(Protocol Data Unit):2~39字节,这是核心,包含广播数据。
- CRC(Cyclic Redundancy Check):3字节,校验用。
PDU里又分两种:广播指示(ADV_IND)和扫描响应(SCAN_RSP)。咱们先看ADV_IND。
ADV_IND 结构:
- 报头(Header):2字节,包含PDU类型、发送地址类型等
- 广播地址(AdvA):6字节,设备的蓝牙MAC地址
- 广播数据(AdvData):0~31字节,这才是咱们真正要关心的
广播数据部分,采用AD Structure(Advertising Data Structure)格式。每个AD Structure由三部分组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| Length | 1字节 | 表示后面Data的长度(不包括Length本身) |
| AD Type | 1字节 | 数据类型标识,比如0x01表示Flags,0x09表示完整本地名称 |
| AD Data | Length-1字节 | 具体数据内容 |
举个例子,血糖仪广播时,我通常会这样组织数据:
// 广播数据示例(十六进制)
02 01 06 // Flags: LE General Discoverable Mode
03 02 0D 18 // 完整16位UUID列表: 0x180D(心率服务,血糖仪常用)
09 09 47 6C 75 63 6F 6D 65 74 65 72 // 完整本地名称: "Glucometer"
这里要注意,广播数据总长度不能超过31字节。我在项目中就遇到过,想把设备序列号、电量、血糖值全塞进去,结果超了。后来只能精简,只放UUID和名称,其他数据放到扫描响应里。
3.2 扫描响应数据
扫描响应,说白了就是“你问我答”。当手机扫描到设备后,可以主动请求更多信息,设备就回复一个SCAN_RSP包。
SCAN_RSP的结构和ADV_IND几乎一样,只是PDU类型不同。它的数据也遵循AD Structure格式,同样不能超过31字节。
我个人习惯这样分配:
- 广播包:放Flags、UUID、设备名称(如果名称短的话)
- 扫描响应:放设备名称(如果名称长)、制造商自定义数据、设备状态等
举个例子,血糖仪的扫描响应可以这样:
// 扫描响应数据示例
09 09 47 6C 75 63 6F 6D 65 74 65 72 2D 41 42 // 完整本地名称: "Glucometer-AB"
05 FF 00 01 02 03 04 // 制造商自定义数据: 公司ID 0x0001,数据0x02,0x03,0x04
这里FF是制造商特定数据(Manufacturer Specific Data),前两个字节是公司ID(由蓝牙SIG分配),后面可以放任意数据。我在血糖仪项目里,就把设备序列号和当前电量放在这里,方便手机端快速识别。
小技巧:如果设备名称超过31字节,别硬塞。把名称拆成两部分:广播包放前几个字符,扫描响应放完整名称。或者干脆用缩写,比如"GLU-001"代替"Glucometer-001"。
3.3 广播间隔设置
广播间隔,就是设备每隔多久发一次广播包。这个参数直接影响功耗和发现速度。
BLE协议规定,广播间隔范围是20ms到10.24s,步进0.625ms。但实际开发中,咱们不会用这么极端的值。
| 应用场景 | 推荐间隔 | 说明 |
|---|---|---|
| 快速发现(如配对) | 20~50ms | 功耗高,但手机能秒发现 |
| 正常连接(如血糖仪) | 100~500ms | 平衡功耗和发现速度 |
| 低功耗(如信标) | 1~5s | 省电,但发现慢 |
我曾经在血糖仪项目里,默认用200ms间隔。结果测试时发现,手机靠近设备要等好几秒才能发现。后来改成100ms,发现速度明显提升,功耗也没增加多少。嗯,这里要注意,广播间隔不是固定值,芯片会在设定值上加上0~10ms的随机延迟,防止多个设备同时广播时碰撞。
另外,广播间隔还分两种模式:
- 高占空比广播(High Duty Cycle):间隔20~100ms,用于快速连接。但只能持续30.72秒,之后必须切换到低占空比或停止。
- 低占空比广播(Low Duty Cycle):间隔100ms~10.24s,可以一直广播。
我建议,血糖仪开机后先用高占空比广播30秒,让手机快速发现。如果30秒内没连接,就切换到低占空比,比如500ms,省电。
3.4 被动扫描与主动扫描
扫描,就是手机(或别的设备)在“听”广播。分两种:
- 被动扫描(Passive Scanning):只监听广播包,不发送任何请求。说白了就是“只听不说”。
- 主动扫描(Active Scanning):监听到广播包后,还会发送扫描请求(SCAN_REQ),设备回复扫描响应(SCAN_RSP)。就是“听了还要问”。
这两种方式有什么区别?
| 特性 | 被动扫描 | 主动扫描 |
|---|---|---|
| 功耗 | 低 | 高(因为要发送请求) |
| 获取信息 | 仅广播包数据 | 广播包 + 扫描响应数据 |
| 适用场景 | 简单发现、信标 | 需要更多设备信息时 |
我遇到过一个问题:有些设备在主动扫描时,会回复扫描响应,但回复的内容可能和广播包重复。比如广播包已经放了设备名称,扫描响应又放一遍,白白浪费带宽。所以设计时要注意,广播包和扫描响应的数据不要重复。
避坑指南:我曾经在调试时发现,手机扫描不到某些设备。后来排查半天,原来是设备广播间隔设成了10秒,而手机扫描窗口只有5秒,刚好错过。所以广播间隔和扫描窗口要匹配。一般建议广播间隔不超过扫描窗口的一半。
最后总结一下:广播包是设备在“喊”,扫描响应是“回答”,广播间隔控制“喊”的频率,被动扫描和主动扫描决定“听”的方式。血糖仪开发中,我建议广播包放UUID和简短名称,扫描响应放完整名称和制造商数据,广播间隔用100ms(快速发现)或500ms(省电),手机端用主动扫描获取完整信息。
嗯,这块内容就到这里。下一章咱们讲连接建立,到时候会涉及更多实战细节。