1. GATT协议基础:BLE协议栈架构、GATT与GAP的区别、GATT的角色(Server/Client)、Attribute Protocol基础

各位同学,咱们今天正式开讲《GATT服务数据采集与模组联调实战》的第一章。说实话,我做了这么多年嵌入式开发,接触过的无线协议少说也有七八种,但BLE(蓝牙低功耗)这套东西,尤其是GATT(通用属性协议),是让我觉得「既简单又容易踩坑」的一个。为什么这么说?你往下看就知道了。

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

先看整体。BLE协议栈分两层:Controller(控制器)和Host(主机)。中间有个HCI(主机控制器接口)把它们连起来。我刚开始学的时候,总觉得这分层有点多余——直接调API不就行了?后来做量产项目才发现,分层是为了解耦。芯片厂负责Controller,你做应用层,各管各的,互不干扰。

具体分层是这样的:

层级 归属 主要职责
Physical Layer(物理层) Controller 2.4GHz ISM频段,40个信道,调制解调
Link Layer(链路层) Controller 广播、扫描、连接建立、数据包重传
HCI(主机控制器接口) 边界 Host与Controller之间的命令/事件通道
L2CAP(逻辑链路控制与适配协议) Host 数据包分段重组、多路复用
ATT(属性协议) Host 属性数据的读写、通知、指示
GATT(通用属性协议) Host 基于ATT的服务/特征值/描述符框架
GAP(通用访问协议) Host 广播、扫描、连接、安全、角色管理

你看,GATT和GAP都在Host层,但干的活完全不同。GAP管「怎么找到对方、怎么连上」,GATT管「连上之后怎么交换数据」。我见过不少新手把这两个搞混,调了半天GATT发现连广播都没配好——嗯,顺序很重要。

核心要点:GAP负责连接建立前的行为(广播、扫描、连接),GATT负责连接建立后的数据交互(读写、通知)。两者分工明确,互不重叠。

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

咱们用个生活化的例子。GAP就像你去参加一个行业交流会——你得先找到会场(扫描),跟人打招呼(广播),交换名片(连接请求)。GATT呢,是你们交换完名片之后,坐下来聊具体业务——你问对方「你们公司做什么产品?」(读特征值),对方说「我们做传感器模组」(写响应),或者主动告诉你「我们刚发布了新品」(通知)。

具体区别我列了个表:

维度 GAP GATT
核心功能 设备发现、连接建立、安全配对 数据交互、服务发现、特征值读写
角色 Broadcaster/Observer/Peripheral/Central Server/Client
数据单元 广播包、扫描响应包 属性(Attribute)
工作阶段 连接前 + 连接中(部分) 连接建立后
典型API gap_advertise_start(), gap_scan_start() gatt_read_char(), gatt_write_cmd()

我个人习惯是:先配GAP,再配GATT。GAP没配好,GATT根本跑不起来。有一次我在项目里调一个心率采集模组,死活连不上手机,折腾了两天——最后发现是广播间隔设得太短,手机端扫描窗口没对齐。你看,GAP的锅,别让GATT背。

1.3 GATT的角色:Server与Client

GATT只有两个角色:Server(服务端)和Client(客户端)。Server持有数据,Client读写数据。一个设备可以同时是Server和Client,但通常嵌入式模组扮演Server,手机App扮演Client。

举个例子。你做一个温湿度传感器模组,模组里存着温度和湿度的数值——它就是Server。手机App连上来,读取温度值——它就是Client。Server不能主动去读Client的数据,Client也不能被动等Server推送——除非Server用Notification(通知)或Indication(指示)主动发。

实战经验:我建议你在设计GATT服务时,把Server端的数据更新逻辑和Client端的读取逻辑分开考虑。Server只管「数据对不对、什么时候通知」,Client只管「怎么读、怎么解析」。这样联调时定位问题快得多。

角色关系可以用一张图来理解:

GATT Server/Client 角色关系图 GATT Server / Client 角色关系 GATT Server 持有属性数据 响应读写请求 主动发送通知/指示 例:传感器模组 GATT Client 发现服务/特征值 发送读写请求 接收通知/指示 例:手机App 实线箭头:Client发起读写请求 → Server响应 虚线箭头:Server主动发送Notification/Indication → Client接收

这张图里,实线箭头表示Client主动发起的读写请求,虚线箭头表示Server主动推送的通知。你想想看,如果Server想主动告诉Client「温度超标了」,它不能直接写Client的数据——只能用Notification。这个机制在数据采集场景里特别常用。

1.4 Attribute Protocol基础:GATT的基石

GATT不是凭空存在的,它建立在ATT(属性协议)之上。ATT定义了数据的基本单元——属性(Attribute)。每个属性包含四个要素:

  • Attribute Handle(属性句柄):16位整数,唯一标识一个属性。Server内部用句柄索引属性,Client通过句柄读写属性。
  • Attribute Type(属性类型):用UUID(通用唯一标识符)表示。16位UUID用于标准服务(如心率服务0x180D),128位UUID用于自定义服务。
  • Attribute Permissions(属性权限):决定谁能读、谁能写、是否需要加密。权限配错了,Client读写就会失败。
  • Attribute Value(属性值):实际数据,长度可变(最长512字节,但实际受MTU限制)。

避坑指南:我曾经在一个项目里把属性权限配成了「仅加密可读」,但配对流程没走完,结果手机App死活读不到数据。查了两天才发现是权限和配对顺序的问题。建议你在开发阶段先用「无加密可读可写」,调通后再加安全策略。

ATT协议定义了6种PDU(协议数据单元)类型,GATT主要用到其中4种:

PDU类型 方向 用途
Read Request / Response Client → Server → Client Client读取属性值,Server返回数据
Write Request / Response Client → Server → Client Client写入属性值,Server确认写入成功
Write Command Client → Server Client写入属性值,Server不回复(无确认)
Notification / Handle Value Notification Server → Client Server主动推送数据,Client不确认
Indication / Handle Value Indication Server → Client → Server Server主动推送数据,Client必须确认

这里有个细节:Notification和Indication的区别在于是否需要Client确认。Notification快但不可靠(丢包不重传),Indication可靠但慢(有确认机制)。我在做数据采集项目时,高频数据用Notification(比如加速度计100Hz输出),关键事件用Indication(比如报警触发)。

1.5 GATT数据模型:Service、Characteristic、Descriptor

GATT在ATT的基础上,定义了三级数据模型:

  1. Service(服务):一组相关功能的集合。比如「心率服务」包含心率测量值、心率传感器位置等特征值。
  2. Characteristic(特征值):一个具体的数据点。包含一个值(Value)和若干描述符(Descriptor)。比如「心率测量值」特征值,值就是心率数值。
  3. Descriptor(描述符):描述特征值的元数据。比如「客户端特征配置描述符(CCCD)」用来开启或关闭Notification。

我画了个结构图,帮你理解这三层的关系:

GATT 数据模型结构 GATT 数据模型:Service → Characteristic → Descriptor Service(服务) UUID: 0x180D (心率服务) Characteristic(特征值) UUID: 0x2A37 (心率测量值) 属性: 读 + 通知 CCCD 0x2902 报告参考 0x2908 Characteristic(特征值) UUID: 0x2A38 (传感器位置) 属性: 读 特征值描述 0x2901 一个Service包含多个Characteristic,每个Characteristic包含一个Value和零到多个Descriptor

你看,一个Service就像一个大抽屉,里面放着多个Characteristic(小盒子),每个小盒子里除了数据本身,还贴着标签(Descriptor)。CCCD(0x2902)是最常用的Descriptor——Client往里面写0x0001,Server就开始发Notification;写0x0000,就停止。

小技巧:调试GATT服务时,我习惯先用手机App(比如nRF Connect或LightBlue)扫描设备,看看Service和Characteristic有没有正确暴露出来。如果App里能看到UUID但读不到值,八成是权限或句柄映射的问题。

1.6 本章小结

咱们把这一章的核心内容捋一遍:

  • BLE协议栈分Controller和Host两层,GATT和GAP都在Host层,但分工不同——GAP管连接,GATT管数据。
  • GATT只有两个角色:Server(持有数据)和Client(读写数据)。嵌入式模组通常是Server。
  • ATT是GATT的底层协议,属性由句柄、类型、权限、值四要素组成。
  • GATT数据模型分三层:Service(服务)、Characteristic(特征值)、Descriptor(描述符)。
  • Notification和Indication是Server主动推送数据的两种方式,前者快但不可靠,后者可靠但慢。