1. GATT 协议概述:从 BLE 协议栈到核心概念
各位同学,欢迎来到《GATT 服务设计与开发》的第一章。我是你们这门课的老朋友,一个在蓝牙低功耗领域摸爬滚打多年的工程师。今天咱们不聊虚的,直接切入正题——GATT 协议到底是什么?它凭什么能成为 BLE 应用开发的基石?
说实话,我刚开始接触 BLE 时,也被那一堆缩写搞得头晕:HCI、L2CAP、ATT、GATT、GAP…… 但后来我发现,只要抓住一条主线,整个协议栈就清晰了。这条主线就是:数据怎么组织,数据怎么交互。GATT 就是负责「数据怎么组织」的那一层。
1.1 BLE 协议栈架构:分层看更清楚
BLE 协议栈不是铁板一块,它分了好几层。每一层各司其职,就像公司里的不同部门。我习惯把它分成三大部分:
- Controller(控制器):负责最底层的射频收发、链路控制。包括物理层(PHY)和链路层(LL)。
- Host(主机):负责协议逻辑、数据封装、安全管理。包括 L2CAP、ATT、GATT、GAP、SM(安全管理)等。
- Application(应用层):你写的业务代码,直接使用 GATT/GAP API。
你想想看,如果让你从零开始写一个蓝牙应用,你肯定不想去管射频时序对不对、数据包重传怎么处理。这些脏活累活,Controller 和 Host 已经帮你干了。你只需要关心:我要暴露什么数据?对方怎么读写? 这就是 GATT 的舞台。
核心观点: GATT 位于 Host 层的最顶部,直接面向应用。它不关心数据怎么传过去,只关心数据长什么样、怎么访问。
下面这张图,是我自己画的一个简化版 BLE 协议栈分层。每次带新人时,我都会先让他们看这张图。
我的经验: 实际开发中,你几乎不需要碰 HCI 以下的层。但理解 ATT 和 L2CAP 的边界很有用——比如遇到 MTU 协商问题时,你就知道该去 L2CAP 层找原因。
1.2 GATT 与 GAP 的关系:一个管连接,一个管数据
很多初学者会把 GATT 和 GAP 搞混。我打个比方你就明白了:
- GAP(通用访问协议) 负责「怎么找到对方、怎么建立连接」。它管广播、扫描、连接建立、连接参数更新。说白了,GAP 是「社交礼仪」。
- GATT(通用属性协议) 负责「连接建立之后,数据怎么交换」。它管服务、特征、描述符的定义和读写。GATT 是「业务逻辑」。
举个例子:你打开手机蓝牙,扫描到一个智能手环。这个「扫描」过程是 GAP 的事。你连上手环,读取心率数据——这个「读取」过程就是 GATT 的事了。
| 维度 | GAP | GATT |
|---|---|---|
| 职责 | 设备发现、连接建立、安全配对 | 数据组织、属性读写、通知/指示 |
| 角色 | Central / Peripheral(中心/外设) | Client / Server(客户端/服务端) |
| 核心概念 | 广播、扫描、连接间隔、白名单 | Service、Characteristic、Descriptor |
| 工作时机 | 连接前 + 连接中 | 连接建立后 |
注意: GAP 角色和 GATT 角色是独立的。一个 GAP Peripheral 设备可以是 GATT Server,也可以是 GATT Client。我曾经见过有人把这两个角色混为一谈,结果在设计中把角色搞反了,调试了两天才发现。
1.3 GATT 的角色:Server 与 Client
GATT 定义了两种角色:
- GATT Server:持有数据的一方。它定义 Service、Characteristic、Descriptor,并响应 Client 的读写请求。
- GATT Client:发起数据访问的一方。它发现 Server 上的服务,然后读写对应的 Characteristic。
我习惯这样记:Server 是「数据仓库」,Client 是「取货员」。仓库里有什么货、怎么摆放,是 Server 说了算。取货员只能按照仓库的规则来取货。
在实际项目中,最常见的组合是:
- 智能设备(如手环、传感器)作为 GATT Server
- 手机 App 作为 GATT Client
但这不是绝对的。比如手机给手环同步配置数据时,手机也可以作为 Server,手环作为 Client。嗯,这里要注意:角色是动态的,取决于谁提供数据,谁消费数据。
1.4 GATT 的核心概念:Service、Characteristic、Descriptor
这是 GATT 最核心的内容,也是你设计蓝牙服务时必须掌握的三板斧。咱们一个一个说。
1.4.1 Service(服务)
Service 是一组相关功能的集合。比如「心率服务」包含心率测量值、心率测量位置等数据。每个 Service 由一个 16-bit 或 128-bit UUID 唯一标识。
蓝牙 SIG(蓝牙技术联盟)定义了很多标准 Service,比如:
- 0x180D — 心率服务(Heart Rate Service)
- 0x180F — 电池服务(Battery Service)
- 0x181A — 环境感知服务(Environmental Sensing Service)
你也可以自定义 Service,用 128-bit UUID。我在项目中就经常这么干——标准服务不够用时,自己定义一套。
1.4.2 Characteristic(特征)
Characteristic 是 GATT 数据交换的最小单元。一个 Service 可以包含多个 Characteristic。每个 Characteristic 包含:
- Value:实际的数据值(比如心率数值 72 bpm)
- Properties:访问权限(读、写、通知、指示等)
- Permissions:安全级别(是否需要配对、加密等)
举个例子,「心率服务」里有一个 Characteristic 叫「心率测量值」,它的 UUID 是 0x2A37。这个 Characteristic 的 Properties 通常包含「通知」——因为心率数据是实时变化的,Server 需要主动推送给 Client。
关键点: Characteristic 的 Properties 和 Permissions 是两个不同的概念。Properties 告诉 Client「我能做什么」,Permissions 告诉底层协议栈「谁可以做什么」。我见过有人把这两个搞混,导致明明设置了可读,但 Client 读不到数据——其实是 Permissions 没配好。
1.4.3 Descriptor(描述符)
Descriptor 是 Characteristic 的附加信息。它用来描述 Characteristic 的配置、范围、单位等。最常用的 Descriptor 是:
- Client Characteristic Configuration Descriptor (CCCD):UUID 0x2902。用来启用或禁用通知/指示。Client 往这个 Descriptor 写 0x0001 启用通知,写 0x0000 禁用。
- Characteristic User Description:UUID 0x2901。用来给 Characteristic 加一个人类可读的名称。
Descriptor 不是必须的。但如果你要用通知功能,CCCD 就是必须的。我记得有一次调试一个心率设备,Client 收不到通知,查了半天才发现是 CCCD 没有正确写入。从那以后,我每次都会先检查 CCCD 的配置。
1.5 三者关系:一张图说清楚
Service、Characteristic、Descriptor 的关系是树形的:
你看,一个 Service 下面可以挂多个 Characteristic,每个 Characteristic 下面又可以挂多个 Descriptor。这就是 GATT 的数据组织方式——层次化、结构化。
1.6 一个完整的 GATT 数据交互流程
说了这么多理论,咱们来看一个实际流程。假设手机 App 要读取手环的电池电量:
- GAP 阶段:手机扫描到手环,发起连接。
- 服务发现:手机(Client)发送请求,发现手环(Server)上的所有 Service。
- 特征发现:手机找到电池服务(0x180F),然后发现该服务下的电池电量 Characteristic(0x2A19)。
- 读取操作:手机发送 Read Request,手环返回当前电量值(比如 85%)。
- (可选)启用通知:如果手机想实时获取电量变化,就向 CCCD 写入 0x0001,启用通知。
- 数据推送:当电量变化时,手环主动发送 Notification 给手机。
我的建议: 刚开始做 GATT 开发时,先用手机上的 nRF Connect 或 LightBlue 工具手动走一遍这个流程。亲眼看到 Service、Characteristic、Descriptor 的层级结构,比看十遍文档都管用。
1.7 本章小结
咱们这一章把 GATT 的骨架搭起来了:
- BLE 协议栈是分层的,GATT 在最上面,直接面向应用。
- GAP 管连接,GATT 管数据,各司其职。
- GATT 有 Server 和 Client 两种角色,Server 提供数据,Client 消费数据。
- Service、Characteristic、Descriptor 是 GATT 的三大核心概念,它们组成树形结构。