3. 蓝牙协议栈基础:BLE协议栈架构、GAP与GATT、广播与扫描
好,咱们今天聊聊蓝牙协议栈。说实话,很多初学者一听到「协议栈」三个字就头大,觉得这东西太底层、太抽象。我刚开始接触 BLE 的时候也一样,翻着 spec 文档,满眼都是 HCI、L2CAP、ATT、GATT…… 感觉像在看天书。
但后来我发现一个窍门:别把它当协议栈,把它当「通信的规矩」。你想想看,两个人要聊天,总得有个共同语言吧?谁先说、说多久、说完怎么确认对方听懂了——这些规矩就是协议栈干的事。
这一章,我就带你捋清楚 BLE 协议栈的骨架。咱们不抠太细的寄存器,先把架构、GAP、GATT、广播和扫描这几个核心概念吃透。
3.1 BLE 协议栈整体架构
BLE 协议栈分两层:Controller(控制器) 和 Host(主机)。中间有个 HCI(Host Controller Interface)隔开,像一道墙。
- Controller:负责物理层和链路层。说白了,就是管「怎么发出去、怎么收回来」的。这部分通常由芯片硬件实现,你写代码基本碰不到。
- Host:负责上层逻辑。包括 L2CAP(数据分包重组)、ATT(属性协议)、GATT(通用属性协议)、GAP(通用访问协议)、SM(安全管理)。这些才是你写应用时要打交道的。
我个人习惯把 Host 再拆成两半:GAP 管连接和发现,GATT 管数据读写。这样思路就清晰多了。
核心要点:Controller 是「快递员」,Host 是「客服」。快递员只管把包裹送到,客服才管包裹里装了什么、怎么拆。
3.2 GAP——通用访问协议
GAP 是 BLE 的「社交礼仪」。它定义了设备怎么被发现、怎么建立连接、以及连接后处于什么状态。
GAP 定义了四种角色:
- Broadcaster(广播者):只发广播,不连。比如 iBeacon 信标。
- Observer(观察者):只收广播,不连。比如扫描周围设备的 App。
- Peripheral(外设):发广播,等待连接。比如心率带、温度传感器。
- Central(中心):扫描广播,主动发起连接。比如手机。
嗯,这里要注意:一个设备可以同时扮演多个角色。我在项目中就遇到过,一个网关既要扫描周围的传感器(Central),又要广播自己的状态(Broadcaster)。这种场景在物联网里很常见。
GAP 还定义了连接参数:连接间隔、延迟、超时时间。这些参数直接影响功耗。我踩过一个坑:把连接间隔设得太短(比如 7.5ms),结果设备功耗飙升,电池两天就耗光了。后来改成 50ms,功耗降了 70%。
避坑指南:连接间隔不是越小越好。对于温度、湿度这类慢变数据,100ms 以上完全够用。只有音频、数据流这类场景才需要短间隔。
3.3 GATT——通用属性协议
GATT 是 BLE 的「数据字典」。它定义了数据怎么组织、怎么读写。
GATT 的核心概念有三个:
- Service(服务):一组相关数据的集合。比如「电池服务」包含电量百分比、电量状态等。
- Characteristic(特征):具体的数据点。比如「电量百分比」就是一个 Characteristic。
- Descriptor(描述符):对 Characteristic 的补充说明。比如单位、范围、配置信息。
你可以把 Service 想象成一个文件夹,Characteristic 是里面的文件,Descriptor 是文件的属性信息。
GATT 还定义了操作方式:
- Read:客户端主动读数据。
- Write:客户端写数据到服务端。
- Notify/Indicate:服务端主动推数据给客户端。Notify 不需要确认,Indicate 需要确认。
我记得有一次做智能锁项目,用 Notify 推送开锁状态。结果发现手机偶尔收不到通知。排查了半天,原来是 Notify 没有确认机制,丢包了。换成 Indicate 后问题解决。所以,关键数据用 Indicate,非关键数据用 Notify。
核心要点:GATT 是 BLE 应用开发的核心。你写的 90% 的代码都在跟 Service 和 Characteristic 打交道。
3.4 广播与扫描
广播和扫描是 BLE 的「打招呼」方式。设备还没连上之前,全靠广播和扫描来发现彼此。
3.4.1 广播
广播分两种:
- 可连接广播:广播者希望被连接。扫描者收到后可以发起连接请求。
- 不可连接广播:广播者只发数据,不接受连接。比如 Beacon。
广播包最大 31 字节。嗯,你没看错,只有 31 字节。所以你得精打细算,把最重要的信息塞进去。我一般把设备名称、服务 UUID、电量这些放广播包里,这样扫描者不用连接就能知道设备的基本信息。
广播间隔也有讲究。间隔越短,被发现越快,但功耗越高。我做过一个测试:
| 广播间隔 | 被发现时间(平均) | 功耗(相对) |
|---|---|---|
| 20ms | < 1s | 高 |
| 100ms | 1-3s | 中 |
| 1000ms | 5-10s | 低 |
对于需要快速响应的场景(比如门锁),用 20ms。对于传感器这类不着急的,1000ms 都行。
3.4.2 扫描
扫描分两种模式:
- 被动扫描:只收广播包,不发任何数据。省电。
- 主动扫描:收到广播包后,发送扫描请求,获取更多信息。比如设备名称、额外数据。
我建议:能用被动扫描就别用主动扫描。主动扫描会增加空中包数量,容易造成干扰。而且有些设备对扫描请求有安全限制,可能不响应。
警告:不要在同一信道同时做广播和扫描。我在项目中遇到过,一个设备既广播又扫描,结果自己发的广播把自己给扫描到了,导致逻辑混乱。后来加了信道隔离才解决。
3.5 实战经验总结
说了这么多,我总结几条实战经验:
- 先定角色,再写代码。明确你的设备是 Peripheral 还是 Central,是 Broadcaster 还是 Observer。角色定了,协议栈的配置就清晰了。
- 广播包要精简。31 字节很宝贵,只放必要信息。设备名称太长就截断,或者用缩写。
- 连接参数要调优。根据数据更新频率来设连接间隔。别盲目追求低延迟。
- Notify 和 Indicate 要选对。关键数据用 Indicate,普通数据用 Notify。
- 多测试不同手机。不同品牌的手机对 BLE 的支持有差异。我遇到过某款手机扫描不到广播包的问题,最后发现是手机蓝牙驱动有 bug。
好了,这一章就到这里。下一章咱们会深入 GATT 的实战,手把手教你写一个 Service 和 Characteristic。到时候你会觉得,其实 BLE 开发没那么神秘。