1. GATT 协议基础:BLE 协议栈架构、GAP 与 GATT 的区别、Service 与 Characteristic 的概念

各位好,我是老张。做物联网嵌入式开发十几年了,今天咱们来聊聊 BLE 里最核心的东西——GATT 协议。说实话,很多新手一上来就扎进代码里,结果连 Service 和 Characteristic 都分不清,调试断连问题的时候一头雾水。我个人习惯是,先把地基打牢,后面诊断问题才能快准狠。

1.1 BLE 协议栈架构:从物理层到应用层

先看整体架构。BLE 协议栈分三层:Controller、Host 和 Application。你想想看,这就像寄快递——物理层是卡车,链路层是分拣中心,而 GATT 就是快递单上的收件人信息。

核心要点:GATT 位于 Host 层的最顶部,直接面向应用。它不关心数据怎么传,只关心数据长什么样、怎么读写。

我画了一张图,帮你把这几层的关系理清楚:

BLE 协议栈分层架构 Application(应用层) 用户自定义 Profile / GATT 服务 Host(主机层) GATT(通用属性协议) GAP(通用访问协议) L2CAP(逻辑链路控制与适配协议) SMP / ATT Controller(控制器层) LL(链路层) PHY(物理层) GATT 位于 Host 层顶部,直接为应用层提供数据交互接口

从下往上看:PHY 管射频收发,LL 管连接状态和广播,L2CAP 做数据分包重组,ATT 提供属性读写基础,最后 GATT 把属性组织成有意义的服务。嗯,这里要注意——GATT 和 ATT 的关系很多人搞混。ATT 是底层传输协议,GATT 是上层框架协议。GATT 用 ATT 来传输数据,但定义了数据怎么组织。

1.2 GAP 与 GATT 的区别:一个管连接,一个管数据

这两个词长得像,但分工完全不同。我刚开始做 BLE 的时候也犯过糊涂,后来用一句话就记住了:GAP 管“能不能连”,GATT 管“连上以后干什么”。

对比维度 GAP(通用访问协议) GATT(通用属性协议)
核心职责 设备发现、连接建立、广播 数据读写、通知、服务发现
角色定义 Central / Peripheral Client / Server
工作阶段 连接前和连接建立时 连接建立后
数据粒度 广播包(31 字节) 属性(最大 512 字节)
典型场景 手机扫描手环、配对 读取心率值、下发指令

我的经验:诊断断连问题时,先看 GAP 层有没有掉线(连接超时),再看 GATT 层有没有事务卡死(MTU 协商失败、写队列阻塞)。我曾经在一个项目中,设备每隔 30 秒就断连,查了两天才发现是 GAP 的连接间隔参数配得太激进,跟 GATT 没关系。

说白了,GAP 是“门卫”,负责开门关门;GATT 是“管家”,负责屋里东西怎么摆放、怎么取用。你调试断连问题的时候,如果连都连不上,那肯定是 GAP 的事;如果连上了但读不到数据,那就是 GATT 的事。

1.3 Service 与 Characteristic 的概念:数据组织的骨架和血肉

这是 GATT 最核心的两个概念。我见过太多人把 Service 当成“服务”,把 Characteristic 当成“特征”,结果写代码的时候完全不知道 UUID 该怎么配。

Service(服务):一组相关功能的集合。比如“心率服务”包含心率测量值、心率传感器位置等。每个 Service 有一个 16 位或 128 位的 UUID。

Characteristic(特征):Service 里的具体数据点。比如“心率测量值”就是一个 Characteristic,它包含值、属性(可读/可写/可通知)、描述符。

我画个结构图你就明白了:

GATT Service / Characteristic 层级结构 Profile(应用规范) Service A(心率服务) UUID: 0x180D Service B(电池服务) UUID: 0x180F Char: 心率测量值 UUID: 0x2A37 | 可通知 Char: 传感器位置 UUID: 0x2A38 | 可读 Char: 电池电量 UUID: 0x2A19 | 可读 CCCD(客户端特征配置描述符) 一个 Profile 包含多个 Service,一个 Service 包含多个 Characteristic 每个 Characteristic 可以有多个 Descriptor(如 CCCD 用于开启通知)

避坑指南:我曾经在一个项目中,把心率服务的 UUID 写错了(0x180D 写成了 0x180E),结果手机端一直发现不了服务。查了两天,最后用 nRF Connect 抓包才发现 UUID 对不上。记住:Service UUID 和 Characteristic UUID 必须严格遵循 Bluetooth SIG 规范,自定义服务要用 128 位 UUID。

每个 Characteristic 还有几个关键属性:

  • Read:客户端可以读取这个值
  • Write:客户端可以写入这个值(有带响应和不带响应两种)
  • Notify:服务端主动推送值给客户端(不需要客户端确认)
  • Indicate:服务端主动推送值给客户端(需要客户端确认)

你想想看,Notify 和 Indicate 有什么区别?说白了,Notify 像发——发了就不管了;Indicate 像发挂号信——对方必须回个“收到了”。在断连诊断中,如果 Indicate 的确认包丢了,GATT 层就会卡死,连接也就断了。我遇到过好几次这种情况,最后都是通过抓包发现 Indicate 超时导致的。

1.4 小结:GATT 是断连诊断的起点

好了,这一章的内容就这些。记住三句话:

  1. GATT 在协议栈的 Host 层顶部,管的是连接后的数据交互
  2. GAP 管连接,GATT 管数据,两者分工明确
  3. Service 是功能集合,Characteristic 是具体数据点,Descriptor 是配置开关