2、BLE协议栈架构:从物理层到应用层的完整拆解
BLE协议栈,说白了就是一套分层的软件协议。每一层各司其职,上层依赖下层提供服务。我刚开始接触BLE时,面对这堆缩写——PHY、LL、HCI、L2CAP、SM、ATT、GATT、GAP——说实话,头都大了。但后来我发现,只要理解了每一层「管什么事」,整个架构就清晰了。
核心观点:BLE协议栈的核心设计哲学是「分层解耦」。每一层只关心自己的职责,层与层之间通过标准接口通信。这种设计让开发变得灵活——你可以在不修改上层应用的情况下,更换底层的硬件实现。
2.1 物理层(PHY)—— 最底层的「无线电」
物理层是协议栈的最底层。它负责把数据变成无线电波发出去,也负责把收到的无线电波还原成数据。说白了,就是「调制解调」那点事。
BLE在2.4GHz ISM频段工作,这个频段也是Wi-Fi、Zigbee、微波炉的「地盘」。所以干扰是家常便饭。BLE物理层用了GFSK调制(高斯频移键控),抗干扰能力还不错。
我的经验:我在一个智能门锁项目里,遇到过设备在微波炉旁边连接不稳定的情况。后来发现是物理层干扰。解决办法很简单——调整了设备的跳频算法参数,避开了被占用的信道。嗯,物理层的问题,有时候得从上层找解法。
BLE物理层有几个关键参数:
| 参数 | BLE 1M PHY | BLE 2M PHY | BLE 长距离 PHY |
|---|---|---|---|
| 数据速率 | 1 Mbps | 2 Mbps | 125 kbps / 500 kbps |
| 调制方式 | GFSK | GFSK | GFSK |
| 灵敏度 | -96 dBm(典型) | -93 dBm(典型) | -103 dBm(典型) |
| 应用场景 | 通用 | 高速数据传输 | 远距离、低速率 |
你想想看,为什么BLE 2M PHY的灵敏度反而比1M PHY差?因为速率高了,接收机的带宽就宽了,噪声也进来了。这是物理层的「物理规律」,绕不开。
2.2 链路层(LL)—— 连接的「交通警察」
链路层在物理层之上。它负责管理设备之间的连接状态机,处理数据包的收发、重传、加密等。我个人觉得,LL是整个协议栈里最「硬核」的一层。
LL定义了五种状态:
- 待机(Standby)—— 什么都不做,省电模式
- 广播(Advertising)—— 向外发送广播包,告诉别人「我在这里」
- 扫描(Scanning)—— 监听广播包,寻找周围设备
- 发起连接(Initiating)—— 收到广播后,主动发起连接请求
- 已连接(Connected)—— 连接建立成功,开始数据通信
避坑指南:我曾经在一个项目中,把广播间隔设得太短(20ms),结果设备功耗飙升,电池撑不过一天。后来把广播间隔调到100ms,功耗降了70%。广播间隔不是越小越好,要根据应用场景权衡。
LL还负责跳频。BLE在40个信道中工作,其中3个是广播信道,37个是数据信道。连接建立后,LL会在37个数据信道之间跳来跳去,避开干扰。这个机制叫自适应跳频(AFH)。
2.3 主机控制接口(HCI)—— 主机和控制器之间的「翻译官」
HCI是主机层和控制器层之间的接口。它定义了一套标准命令和事件,让上层软件可以控制底层硬件。
HCI有两种传输方式:
- UART —— 最常见,用串口通信,简单可靠
- USB —— 用于USB蓝牙适配器
- SDIO —— 用于高速嵌入式系统
我记得第一次调试HCI时,用逻辑分析仪抓UART数据,看到一堆十六进制命令。当时完全看不懂。后来发现,HCI命令有固定格式:操作码(Opcode)+ 参数长度 + 参数。掌握了这个规律,调试就简单多了。
实用技巧:如果你在调试HCI,推荐用btmon(Linux)或hcitool。这些工具可以解析HCI数据包,把十六进制转成可读的文本。省去手动查规格书的痛苦。
2.4 逻辑链路控制与适配协议(L2CAP)—— 数据的「分包与重组」
L2CAP位于HCI之上。它负责把上层的大数据包拆分成LL能处理的小包,也负责把LL收到的小包重组回上层的大包。
L2CAP还提供协议多路复用功能。什么意思?就是多个上层协议(ATT、SM等)可以共用同一个L2CAP通道。L2CAP通过通道ID(CID)来区分不同的协议。
BLE的L2CAP和经典蓝牙的L2CAP不太一样。BLE的L2CAP简化了很多,只支持面向连接通道(CoC)和无连接通道(CL)。我个人习惯把L2CAP看作「快递分拣中心」——它不关心包裹里是什么,只负责把包裹送到正确的目的地。
2.5 安全管理(SM)—— 连接的「保安队长」
SM负责BLE设备之间的配对、认证和加密。没有SM,你的BLE数据就是「裸奔」的——任何人都可以监听。
SM定义了三种配对方式:
| 配对方式 | 安全性 | 适用场景 |
|---|---|---|
| Just Works | 低 | 无显示/无输入设备(如灯泡) |
| Passkey Entry | 中 | 一方有显示,一方有输入(如手机+门锁) |
| Numeric Comparison | 高 | 双方都有显示(如手机+手表) |
注意:Just Works配对虽然方便,但容易受到中间人攻击(MITM)。如果你的产品涉及敏感数据(如门锁、支付),一定要用Passkey Entry或Numeric Comparison。我曾经见过一个智能门锁项目,因为用了Just Works,被安全团队打回重做。
2.6 属性协议(ATT)—— 数据的「读写规则」
ATT定义了BLE设备之间如何读写数据。它把数据组织成属性(Attribute),每个属性都有一个句柄(Handle)、UUID和权限。
ATT支持六种操作:
- 请求(Request) —— 客户端发请求,服务器回复
- 响应(Response) —— 服务器回复请求
- 命令(Command) —— 客户端发命令,不需要回复
- 通知(Notification) —— 服务器主动发数据,不需要确认
- 指示(Indication) —— 服务器主动发数据,需要确认
- 确认(Confirmation) —— 客户端确认指示
你想想看,为什么要有「通知」和「指示」两种方式?通知效率高,但可能丢包;指示可靠,但多了一次确认握手。实际项目中,我一般用通知传输传感器数据(丢一包无所谓),用指示传输控制命令(必须可靠)。
2.7 通用属性配置文件(GATT)—— 数据的「组织规范」
GATT在ATT之上,定义了数据的组织结构。它把属性组织成服务(Service)和特征(Characteristic)。
- 服务 —— 一组相关功能的集合。比如「电池服务」包含电池电量、电池状态等特征。
- 特征 —— 具体的数据项。比如「电池电量」特征,值是一个0-100的百分比。
- 描述符(Descriptor) —— 特征的附加信息,比如单位、范围等。
我的习惯:设计GATT服务时,我建议先画一张「服务-特征-描述符」的树状图。把每个特征的UUID、权限、数据类型都列清楚。这样写代码时就不会乱。我在一个健康手环项目里,因为GATT设计不合理,导致手机App连接后要花5秒才能发现所有服务。后来重新设计了GATT结构,把不常用的服务放到「次要服务」里,发现时间降到了1秒。
2.8 通用访问配置文件(GAP)—— 设备的「社交礼仪」
GAP定义了BLE设备如何发现彼此、如何建立连接、以及设备在连接中的角色。
GAP定义了四种角色:
| 角色 | 职责 | 典型设备 |
|---|---|---|
| 广播者(Broadcaster) | 只发送广播,不接收连接 | 信标(Beacon) |
| 观察者(Observer) | 只扫描广播,不发起连接 | 扫描仪 |
| 外设(Peripheral) | 发送广播,接受连接 | 传感器、手环 |
| 中央(Central) | 扫描广播,发起连接 | 手机、平板 |
GAP还定义了广播数据格式。广播包可以包含设备名称、UUID、Tx Power等信息。我建议在广播包里只放必要信息,因为广播包最大只有31字节。放太多东西,反而会影响扫描效率。
实用建议:设计广播数据时,优先放服务UUID和设备名称。Tx Power可以放在扫描响应包里,不用占广播包的空间。这样扫描设备可以快速识别你的设备,用户体验更好。
好了,以上就是BLE协议栈的八个核心层次。每一层都有自己的职责,但最终都是为了实现一个目标——让两个BLE设备能够可靠、安全、高效地通信。理解了这个分层架构,后续的编码和调试就会轻松很多。