4. BLE协议基础(下):L2CAP、ATT、GATT、GAP层详解
上一讲我们把物理层和链路层聊透了。今天继续往下走,看看协议栈中间那几层——L2CAP、ATT、GATT、GAP。说实话,这几层才是真正跟应用开发打交道的部分。我刚开始做BLE项目时,总觉得GATT和GAP长得像双胞胎,搞混了好几次。后来踩了坑才明白,它们分工完全不同。
先给你一张总览图,看看这几层在协议栈里站什么位置:
4.1 L2CAP——数据搬运工
L2CAP的全称是逻辑链路控制与适配协议。名字挺长,但活儿其实很单纯——它负责把上层的数据包拆成链路层能吃的MTU大小,再传过去。反过来,收到链路层的数据后,它再拼回去交给上层。
我打个比方:L2CAP就像快递中转站。你寄一个大箱子,中转站发现货车装不下,就帮你拆成几个小箱子分别运。到了目的地再拼回去。你作为寄件人根本不用操心拆箱的事。
L2CAP有几个关键点:
- MTU协商:连接建立后,双方会协商一个最大传输单元。默认是23字节,但可以协商到更大。我做过一个OTA升级项目,MTU没协商好,固件分包传得乱七八糟。后来把MTU提到247字节,速度直接翻倍。
- 通道复用:L2CAP支持多个上层协议共用同一个链路层连接。ATT、SMP(安全管理协议)都跑在L2CAP的不同通道上。
- 分段重组:这是它最核心的活儿。上层发一个100字节的包,链路层MTU只有27字节,L2CAP就自动切成4段,每段带个序号,接收方再拼回去。
4.2 ATT——属性协议,数据的搬运规则
ATT全称是属性协议。它定义了一台设备怎么去读写另一台设备上的“属性”。属性是什么?说白了就是一小块数据,带个句柄、UUID、权限描述。
ATT的角色是服务器-客户端模型:
- GATT服务器:持有属性数据,响应读写请求。
- GATT客户端:发起读写请求,接收通知。
注意,一个设备可以同时是服务器和客户端。比如手机连手环,手机是客户端,手环是服务器。但手环也可以主动发通知给手机,这时候手环在ATT层面还是服务器,只是它主动推送了数据。
ATT定义了6种操作类型:
| 操作 | 方向 | 说明 |
|---|---|---|
| Request | 客户端→服务器 | 请求读/写,服务器必须回复 |
| Response | 服务器→客户端 | 对Request的应答 |
| Command | 客户端→服务器 | 写操作,不需要回复 |
| Notification | 服务器→客户端 | 服务器主动推送,不需要确认 |
| Indication | 服务器→客户端 | 服务器主动推送,需要客户端确认 |
| Confirm | 客户端→服务器 | 对Indication的确认 |
这里有个坑:Notification和Indication看起来差不多,但Indication有确认机制,可靠性更高。不过Indication的吞吐量比Notification低,因为每次都要等确认。我做过一个心率监测项目,用Notification每秒传10个数据点,换成Indication后只能传3个。所以对可靠性要求不高的场景,用Notification更划算。
4.3 GATT——属性协议的“上层建筑”
GATT全称是通用属性规范。它建立在ATT之上,定义了属性的组织方式。说白了,ATT只负责怎么读写数据,GATT负责数据长什么样、怎么分组。
GATT的核心概念有三个:
- Service(服务):一组相关功能的集合。比如电池服务、设备信息服务。
- Characteristic(特征):一个具体的数据点。比如电池电量百分比、设备序列号。
- Descriptor(描述符):对特征的额外描述。比如配置通知开关、设置数据格式。
它们的层级关系是这样的:
每个Characteristic都有UUID来标识。标准UUID是16位的,比如0x2A19代表电池电量。自定义服务用128位UUID。我建议你尽量复用标准UUID,别自己造轮子。蓝牙SIG官网上有完整的标准服务列表,省时省力。
4.4 GAP——设备怎么被发现和连接
GAP全称是通用访问规范。它管的是设备怎么广播自己、怎么被扫描到、怎么建立连接。说白了,GAP是设备在“社交”层面的行为规范。
GAP定义了四种角色:
| 角色 | 说明 | 典型设备 |
|---|---|---|
| Broadcaster(广播者) | 只发广播,不接受连接 | 信标、温度标签 |
| Observer(观察者) | 只扫描广播,不发起连接 | 扫描仪、网关 |
| Peripheral(外设) | 发广播,接受连接 | 手环、传感器 |
| Central(中心) | 扫描广播,发起连接 | 手机、平板 |
GAP还定义了连接建立的流程:
- 广播阶段:外设周期性地发广播包,里面包含设备名称、服务UUID等信息。
- 扫描阶段:中心设备扫描到广播包,可以发起扫描请求获取更多信息。
- 连接阶段:中心设备发送连接请求,双方建立连接。
- 参数协商:连接建立后,双方可以协商连接间隔、延迟、超时等参数。
这里有个经验:广播间隔和连接间隔的配置直接影响功耗。广播间隔越短,被发现越快,但功耗越高。我做过一个纽扣电池供电的设备,广播间隔设成100ms,电池撑了不到一周。后来改成500ms,电池撑了一个月。你想想看,如果设备不需要频繁被发现,把广播间隔拉大一点,省电效果立竿见影。
4.5 它们之间的关系
这几层的关系,我用一句话总结:GAP管社交,GATT管数据,ATT管传输,L2CAP管搬运。
具体来说:
- GAP决定设备能不能被找到、能不能连上。
- 连上之后,GATT定义数据长什么样(服务、特征、描述符)。
- ATT负责具体怎么读写这些数据(请求、响应、通知)。
- L2CAP在底层把数据包拆好、拼好,保证ATT能顺畅通信。
举个例子:你用手环测心率。
- GAP:手环广播“我是心率手环”,手机扫描到并发起连接。
- GATT:手机发现手环有心率服务(UUID 0x180D),里面有心率测量特征(UUID 0x2A37)。
- ATT:手机发请求读心率特征的值,手环回复数据。或者手环主动发Notification推送心率数据。
- L2CAP:心率数据包如果太大,L2CAP负责分段传输,接收方再重组。
嗯,这几层的关系捋清楚了,后面搭建GATT服务就顺理成章了。你只要记住:GAP是门卫,GATT是前台,ATT是快递员,L2CAP是分拣中心。各司其职,缺一不可。