目录
[1 GATT 基本概念](#1 GATT 基本概念)
[1.1 GATT 的介绍](#1.1 GATT 的介绍)
[1.2 GATT 的角色](#1.2 GATT 的角色)
[1.3 核心组件](#1.3 核心组件)
[1.4 客户端操作](#1.4 客户端操作)
[2 bt_gatt_discover函数的功能和应用](#2 bt_gatt_discover函数的功能和应用)
[2.1 函数介绍](#2.1 函数介绍)
[2.1 发现类型(Discover Type)](#2.1 发现类型(Discover Type))
[3 典型使用流程](#3 典型使用流程)
[3.1 服务发现示例](#3.1 服务发现示例)
[3.2 级联发现模式](#3.2 级联发现模式)
[3.3 按UUID过滤发现](#3.3 按UUID过滤发现)
[3.4 发现描述符](#3.4 发现描述符)
[4 错误处理](#4 错误处理)
[4.1 常见错误码](#4.1 常见错误码)
[4.2 错误处理示例](#4.2 错误处理示例)
[5 性能优化建议](#5 性能优化建议)
[6 资源管理注意事项](#6 资源管理注意事项)
[6.1 参数生命周期](#6.1 参数生命周期)
[6.2 取消发现](#6.2 取消发现)
概述
本文介绍了蓝牙低功耗(BLE)中的GATT协议及其核心功能。GATT定义了BLE设备通过服务和特征交换数据的标准框架,包含服务器和客户端两种角色。文章详细解析了GATT的核心组件(服务、特性、描述符)及其层级结构,并以Zephyr协议栈中的bt_gatt_discover函数为例,说明其参数配置、发现类型和使用流程,包括服务发现、级联发现、UUID过滤等典型应用场景。最后还列举了常见错误码及处理方法,为BLE应用的开发提供了实用指导。
1 GATT 基本概念
1.1 GATT的介绍
GATT (Generic Attribute Profile) 是 Bluetooth Low Energy (BLE) 的核心协议,定义了 数据通信的标准框架 ,使BLE设备能够通过 服务(Services) 和 特征(Characteristics) 交换数据。
1.2 GATT 的角色
角色 | 说明 | 典型设备 |
---|---|---|
GATT 服务器(Server) | 存储并提供数据(如传感器数据) | 心率带、温度计 |
GATT 客户端(Client) | 读取或写入服务器数据 | 手机、中央设备 |
1.3 核心组件
1)层级结构
cpp
GATT Profile
├── Services (服务)
│ ├── Characteristics (特性)
│ │ ├── Value (值)
│ │ ├── Descriptors (描述符)
│ │ │ └── Client Characteristic Configuration (CCC)
│ │ └── Properties (属性)
│ └── Includes (包含服务)
└── Attributes (属性)
2) 关键组件说明
组件 | 说明 | 示例UUID |
---|---|---|
服务(Service) | 功能逻辑集合 | 0x180A (设备信息服务) |
特性(Characteristic) | 服务中的数据项 | 0x2A29 (厂商名称) |
描述符(Descriptor) | 特性的元数据 | 0x2902 (CCC描述符) |
属性(Attribute) | 数据库基本单元 | 由协议栈管理 |
1.4 客户端操作
操作 | 函数(Zephyr示例) | 说明 |
---|---|---|
发现服务 | bt_gatt_discover() |
扫描远程设备的GATT表 |
读取特征值 | bt_gatt_read() |
读取数据(如电池电量) |
写入特征值 | bt_gatt_write() |
发送命令或配置 |
启用通知 | bt_gatt_subscribe() |
订阅实时数据(如心率) |
2 bt_gatt_discover函数的功能和应用
2.1 函数介绍
bt_gatt_discover
是 Zephyr BLE 协议栈中用于发现远程设备 GATT 服务的核心函数,下面我将从多个维度进行详细说明:
- 函数原型与参数
cpp
int bt_gatt_discover(
struct bt_conn *conn,
struct bt_gatt_discover_params *params
);
2)参数说明:
-
conn:已建立的BLE连接句柄
-
params:发现参数结构体,包含以下关键字段:
cpp
struct bt_gatt_discover_params {
const struct bt_uuid *uuid; // 目标UUID(可选过滤条件)
uint16_t start_handle; // 起始属性句柄(通常0x0001)
uint16_t end_handle; // 结束属性句柄(通常0xFFFF)
enum bt_gatt_discover_type type; // 发现类型
void (*func)(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params);
};
2.1 发现类型(Discover Type)
类型枚举值 | 说明 | 对应ATT操作 |
---|---|---|
BT_GATT_DISCOVER_PRIMARY |
发现主服务 | ATT Read By Group Type Req |
BT_GATT_DISCOVER_SECONDARY |
发现次要服务 | ATT Read By Group Type Req |
BT_GATT_DISCOVER_INCLUDE |
发现包含的服务 | ATT Read By Type Req |
BT_GATT_DISCOVER_CHARACTERISTIC |
发现特性 | ATT Read By Type Req |
BT_GATT_DISCOVER_DESCRIPTOR |
发现描述符 | ATT Find Information Req |
BT_GATT_DISCOVER_STD_CHAR_DESC |
发现标准特性描述符 | ATT Read By Type Req |
3 典型使用流程
3.1 服务发现示例
cpp
static struct bt_gatt_discover_params discover_params;
static void discover_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
if (!attr) {
printk("Discovery complete\n");
return;
}
switch (params->type) {
case BT_GATT_DISCOVER_PRIMARY: {
struct bt_gatt_service_val *svc = attr->user_data;
printk("Service found: start_handle=0x%04X, end_handle=0x%04X\n",
attr->handle, svc->end_handle);
break;
}
case BT_GATT_DISCOVER_CHARACTERISTIC: {
struct bt_gatt_chrc *chrc = attr->user_data;
printk("Characteristic: handle=0x%04X, properties=0x%02X\n",
chrc->value_handle, chrc->properties);
break;
}
}
}
void start_discovery(struct bt_conn *conn)
{
discover_params.uuid = NULL; // 发现所有主服务
discover_params.start_handle = 0x0001;
discover_params.end_handle = 0xFFFF;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
discover_params.func = discover_cb;
int err = bt_gatt_discover(conn, &discover_params);
if (err) {
printk("Discovery failed to start (err %d)\n", err);
}
}
3.2 级联发现模式
cpp
static void discover_chars(struct bt_conn *conn, uint16_t start, uint16_t end)
{
static struct bt_gatt_discover_params params;
params.start_handle = start;
params.end_handle = end;
params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
params.func = discover_cb;
bt_gatt_discover(conn, ¶ms);
}
static void discover_cb(...)
{
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
// 发现主服务后继续发现特性
struct bt_gatt_service_val *svc = attr->user_data;
discover_chars(conn, attr->handle + 1, svc->end_handle);
}
}
3.3 按UUID过滤发现
cpp
static struct bt_uuid_16 find_uuid = BT_UUID_INIT_16(0x180F); // 电池服务
void find_battery_service(struct bt_conn *conn)
{
discover_params.uuid = &find_uuid.uuid;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
bt_gatt_discover(conn, &discover_params);
}
3.4 发现描述符
cpp
void discover_descriptors(struct bt_conn *conn, uint16_t start, uint16_t end)
{
discover_params.start_handle = start;
discover_params.end_handle = end;
discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
bt_gatt_discover(conn, &discover_params);
}
4 错误处理
4.1 常见错误码
错误码 | 说明 |
---|---|
0 | 成功 |
-ENOTCONN | 未建立连接 |
-EINVAL | 无效参数 |
-ENOMEM | 内存不足 |
-EOPNOTSUPP | 操作不支持 |
4.2 错误处理示例
cpp
int err = bt_gatt_discover(conn, ¶ms);
if (err) {
printk("Error %d during discovery\n", err);
switch (err) {
case -ENOTCONN:
// 处理连接问题
break;
case -EINVAL:
// 检查参数有效性
break;
}
}
5 性能优化建议
分阶段发现:先发现服务,再根据需要发现特性和描述符
缓存发现结果:避免重复发现
合理设置句柄范围:缩小start_handle/end_handle范围
使用UUID过滤:减少不必要的发现操作
6 资源管理注意事项
6.1 参数生命周期
cpp
// 错误:使用栈变量(函数返回后失效)
void start_temp_discovery(struct bt_conn *conn) {
struct bt_gatt_discover_params temp_params = {...};
bt_gatt_discover(conn, &temp_params); // 危险!
}
// 正确:使用静态或动态分配
static struct bt_gatt_discover_params persistent_params;
6.2 取消发现
cpp
void cancel_discovery(struct bt_conn *conn) {
bt_gatt_discover_cancel(conn, &discover_params);
}