目录
概述
本文详细介绍了蓝牙低功耗(BLE)中的GATT协议及其关键组件,重点分析了Zephyr协议栈中的bt_gatt_read函数。文章首先阐述了GATT的基本概念及其在BLE通信中的核心作用,包括服务(Service)、特性(Characteristic)和描述符(Descriptor)的层级结构。然后深入解析了bt_gatt_read函数的原型参数、读取模式(单句柄/多句柄)及具体实现方法,并提供了长数据分片读取和安全读取的代码示例。此外,文章还介绍了常见的ATT错误处理方案,提出了通过MTU协商和批量读取优化性能的方法,最后强调了参数生命周期管理和连接状态检查等资源管理注意事项。全文通过丰富的代码示例和结构化的技术说明,为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.3.1 层级结构
GATT Profile
├── Services (服务)
│ ├── Characteristics (特性)
│ │ ├── Value (值)
│ │ ├── Descriptors (描述符)
│ │ │ └── Client Characteristic Configuration (CCC)
│ │ └── Properties (属性)
│ └── Includes (包含服务)
└── Attributes (属性)
1.3.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_read函数介绍
bt_gatt_read
是 Zephyr BLE 协议栈中用于从远程设备读取 GATT 特性值或描述符的核心函数。下面我将从多个技术维度进行详细说明:
2.1 函数原型与参数
int bt_gatt_read(
struct bt_conn *conn,
struct bt_gatt_read_params *params
);
参数说明:
conn:已建立的BLE连接句柄
params:读取参数结构体,包含以下关键字段:
struct bt_gatt_read_params {
uint16_t handle_count; // 要读取的句柄数量
union {
struct {
uint16_t handle; // 单个句柄读取时的目标句柄
uint16_t offset; // 读取偏移量
} single;
struct {
uint16_t *handles; // 多个句柄数组
uint16_t *offsets; // 对应的偏移量数组
} multiple;
};
void (*func)(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length);
};
2.2 读取模式
2.2.1 单句柄读取
static void read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
if (err) {
printk("Read failed (err 0x%02x)\n", err);
return;
}
printk("Read %u bytes: ", length);
for (uint16_t i = 0; i < length; i++) {
printk("%02X ", ((const uint8_t *)data)[i]);
}
printk("\n");
}
void read_characteristic(struct bt_conn *conn, uint16_t handle)
{
static struct bt_gatt_read_params params = {
.func = read_cb,
.handle_count = 1,
.single.handle = handle,
.single.offset = 0, // 从起始位置读取
};
int err = bt_gatt_read(conn, ¶ms);
if (err) {
printk("Read request failed (err %d)\n", err);
}
}
2.2.2 多句柄批量读取
static uint16_t handles[] = {0x0003, 0x0005, 0x0007};
static uint16_t offsets[] = {0, 0, 0}; // 每个句柄的偏移量
static void multi_read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
static uint8_t read_count = 0;
if (err) {
printk("Multi-read error (err 0x%02x)\n", err);
return;
}
printk("Read chunk %d: len=%u\n", ++read_count, length);
if (length == 0) {
printk("Multi-read complete\n");
read_count = 0;
}
}
void read_multiple_characteristics(struct bt_conn *conn)
{
static struct bt_gatt_read_params params = {
.func = multi_read_cb,
.handle_count = ARRAY_SIZE(handles),
.multiple.handles = handles,
.multiple.offsets = offsets,
};
bt_gatt_read(conn, ¶ms);
}
3 bt_gatt_read的用法
3.1
长数据分片读取
static uint16_t current_offset = 0;
static uint8_t buffer[256];
static void long_read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
if (err) {
printk("Long read error (err 0x%02x)\n", err);
return;
}
if (length > 0) {
// 存储接收到的数据
memcpy(buffer + current_offset, data, length);
current_offset += length;
// 继续读取下一片段
params->single.offset = current_offset;
bt_gatt_read(conn, params);
} else {
printk("Long read complete, total %u bytes\n", current_offset);
current_offset = 0;
}
}
void read_long_characteristic(struct bt_conn *conn, uint16_t handle)
{
static struct bt_gatt_read_params params = {
.func = long_read_cb,
.handle_count = 1,
.single.handle = handle,
.single.offset = 0,
};
current_offset = 0;
bt_gatt_read(conn, ¶ms);
}
3.2 带认证的读取
void read_with_security(struct bt_conn *conn, uint16_t handle)
{
// 首先检查安全等级
if (bt_conn_get_security(conn) < BT_SECURITY_L2) {
// 提升安全等级
int err = bt_conn_set_security(conn, BT_SECURITY_L2);
if (err) {
printk("Security upgrade failed (err %d)\n", err);
return;
}
// 通常需要等待安全等级提升事件
// 这里简化处理,实际应使用状态机
k_sleep(K_MSEC(500));
}
// 执行安全读取
struct bt_gatt_read_params params = {
.func = read_cb,
.handle_count = 1,
.single.handle = handle,
};
bt_gatt_read(conn, ¶ms);
}
4 错误处理
4.1 常见ATT错误码
错误码 | 说明 |
---|---|
0x00 | 成功 |
0x01 | 无效句柄 |
0x02 | 读取不被允许 |
0x04 | 无效PDU |
0x05 | 认证不足 |
0x06 | 请求不支持 |
0x07 | 无效偏移量 |
0x08 | 授权不足 |
4.2 错误处理示例
static void read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
switch (err) {
case 0x00:
// 成功处理数据
break;
case 0x05: // 认证不足
printk("Authentication required\n");
bt_conn_set_security(conn, BT_SECURITY_L2);
break;
case 0x02: // 读取不被允许
printk("Read not permitted\n");
break;
default:
printk("Read error 0x%02x\n", err);
}
}
5 性能优化
5.1 MTU协商优化
static void exchange_mtu_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_exchange_params *params)
{
if (err) {
printk("MTU exchange failed (err 0x%02x)\n", err);
return;
}
uint16_t mtu = bt_gatt_get_mtu(conn);
printk("MTU updated to %u\n", mtu);
}
void optimize_read_size(struct bt_conn *conn)
{
static struct bt_gatt_exchange_params params = {
.func = exchange_mtu_cb
};
bt_gatt_exchange_mtu(conn, ¶ms);
}
5.2 批量读取优化
void optimized_batch_read(struct bt_conn *conn)
{
// 按物理邻近的句柄分组读取
static uint16_t group1[] = {0x0003, 0x0004, 0x0005};
static uint16_t group2[] = {0x0007, 0x0008};
struct bt_gatt_read_params params[2] = {
{.handle_count = ARRAY_SIZE(group1), .multiple.handles = group1},
{.handle_count = ARRAY_SIZE(group2), .multiple.handles = group2},
};
bt_gatt_read(conn, ¶ms[0]);
bt_gatt_read(conn, ¶ms[1]);
}
6 资源管理注意事项
6.1 参数生命周期管理
// 错误示例(栈变量风险)
void temp_read(struct bt_conn *conn, uint16_t handle) {
struct bt_gatt_read_params temp = {...};
bt_gatt_read(conn, &temp); // 回调时temp可能已失效
}
// 正确做法(静态或动态分配)
static struct bt_gatt_read_params persistent_params;
// 或
struct bt_gatt_read_params *params = k_malloc(sizeof(*params));
6.2 连接状态检查:
if (bt_conn_get_state(conn) != BT_CONN_CONNECTED) {
printk("Connection not ready\n");
return;
}
6.3 取消读取操作
void cancel_read(struct bt_conn *conn) {
bt_gatt_read_cancel(conn, ¶ms);
}