4、GAP 基础:广播者与观察者角色、连接建立流程、广播数据包结构

好,咱们进入第四章。这一章聊的是 GAP——Generic Access Profile。说白了,GAP 就是蓝牙设备之间「怎么打招呼、怎么认识、怎么聊天」的一套规矩。你想想看,两个蓝牙设备要通信,总得有个先来后到吧?谁先说话?说什么?说完了怎么建立连接?这些都在 GAP 里定义好了。

我个人习惯把 GAP 理解成蓝牙世界的「社交礼仪」。你走进一个房间,先环顾四周(扫描),然后有人跟你打招呼(广播),你们聊了几句觉得投缘,就交换名片(配对),最后找个安静角落深入交流(连接)。嗯,就是这么回事。

4.1 广播者与观察者:谁在说话,谁在听?

在 BLE 里,设备角色分得很清楚。GAP 定义了四种角色,但最基础、最常用的就是两个:广播者(Broadcaster)观察者(Observer)

  • 广播者:只管往外发数据包,不接收连接。就像一个举着喇叭喊「我在这儿!我有这些功能!」的人。
  • 观察者:只管接收广播数据,不发起连接。就像在会场里竖起耳朵听别人介绍的人。

我在项目中遇到过不少新手,上来就问:「广播者和观察者能不能互相通信?」答案是:不能。广播者只管发,观察者只管收,它们之间没有双向数据通道。要真正通信,必须进入连接状态。

关键点:广播者和观察者都是「非连接」角色。它们的存在意义是发现设备、传递信息,而不是传输大量数据。

当然,还有另外两个角色:外设(Peripheral)中央(Central)。外设可以广播,也可以被连接;中央可以扫描,也可以发起连接。实际项目中,绝大多数设备都是外设或中央角色。广播者和观察者更多用在信标(Beacon)或者简单的数据采集场景。

我的经验:如果你在做低功耗传感器,比如温湿度标签,用广播者角色就够了。它每隔几秒发一次数据,发完就睡,功耗极低。我做过一个项目,用 CR2032 电池供电的广播者,跑了一年多还有电。

4.2 连接建立流程:从「你好」到「我们聊聊」

连接建立,说白了就是两个设备从「互相看见」到「建立专线」的过程。我把它拆成三步:

  1. 广播:外设(Peripheral)在三个广播信道上(37、38、39)周期性地发送广播包。
  2. 扫描:中央(Central)在这三个信道上轮流监听,收到广播包后,可以发送扫描请求获取更多信息。
  3. 发起连接:中央决定跟这个外设建立连接,就发送一个连接请求(CONNECT_REQ)。外设收到后,双方进入连接状态。

为什么会这样设计?你想想看,如果所有设备都在同一个频率上广播,那肯定撞车。所以 BLE 用了三个专用信道做广播,数据信道有 37 个。广播信道只负责「打招呼」,数据信道才负责「聊天」。这叫频分复用,很巧妙。

我记得有一次调试连接问题,外设广播了,中央也扫描到了,但就是连不上。查了半天,发现是连接间隔参数设置得太小,中央来不及处理。嗯,这里要注意:连接参数(连接间隔、延迟、超时)需要双方协商,不是你想设多少就设多少。

避坑指南:我曾经遇到过中央设备发送 CONNECT_REQ 后,外设没有及时回复,导致连接失败。原因是外设的广播间隔太长,中央在发送连接请求时,外设刚好处于休眠状态。解决办法是:在发起连接前,先发送一个扫描请求,确认外设还在活跃状态。

连接建立后,双方会按照协商好的连接间隔定期唤醒、交换数据。这个间隔可以动态调整,但需要双方同意。我一般建议:对功耗敏感的场景,用 100ms 以上的间隔;对实时性要求高的,用 20ms 左右。

4.3 广播数据包结构:拆开看看里面有什么

广播数据包,说白了就是一段二进制数据。但这段数据有严格的格式要求。你如果不按规矩来,别的设备就解析不了。咱们直接看结构:

字段 长度(字节) 说明
前导码 1 用于接收端同步时钟,固定为 0xAA 或 0x55
访问地址 4 广播信道固定为 0x8E89BED6
PDU 2-39 协议数据单元,包含报头、MAC 地址和广播数据
CRC 3 循环冗余校验,用于检测数据错误

咱们重点关注 PDU 部分。PDU 里又分三块:

  • 报头(Header):2 字节,包含 PDU 类型、长度、发送地址类型等信息。
  • MAC 地址:6 字节,设备的蓝牙地址。
  • 广播数据(Advertising Data):最多 31 字节,这是咱们真正关心的内容。

广播数据的结构是「TLV」格式——Type、Length、Value。每个数据段由三部分组成:

  • 长度(Length):1 字节,表示后面 Value 的长度(不包含 Type 和 Length 本身)。
  • 类型(Type):1 字节,表示这个数据段的含义。比如 0x01 表示 Flags,0x09 表示 Complete Local Name。
  • 值(Value):N 字节,具体数据。

举个例子,一个广播包包含设备名称「MyTag」和 TX Power Level:

// 广播数据示例(十六进制)
// 02 01 06          -> Flags: 0x06 (LE General Discoverable + BR/EDR Not Supported)
// 05 09 4D 79 54 61 67 -> Complete Local Name: "MyTag"
// 02 0A 0F          -> TX Power Level: 15 dBm

// 实际发送的字节流:
02 01 06 05 09 4D 79 54 61 67 02 0A 0F

注意:广播数据总长度不能超过 31 字节。如果你的设备名称很长,或者要携带大量自定义数据,就得用 Scan Response 数据(也是 31 字节)。扫描响应数据是中央发送扫描请求后,外设额外返回的数据。

我在项目中经常看到有人把广播数据塞得满满当当,结果连最基本的 Flags 都忘了放。Flags 是必须的,它告诉接收端这个设备支持什么模式。没有 Flags,很多手机根本扫描不到你的设备。

我的习惯:广播数据里,我一般只放三类信息:Flags、设备名称、自定义服务 UUID。自定义数据尽量精简,能用 1 字节表示的绝不用 2 字节。毕竟广播信道带宽有限,数据越长,碰撞概率越大,功耗也越高。

4.4 实战建议:广播参数怎么配?

广播参数直接影响功耗和发现速度。我列几个关键参数,你配置的时候心里要有数:

参数 推荐值 说明
广播间隔 100ms - 1s 间隔越短,发现越快,但功耗越高
广播类型 可连接广播 大多数外设用这个,允许中央发起连接
广播信道 37、38、39 全开 三个信道都广播,提高被发现概率
广播数据长度 尽量小于 20 字节 留有余量给扫描响应数据

嗯,这里要提醒一下:广播间隔不是越短越好。我见过有人设成 20ms,结果设备发热严重,电池半天就没了。一般来说,100ms 是个不错的折中点。如果你做的是信标类产品,1 秒广播一次都行。

好了,这一章的内容就这些。GAP 是 BLE 的基石,搞懂了广播、扫描、连接这三个概念,后面的 GATT 和实际应用就好理解了。下一章咱们聊 GATT——数据是怎么组织、怎么读写的。