1. GATT 协议基础与特征模型
1.1 概述
GATT(Generic Attribute Profile)是 BLE 协议栈中最高层的 Profile,定义了客户端-服务器架构下如何通过 属性协议(ATT) 发现、读取、写入和通知特征值。GATT 的核心模型包括:
- 属性协议(ATT):提供底层属性数据的读/写/通知原语。
- 特征(Characteristic):包含一个值以及对该值的元数据(属性、权限、描述符)。
- 服务(Service):一组相关特征的集合。
- UUID 规范:用于唯一标识服务、特征和描述符。
实现要点: 理解 ATT 与 GATT 的分层关系是后续实现特征读写和本地缓存更新的基础。ATT 负责数据的传输,GATT 负责数据模型的构建与交互流程。
1.2 属性协议(ATT)
ATT 是 GATT 的底层传输协议,操作对象为 属性(Attribute)。每个属性包含:
| 属性字段 | 长度 | 说明 |
|---|---|---|
| 句柄(Handle) | 2 字节 | 属性的唯一索引,从 0x0001 开始 |
| 类型(Type) | 2 或 16 字节 | UUID,标识属性属于哪个服务/特征 |
| 权限(Permissions) | 可变 | 读/写/加密等访问权限 |
| 值(Value) | 可变 | 实际承载的数据 |
ATT 定义了多种 PDU,包括:Read Request/Response、Write Request/Command、Notify/Indicate 等。一个典型的 Read 流程如下(命令示例):
// GATT Client 发送 Read Request
ATT_Read_Req(handle = 0x0020)
// GATT Server 返回 Read Response
ATT_Read_Rsp(value = [0x01, 0x02, 0x03])
1.3 特征结构
一个特征由三个属性的固定组合构成:
- 特征声明(Characteristic Declaration):属性类型为
0x2803,包含特征属性、值句柄和特征 UUID。 - 特征值(Characteristic Value):属性类型为特征 UUID,存储实际数据。
- 特征描述符(Characteristic Descriptor):可选,例如客户端特征配置描述符(CCCD,UUID
0x2902)用于开启通知/指示。
特征声明属性的值结构如下:
typedef struct {
uint8_t properties; // 位掩码:读/写/通知/指示等
uint16_t value_handle; // 特征值属性句柄
uint16_t uuid; // 特征 UUID 或 128-bit UUID 的低16位
} __attribute__((packed)) characteristic_decl_t;
常见错误: 实现特征读写时,忘记定义 CCCD 导致通知无法开启。CCCD 必须为每个支持通知/指示的特征手动添加描述符,并处理 GATT 客户端的写请求。
1.4 服务与特征层次
GATT 的层次结构从顶层到底层依次为:Profile → Service → Characteristic → Descriptor。
服务分为主要服务(Primary Service)和次要服务(Secondary Service)。每个服务由一个服务声明属性(UUID 0x2800 或 0x2801)起始,其值均为服务 UUID。
以下是一个典型服务层次示例:
// 主要服务声明:UUID = 0x180D (Heart Rate Service)
// ├── 特征声明:UUID = 0x2A37 (Heart Rate Measurement)
// │ ├── 特征值属性(value_handle = 0x0020)
// │ └── CCCD描述符(UUID = 0x2902, handle = 0x0022)
// └── 特征声明:UUID = 0x2A38 (Body Sensor Location)
// └── 特征值属性(value_handle = 0x0024)
1.5 UUID 规范
Bluetooth SIG 定义了标准化 UUID 及自定义 128-bit UUID:
| UUID 类型 | 长度 | 示例 | 用途 |
|---|---|---|---|
| 16-bit UUID (SIG 标准) | 2 字节 | 0x180D | 已发布服务/特征 |
| 32-bit UUID | 4 字节 | 0x0000180D-0000-1000-8000-00805F9B34FB 的低4字节 | 极少使用 |
| 128-bit UUID (自定义) | 16 字节 | E20A39F4-73F5-4BC4-A12F-17D1AD07A961 | 私有服务/特征 |
自定义 128-bit UUID 的 Base UUID 为 00000000-0000-1000-8000-00805F9B34FB,将前32位替换即可。例如:
// 将自定义特征 UUID 注册到 GATT 数据库
static const ble_uuid128_t custom_char_uuid =
{
.u = { .type = BLE_UUID_TYPE_128 },
.value = { 0x61, 0xA9, 0x07, 0xAD, 0xD1, 0x17, 0x2F, 0xA1,
0xC4, 0xBC, 0xF5, 0x73, 0xF4, 0x39, 0x0A, 0xE2 }
};
实现流程总结: GATT 特征模型的搭建步骤:
1) 分配属性句柄(按顺序递增);
2) 声明服务(Primary Service Declaration);
3) 在每个服务内添加特征声明及对应特征值属性;
4) 若需要通知/指示,添加 CCCD 描述符;
5) 填充权限、UUID、初始值。
1) 分配属性句柄(按顺序递增);
2) 声明服务(Primary Service Declaration);
3) 在每个服务内添加特征声明及对应特征值属性;
4) 若需要通知/指示,添加 CCCD 描述符;
5) 填充权限、UUID、初始值。
1.6 GATT 层次结构图
1.7 ATT 与 GATT 的关系总结
| 层次 | 职责 | 典型 PDU |
|---|---|---|
| ATT | 传输属性值,管理句柄、权限 | Read Req/Rsp, Write Req/Cmd, Handle Value Ntf/Ind |
| GATT | 定义服务/特征/描述符的架构和交互流程 | Discover All Primary Services, Read Characteristic Value |
调试建议: 使用 nRF Connect 或 LightBlue 等工具扫描设备后,查看 GATT 数据库树形结构,核对服务声明、特征声明、描述符的排列顺序和句柄值是否与代码中定义的数据库一致。