GATT 服务框架
GATT 服务框架是 AW30N BLE SDK 中基于杰理 gatt_common 模块构建的通用 BLE GATT 服务/客户端子系统,它为上层应用屏蔽了 btstack 协议栈细节,以事件回调 + 配置结构体的方式统一管理广播、扫描、连接、服务搜索、ATT 读写与安全配对。
Purpose and Scope
本文档介绍 AW30N BLE SDK 中 GATT 服务框架的整体设计与实现,覆盖:
gatt_common公共层(le_gatt_common.c/.h):连接句柄管理、角色管理、ATT 数据发送、公共初始化入口ble_comm_init;- GATT Server 子系统(
le_gatt_server.c):广播配置、profile(ATT 表)注册、读写回调、连接参数更新、OTA 钩子; - GATT Client 子系统(
le_gatt_client.c):扫描/连接配置、UUID 搜索、CCC 自动使能、数据上报; - 协议栈暴露的 GATT 客户端底层 API(
sdk/apps/include_lib/bt_include/le/gatt.h); - 安全配对(SM)配置(
sm_cfg_t)与事件模型(gatt_comm_event_e)。
以下内容属于兄弟页面范畴,本文不展开:BLE 广播/扫描底层驱动、SM 配对协议细节、具体业务 Profile(如 HID、SPPLE、OTA/RCSP)的属性表定义。如需了解具体业务 Profile 的属性表,请参阅对应 Profile 文档页。
Overview
在 BLE 开发中,GATT(Generic Attribute Profile)负责在已建立的 LE 链路上组织属性(Attribute)与特征(Characteristic)的数据交互。AW30N SDK 将 GATT 的能力封装为三个子模块:
| 子模块 | 源文件 | 职责 |
|---|---|---|
| 公共层 gatt_common | le_gatt_common.c | 初始化入口、连接/角色/句柄表、ATT 发送与流控、模块使能 |
| GATT Server | le_gatt_server.c | 从机角色:广播、profile 注册、ATT 读写回调分发、连接参数更新、OTA |
| GATT Client | le_gatt_client.c | 主机角色:扫描匹配、发起连接、UUID 搜索、CCC 使能、数据接收 |
三个模块共享同一套配置与控制模型:上层通过 gatt_ctrl_t 控制块把 server/client/SM 配置一次性交给 ble_comm_init,之后所有协议栈事件都通过各角色配置中的 event_packet_handler 回调以 gatt_comm_event_e 事件枚举形式上报给应用。这样的设计意图是:把"链路生命周期"与"业务数据"解耦——应用只需要关心连接建立/断开/加密完成等事件以及 ATT 读写内容,而无需理解 btstack 的 packet handler 机制与 HCI 命令细节。
框架由编译宏控制裁剪:
#if (TRANS_DATA_HID_EN || TRANS_DATA_SPPLE_EN)
#if TCFG_USER_BLE_ENABLE && CONFIG_BT_GATT_COMMON_ENABLE
Source: le_gatt_common.h
即:只有使能了 HID 透传或 SPPLE 透传(TRANS_DATA_HID_EN / TRANS_DATA_SPPLE_EN),且打开 TCFG_USER_BLE_ENABLE 与 CONFIG_BT_GATT_COMMON_ENABLE 时,框架代码才会被编译,符合 SDK 面向裁剪(256KB 补丁包等)的定位。
Architecture
flowchart TD
subgraph sg_App["应用层 (App Profile)"]
AppCB["event_packet_handler<br/>GATT 事件回调"]
AttCB["att_read_cb / att_write_cb<br/>ATT 读写回调"]
end
subgraph sg_Common["GATT 公共层 gatt_common"]
Ctrl["gatt_ctrl_t 控制块"]
Comm["le_gatt_common.c<br/>句柄/角色/状态管理"]
Server["le_gatt_server.c<br/>server_ctl_t"]
Client["le_gatt_client.c<br/>client 控制块"]
end
subgraph sg_Stack["BT 协议栈 (btstack)"]
GattH["gatt.h<br/>gatt_client_* 系列 API"]
HCI["HCI / Link Layer<br/>链路与广播"]
end
AppCB -->|"注册回调"| Ctrl
AttCB -->|"注册回调"| Server
Ctrl -->|"ble_comm_init"| Comm
Ctrl --> Server
Ctrl --> Client
Comm -->|"连接/状态管理"| Server
Comm -->|"连接/状态管理"| Client
Server -->|"ATT 收发"| GattH
Client -->|"ATT 收发/搜索"| GattH
GattH --> HCI
Server -->|"事件上报"| AppCB
Client -->|"事件上报"| AppCB
Server -->|"读写请求分发"| AttCB
架构说明:
- 应用层通过
gatt_ctrl_t把三个配置结构体(gatt_server_cfg_t、gatt_client_cfg_t、sm_cfg_t)注入公共层,并在结构体中提供回调函数指针。这是典型的"配置注入 + 回调"模式:框架不感知具体业务,业务通过回调感知链路事件与数据。 - 公共层持有
gatt_server_conn_handle[]/gatt_client_conn_handle[]等静态句柄表(见下文"连接与角色管理"),为 Server/Client 提供统一的连接状态跟踪与 index 分配。 - Server/Client 子模块分别处理从机侧(广播、被连接、ATT 读写)与主机侧(扫描、连接、搜索、通知)的完整生命周期,最终都通过 btstack 的
gatt_client_*API 与 HCI 交互。 - 事件上报统一走
gatt_comm_event_e枚举(见"事件模型"),使上层代码只需一个 switch 分支即可处理所有链路事件。
关键设计决策
- 单一控制块
gatt_ctrl_t:把所有配置(MTU、缓存、server/client/sm 配置、HCI 回调)聚合在一个结构体中,ble_comm_init一次注册,避免了多个分散的初始化接口,也便于多机(multi_dev_flag)场景扩展。 - 回调优先于轮询:
GATT_COMM_EVENT_CAN_SEND_NOW等事件让应用在协议栈发送完成后才填充数据,实现"填满即发、发完再填"的流控,避免数据堆积。 - 可裁剪编译:整个框架以
TRANS_DATA_HID_EN || TRANS_DATA_SPPLE_EN和TCFG_USER_BLE_ENABLE && CONFIG_BT_GATT_COMMON_ENABLE双重宏包裹,未使能时零代码体积。
实现剖析
1. 公共层:初始化与控制块
公共层的核心状态与入口集中在 le_gatt_common.c:
static u32 *gatt_ram_buffer;
static u8 cbk_event_type;
static gatt_ctrl_t *gatt_control_block;
#define CLR_HANDLER_ROLE() cbk_event_type = 0
#define SET_HANDLER_ROLE(a) cbk_event_type = (1<<a)
#define ADD_HANDLER_ROLE(a) cbk_event_type |= (1<<a)
#define CHECK_HANDLER_ROLE(a) (cbk_event_type & (1<<a))
static u16 gatt_server_conn_handle[SUPPORT_MAX_GATT_SERVER];
static u16 gatt_client_conn_handle[SUPPORT_MAX_GATT_CLIENT];
static u8 gatt_server_conn_handle_state[SUPPORT_MAX_GATT_SERVER];//BLE_ST_CONNECT,BLE_ST_SEND_DISCONN,BLE_ST_NOTIFY_IDICATE
static u8 gatt_client_conn_handle_state[SUPPORT_MAX_GATT_CLIENT];//BLE_ST_CONNECT,BLE_ST_SEND_DISCONN,BLE_ST_SEARCH_COMPLETE
Source: le_gatt_common.c
要点:
gatt_control_block保存ble_comm_init传入的gatt_ctrl_t指针,是全局唯一的配置持有者;cbk_event_type是一个位掩码,用SET/ADD/CHECK_HANDLER_ROLE宏标记当前事件由哪个角色(server/client)处理,避免两个角色争抢同一协议栈事件;- server 与 client 各自维护独立的连接句柄表与状态表,容量由
SUPPORT_MAX_GATT_SERVER/SUPPORT_MAX_GATT_CLIENT决定,这两个宏来自配置CONFIG_BT_GATT_SERVER_NUM/CONFIG_BT_GATT_CLIENT_NUM:
#define SUPPORT_MAX_GATT_SERVER CONFIG_BT_GATT_SERVER_NUM
#define SUPPORT_MAX_GATT_CLIENT CONFIG_BT_GATT_CLIENT_NUM
#define GATT_ROLE_CLIENT 1
#define GATT_ROLE_SERVER 0
#define INVAIL_INDEX ((s8)-1)
#define INVAIL_CONN_HANDLE (0)
Source: le_gatt_common.h
2. 连接与角色管理
公共层以"句柄 → index"的线性映射管理所有连接。ble_comm_dev_get_index 根据角色在对应的句柄表中顺序查找:
s8 ble_comm_dev_get_index(u16 handle, u8 role)
{
s8 i;
u16 *group_handle;
u8 count;
if (GATT_ROLE_SERVER == role) {
group_handle = gatt_server_conn_handle;
count = SUPPORT_MAX_GATT_SERVER;
} else {
group_handle = gatt_client_conn_handle;
count = SUPPORT_MAX_GATT_CLIENT;
}
for (i = 0; i < count; i++) {
if (handle == group_handle[i]) {
return i;
}
}
return INVAIL_INDEX;
}
Source: le_gatt_common.c
配套 API(见 le_gatt_common.h 声明)构成完整的连接管理原语集:
| 函数 | 作用 |
|---|---|
ble_comm_dev_get_index(handle, role) | 由连接句柄查角色索引,未找到返回 INVAIL_INDEX (-1) |
ble_comm_dev_get_idle_index(role) | 查找空闲槽位,用于分配新连接 |
ble_comm_dev_get_handle(index, role) | 由索引反查连接句柄 |
ble_comm_dev_get_handle_state(handle, role) | 查询连接状态(连接中/断开中/可发送等) |
ble_comm_dev_set_handle_state(handle, role, state) | 设置连接状态 |
ble_comm_dev_get_handle_role(handle) | 查询句柄属于 server 还是 client |
ble_comm_register_state_cbk(cbk) | 注册连接状态变化回调,供上层(如 UI/状态机)监听 |
ble_comm_disconnect(conn_handle) | 主动断开指定连接 |
设计意图:用 u16 连接句柄作为唯一键,把多连接(多机)场景收敛为一张可线性遍历的表;INVAIL_INDEX = -1 作为哨兵值区分"有效连接"与"未使用槽位"。由于单芯片 BLE 连接数通常为 1~3,线性查找的 O(n) 开销可接受,避免了维护哈希表的复杂度。
3. GATT Server 子系统
le_gatt_server.c 的核心状态机保存在 server_ctl_t 中:
typedef struct {
gatt_server_cfg_t *server_config; //server 配置
adv_cfg_t *adv_config; //adv 配置
const u8 *profile_data; //profile
u16 profile_data_len; //profile 长度
u8 server_work_state; //未链接ble 状态变化
u8 adv_ctrl_en: 4; //广播控制
u8 rcsp_ctrl_en: 4; //ota 升级控制
const struct conn_update_param_t *server_connection_param_table; //连接参数更新表
u8 server_connection_update_index: 4; //连接参数表执行id
u8 server_connection_update_count: 4; //连接参数表内个数
u8 server_encrypt_process; //配对执行流程
u16 server_operation_handle; //当前执行更新参数流程操作handle
u16 update_conn_handle; //ota 升级连接handle
u16 update_send_att_handle; //ota发送att handle
u8 update_send_att_handle_type; //ota发送att handle的类型
void (*update_ble_state_callback)(void *priv, ble_state_e state); //ota 状态回调
void (*update_recieve_callback)(void *priv, void *buf, u16 len); //ota 接收数据回调
void (*update_resume_send_wakeup)(void); //ota 发送唤醒
} server_ctl_t;
Source: le_gatt_server.c
Server 子系统职责分解:
- profile 注册:
profile_data+profile_data_len指向编译期生成的 ATT 属性表(由ble_gatt_server_profile_init/ble_comm_server_profile_init注册进协议栈),这是从机暴露给主机的全部服务/特征/描述符; - 广播控制:
adv_cfg_t决定广播包内容、周期、类型与通道;adv_ctrl_en位域控制模块是否自动开关广播(使能/断开后自动恢复广播);adv_auto_do位域支持"连接断开后自动重新广播"; - 连接参数更新:
server_connection_param_table是一张"连接参数表"(interval/latency/super timeout),server_connection_update_index/count记录当前执行到第几组参数,用于连接建立后按序请求更优的链路参数; - OTA 钩子:
update_*字段把 OTA(RCSP)升级流程直接嵌入 server 状态机——OTA 需要独占一条连接并持续大块发送数据,因此 server 额外保存update_conn_handle、update_send_att_handle及发送唤醒回调; - 事件转发:
__gatt_server_event_callback_handler把协议栈事件原样转发给应用注册的event_packet_handler,未注册时返回GATT_OP_RET_SUCESS兜底:
static int __gatt_server_event_callback_handler(int event, u8 *packet, u16 size, u8 *ext_param)
{
if (__this->server_config->event_packet_handler) {
return __this->server_config->event_packet_handler(event, packet, size, ext_param);
}
return GATT_OP_RET_SUCESS;
}
Source: le_gatt_server.c
Server 对外 API 包括 ble_gatt_server_init、ble_gatt_server_exit、ble_gatt_server_adv_enable、ble_gatt_server_get_work_state、ble_gatt_server_get_connect_state、ble_gatt_server_module_enable、ble_gatt_server_disconnect_all 等(声明于 le_gatt_common.h 第 234 行起)。
4. GATT Client 子系统
Client 子系统(le_gatt_client.c)把主机侧三段流程(扫描 → 连接 → 服务搜索)做成可配置的自动化链路,配置结构体如下:
typedef struct {
//common
u8 scan_auto_do: 4; /*是否gatt模块自动打开搜索(使能,断开等状态下)*/
u8 creat_auto_do: 4; /*是否gatt模块搜索到匹配的设备自动发起连接*/
u8 set_local_addr_tag;
u8 local_address_info[7];
//scan
u8 scan_type: 4;
u8 scan_filter: 4;
u16 scan_interval;
u16 scan_window;
//creat
u16 creat_conn_interval;
u16 creat_conn_latency;
u16 creat_conn_super_timeout;
//control
u32 creat_state_timeout_ms; /*创建连接后,超时未连上会取消连接,重新开搜索*/
u8 conn_update_accept;
} scan_conn_cfg_t;
typedef struct {
const client_match_cfg_t *match_devices; /*扫描匹配设备表*/
u16 match_devices_count;
u8 match_rssi_enable;
s8 match_rssi_value;
const target_uuid_t *search_uuid_group; /*搜索uuid表*/
u16 search_uuid_count;
u8 auto_enable_ccc; /*是否执行使能匹配的 NOTIFY和INDICATE 通知功能*/
} gatt_search_cfg_t;
Source: le_gatt_common.h
设计意图:
- 扫描即配置:
scan_interval/scan_window以 0.625ms 为单位,creat_conn_interval以 1.25ms 为单位,全部对齐 BLE 规范的时间单位,避免换算错误; - 自动连接链:
scan_auto_do打开扫描 →match_devices表匹配(可选 RSSI 门限)→creat_auto_do自动发起连接 → 连接后按search_uuid_group搜索 →auto_enable_ccc自动使能 NOTIFY/INDICATE。整条链路只需配置位域即可全自动执行,无需业务代码介入; - 失败自愈:
creat_state_timeout_ms在连接超时后自动取消连接并重新开搜索,形成"扫描-连接-失败-重扫"的闭环。
5. 安全配对(SM)配置
sm_cfg_t 集中了链路加密与配对策略,按主机/从机拆分自动请求与等待行为:
typedef struct {
u8 master_security_auto_req: 1; /*主机主动发起加密*/
u8 master_set_wait_security: 1; /*主机等待加密完成再执行profile搜索*/
u8 slave_security_auto_req: 1; /*从机发起加密请求命令*/
u8 slave_set_wait_security: 1; /*从机等待加密处理*/
u8 io_capabilities: 4; /*加密io能力配置*/
u8 authentication_req_flags; /*加密认证配置*/
u8 min_key_size; /*加密key支持的最小长度,range:7~16*/
u8 max_key_size;
int (*sm_cb_packet_handler)(...);
} sm_cfg_t;
Source: le_gatt_common.h
框架还定义了链路加密等级枚举 LINK_ENCRYPTION_NULL(无加密)、LINK_ENCRYPTION_PAIR_JUST_WORKS(Just Works 配对)、LINK_ENCRYPTION_PAIR_SC(安全连接 SC)以及 LINK_ENCRYPTION_RECONNECT = 0xf(重连场景)。GATT_COMM_EVENT_ENCRYPTION_REQUEST / GATT_COMM_EVENT_ENCRYPTION_CHANGE 事件把配对过程异步通知上层,GATT_COMM_EVENT_SM_PASSKEY_INPUT(0x90)用于需要输入 PIN 的场景(如键盘类设备)。
6. 事件模型 gatt_comm_event_e
框架把 btstack 原始事件归一化为统一枚举,按角色分组(见 le_gatt_common.h 第 49~92 行):
| 分组 | 事件 | 含义 |
|---|---|---|
| 公共(0x01~0x15) | GATT_COMM_EVENT_CONNECTION_COMPLETE | LE 链路连接完成 |
GATT_COMM_EVENT_DISCONNECT_COMPLETE | 断开完成 | |
GATT_COMM_EVENT_CONNECTION_COMPLETE_FAIL | 连接建立失败 | |
GATT_COMM_EVENT_ENCRYPTION_REQUEST/CHANGE | 加密请求 / 加密完成 | |
GATT_COMM_EVENT_CAN_SEND_NOW | 协议栈可发送新数据(流控信号) | |
GATT_COMM_EVENT_CONNECTION_UPDATE_COMPLETE | 连接参数更新完成 | |
GATT_COMM_EVENT_CONNECTION_PHY_UPDATE_COMPLETE | PHY 速率更新 | |
GATT_COMM_EVENT_CONNECTION_DATA_LENGTH_CHANGE | DLE 数据长度更新 | |
| ATT(0x20) | GATT_COMM_EVENT_MTU_EXCHANGE_COMPLETE | MTU 交换完成 |
| 从机(0x30~0x40) | GATT_COMM_EVENT_CONNECTION_UPDATE_REQUEST_RESULT | 请求连接参数更新的反馈 |
GATT_COMM_EVENT_DIRECT_ADV_TIMEOUT | 定向广播超时未被连接 | |
GATT_COMM_EVENT_SERVER_STATE | Server 状态变化 | |
GATT_COMM_EVENT_SERVER_INDICATION_COMPLETE | INDICATE 应答结束 | |
| 主机(0x50~0x60) | GATT_COMM_EVENT_SCAN_DEV_MATCH / SCAN_ADV_REPORT | 扫描到匹配设备 / 原始广播报告 |
GATT_COMM_EVENT_CLIENT_STATE | Client 状态变化 | |
GATT_COMM_EVENT_CREAT_CONN_TIMEOUT | 建立连接超时 | |
GATT_COMM_EVENT_GATT_SEARCH_PROFILE_START/MATCH_UUID/PROFILE_COMPLETE | 服务搜索生命周期 | |
GATT_COMM_EVENT_GATT_DATA_REPORT | 收到对端 NOTIFY/INDICATE 数据 | |
GATT_COMM_EVENT_GATT_SEARCH_DESCRIPTOR_RESULT | 搜索到描述符内容 | |
| SM(0x90) | GATT_COMM_EVENT_SM_PASSKEY_INPUT | 需要输入配对密钥 |
事件编号按段分配(0x01、0x20、0x30、0x40、0x50、0x60、0x90),既保留了扩展空间,也让应用层可以按数值区间快速判断事件来源角色。配套的错误码枚举 gatt_op_ret_e(GATT_OP_RET_SUCESS=0,GATT_CMD_RET_BUSY=-100 段,GATT_OP_ROLE_ERR=-200)统一了所有 API 的返回值语义,见 le_gatt_common.h 第 94~109 行。
Core Flow
从机(Server)侧完整流程
sequenceDiagram
participant App as 应用(Profile)
participant Srv as le_gatt_server
participant Comm as le_gatt_common
participant Stack as 协议栈(btstack)
participant Phone as 手机 Master
Phone->>Stack: 扫描到广播并发起连接
Stack->>Srv: 连接建立
Srv->>App: GATT_COMM_EVENT_CONNECTION_COMPLETE
Phone->>Stack: ATT MTU 交换
Stack->>App: GATT_COMM_EVENT_MTU_EXCHANGE_COMPLETE
Srv->>Stack: 按连接参数表请求更新参数
Stack-->>Srv: GATT_COMM_EVENT_CONNECTION_UPDATE_COMPLETE
Phone->>Stack: ATT Read Request
Stack->>Srv: 分发到 att_read_cb(conn_handle, att_handle, offset, buf, size)
Srv->>App: 应用填充属性值
Phone->>Stack: ATT Write Request
Stack->>Srv: 分发到 att_write_cb(...)
Phone->>Stack: 写 CCCD 使能通知
Stack->>App: 使能 NOTIFY/INDICATE
App->>Comm: ble_comm_att_send_data(conn_handle, att_handle, data, len, op_type)
Comm->>Stack: ATT Handle Value Notification
Stack->>App: GATT_COMM_EVENT_CAN_SEND_NOW(可继续发送)
Phone->>Stack: 断开链路
Stack->>App: GATT_COMM_EVENT_DISCONNECT_COMPLETE
Srv->>Stack: adv_auto_do 自动恢复广播
流程要点:从机数据通路是"主机读写 → att_read_cb/att_write_cb 同步回调 → 应用应答";上行通知通路则是"应用主动调 ble_comm_att_send_data → 协议栈发送 → CAN_SEND_NOW 节流"。两条通路分离,写操作天然受 ATT 流控约束,而通知通路依赖 ble_comm_att_check_send 与 cbuffer 做缓冲管理。
主机(Client)侧完整流程
sequenceDiagram
participant App as 应用(Client)
participant Cli as le_gatt_client
participant Stack as 协议栈(btstack)
participant Dev as 远端 Server 设备
App->>Cli: 模块使能(scan_auto_do)
Cli->>Stack: 开启扫描(scan_interval/scan_window)
Stack->>Cli: GATT_COMM_EVENT_SCAN_DEV_MATCH
Cli->>App: 上报匹配设备
Cli->>Stack: creat_auto_do 发起连接(creat_conn_interval/latency/super_timeout)
Stack->>Cli: GATT_COMM_EVENT_CONNECTION_COMPLETE
Cli->>App: 连接完成通知
Cli->>Stack: 搜索 search_uuid_group
Stack->>Cli: GATT_SEARCH_MATCH_UUID / PROFILE_COMPLETE
Cli->>Stack: auto_enable_ccc 写 CCCD
Dev-->>Stack: NOTIFY 数据
Stack->>Cli: GATT_COMM_EVENT_GATT_DATA_REPORT
Cli->>App: 数据上报
App->>Cli: 写特征值请求
Cli->>Stack: gatt_client_write_value_of_characteristic(...)
Stack->>Dev: ATT Write Request
流程要点:Client 侧链路完全由配置位域驱动(scan_auto_do → creat_auto_do → auto_enable_ccc),应用只需在 GATT_COMM_EVENT_GATT_DATA_REPORT 中消费数据、在 GATT_COMM_EVENT_GATT_SEARCH_PROFILE_COMPLETE 后获取服务句柄。底层搜索使用 gatt.h 提供的 gatt_client_deserialize_service / gatt_client_deserialize_characteristic 解析 ATT 报文。
Usage Examples
示例一:Server 配置结构与回调注册(框架定义)
以下代码展示了应用如何组装 server 配置——注册读写回调与事件回调(字段定义见 le_gatt_common.h):
typedef struct {
/*server端被主机操作 读写操作回调*/
u16(*att_read_cb)(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
int (*att_write_cb)(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
/*协议栈事件回调处理*/
int (*event_packet_handler)(int event, u8 *packet, u16 size, u8 *ext_param);
} gatt_server_cfg_t;
Source: le_gatt_common.h
att_read_cb返回填充到buffer的字节数,offset参数支持长特性值的分段读取(Read Blob);att_write_cb的transaction_mode区分 Write Request / Write Command / Write Long 等事务模式;event_packet_handler接收上述gatt_comm_event_e事件,是应用感知链路生命周期的唯一入口。
示例二:公共控制块聚合注册(框架定义)
typedef struct {
//connect
u16 mtu_size; /*mtu配置大小, range:23 ~517*/
u16 cbuffer_size; /*缓存buffer大小,>= mtu_size*/
u8 multi_dev_flag; /*多机使用标识*/
//config
gatt_server_cfg_t *server_config; /*gatt server 配置*/
gatt_client_cfg_t *client_config; /*gatt client 配置*/
sm_cfg_t *sm_config;
/*hci 回调,保留未用*/
int (*hci_cb_packet_handler)(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
} gatt_ctrl_t;
Source: le_gatt_common.h
应用侧典型初始化序列(伪代码,依据头文件声明):构造 gatt_ctrl_t → 填充 server_config/client_config/sm_config → 调用 ble_comm_init(&ctrl) → 按需调用 ble_comm_module_enable(1) / ble_gatt_server_adv_enable(1) 使能广播。multi_dev_flag 标记多机场景,cbuffer_size >= mtu_size 是必须满足的约束(ATT 发送缓存不能小于 MTU)。
示例三:协议栈 GATT 客户端 API(gatt.h)
btstack 层暴露给 Client 子系统的底层原语,供特征值读写与流控使用:
uint8_t gatt_client_read_value_of_characteristic_using_value_handle(btstack_packet_handler_t callback, hci_con_handle_t con_handle, uint16_t value_handle);
uint8_t gatt_client_write_value_of_characteristic_without_response(hci_con_handle_t con_handle, uint16_t value_handle, uint16_t value_length, uint8_t *value);
uint8_t gatt_client_write_value_of_characteristic(btstack_packet_handler_t callback, hci_con_handle_t con_handle, uint16_t value_handle, uint16_t value_length, uint8_t *data);
void gatt_client_request_can_send_now_event(hci_con_handle_t con_handle);
Source: gatt.h
配套的数据结构 le_service_t(服务起止句柄 + UUID)、le_characteristic_t(start_handle/value_handle/end_handle/properties + UUID)与 gatt_client_characteristic_descriptor_t 由 gatt_client_deserialize_* 系列函数从 ATT 报文反序列化得到,是 Client 搜索 Profile 时句柄/UUID 信息的来源。
示例四:公共数据发送与流控
框架对上层提供统一的 ATT 发送与缓存查询接口(声明于 le_gatt_common.h):
u32 ble_comm_cbuffer_vaild_len(u16 conn_handle); /*查询发送缓存可用长度*/
int ble_comm_att_send_data(u16 conn_handle, u16 att_handle, u8 *data, u16 len, att_op_type_e op_type); /*发送通知/指示数据*/
bool ble_comm_att_check_send(u16 conn_handle, u16 pre_send_len); /*检查能否发送 pre_send_len 字节*/
Source: le_gatt_common.h
典型的发送模式:先 ble_comm_att_check_send 判断缓存余量,再 ble_comm_att_send_data 发送,收到 GATT_COMM_EVENT_CAN_SEND_NOW 后继续填下一包,从而在不丢包的前提下最大化吞吐。
Configuration Options
公共控制块 gatt_ctrl_t
| 字段 | 类型 | 默认/范围 | 说明 |
|---|---|---|---|
mtu_size | u16 | 23 ~ 517 | ATT MTU 大小 |
cbuffer_size | u16 | ≥ mtu_size | ATT 发送缓存大小 |
multi_dev_flag | u8 | 0 | 多机(多连接)使用标识 |
server_config | gatt_server_cfg_t* | NULL | Server 配置(含读写/事件回调) |
client_config | gatt_client_cfg_t* | NULL | Client 配置(含事件回调) |
sm_config | sm_cfg_t* | NULL | 安全配对配置 |
hci_cb_packet_handler | 函数指针 | NULL | HCI 回调(保留未用) |
广播配置 adv_cfg_t
| 字段 | 类型 | 说明 |
|---|---|---|
adv_data / adv_data_len | const u8* / u8 | 无定向广播包数据及长度 |
rsp_data / rsp_data_len | const u8* / u8 | 扫描响应包数据及长度 |
adv_interval | u16 | 广播周期,单位 0.625ms,范围 0x0020~0x4000 |
adv_auto_do | 位域:4 | 模块是否自动打开广播(使能/断开等状态) |
adv_type | 位域:4 | 广播类型(无定向可连接/不可连接/定向等) |
adv_channel | u8 | 广播通道,bit0~2 对应 channel 37~39 |
direct_address_info[7] | u8[7] | 定向广播对端地址(addr_type + address) |
set_local_addr_tag | u8 | = USE_SET_LOCAL_ADDRESS_TAG(0x5a) 时使用 local_address_info,用于多机指定设备地址 |
local_address_info[7] | u8[7] | 指定设备地址(addr_type + address) |
扫描/连接配置 scan_conn_cfg_t
| 字段 | 类型 | 说明 |
|---|---|---|
scan_auto_do | 位域:4 | 模块自动打开搜索(使能/断开等状态) |
creat_auto_do | 位域:4 | 搜索到匹配设备后自动发起连接 |
set_local_addr_tag | u8 | 同 adv_cfg_t,用于指定本机地址 |
local_address_info[7] | u8[7] | 指定本机地址 |
scan_type | 位域:4 | 扫描类型 |
scan_filter | 位域:4 | 扫描重复过滤开关 |
scan_interval | u16 | 扫描周期,单位 0.625ms,≥ scan_window,范围 0x0004~0x4000 |
scan_window | u16 | 扫描窗口,单位 0.625ms,≤ scan_interval |
creat_conn_interval | u16 | 连接周期,单位 1.25ms,范围 0x0006~0x0c08 |
creat_conn_latency | u16 | 从机忽略连接事件个数(建议 interval×latency ≤ 2s) |
creat_conn_super_timeout | u16 | 连接超时,单位 10ms,范围 0x000a~0x0c08,建议 600 |
creat_state_timeout_ms | u32 | 建连超时后自动取消并重扫;=0 时只能手动取消 |
conn_update_accept | u8 | 连接过程中是否接受从机的连接参数更新请求 |
搜索配置 gatt_search_cfg_t
| 字段 | 类型 | 说明 |
|---|---|---|
match_devices / match_devices_count | const client_match_cfg_t* / u16 | 扫描匹配设备表及数量 |
match_rssi_enable / match_rssi_value | u8 / s8 | 自动建连时是否检测 RSSI 及最低门限 |
search_uuid_group / search_uuid_count | const target_uuid_t* / u16 | 连接后搜索的 UUID 表及数量 |
auto_enable_ccc | u8 | 是否自动使能匹配的 NOTIFY/INDICATE |
SM 配置 sm_cfg_t
| 字段 | 类型 | 说明 |
|---|---|---|
master_security_auto_req | 位域:1 | 主机主动发起加密 |
master_set_wait_security | 位域:1 | 主机等待加密完成再执行 profile 搜索 |
slave_security_auto_req | 位域:1 | 从机发起加密请求命令 |
slave_set_wait_security | 位域:1 | 从机等待加密处理 |
io_capabilities | 位域:4 | 加密 IO 能力配置 |
authentication_req_flags | u8 | 加密认证配置 |
min_key_size / max_key_size | u8 | 加密密钥长度范围,7~16 |
sm_cb_packet_handler | 函数指针 | SM 回调(保留未用) |
API Reference
公共层(le_gatt_common.h 第 213~231 行)
void ble_comm_init(const gatt_ctrl_t *control_blk)- 框架初始化入口,注册全部配置。应在 BLE 功能使能前调用。
void ble_comm_exit(void)/void ble_comm_module_enable(u8 en)- 退出 / 使能或关闭整个 GATT 公共模块。
u32 ble_comm_cbuffer_vaild_len(u16 conn_handle)- 返回指定连接的发送缓存可用字节数。
int ble_comm_att_send_data(u16 conn_handle, u16 att_handle, u8 *data, u16 len, att_op_type_e op_type)- 发送 NOTIFY/INDICATE 数据;返回
gatt_op_ret_e错误码。
- 发送 NOTIFY/INDICATE 数据;返回
bool ble_comm_att_check_send(u16 conn_handle, u16 pre_send_len)- 预检查能否发送
pre_send_len字节,用于大包拆分前的流控。
- 预检查能否发送
const char *ble_comm_get_gap_name(void)/void ble_comm_set_config_name(const char *name_p, u8 add_ext_name)- 读取/设置广播中的 GAP 设备名。
int ble_comm_disconnect(u16 conn_handle)— 主动断开连接。u8 ble_comm_dev_get_handle_state(u16 handle, u8 role)/void ble_comm_dev_set_handle_state(u16 handle, u8 role, u8 state)— 查询/设置连接状态(BLE_ST_CONNECT、BLE_ST_SEND_DISCONN、BLE_ST_NOTIFY_IDICATE、BLE_ST_SEARCH_COMPLETE等)。void ble_comm_register_state_cbk(void (*cbk)(u16 handle, u8 state))— 注册连接状态变化回调。s8 ble_comm_dev_get_index(u16 handle, u8 role)/s8 ble_comm_dev_get_idle_index(u8 role)— 句柄→索引 / 查找空闲索引。u8 ble_comm_dev_get_handle_role(u16 handle)/u16 ble_comm_dev_get_handle(u8 index, u8 role)— 句柄↔角色/索引互查。int ble_comm_set_connection_data_length(u16 conn_handle, u16 tx_octets, u16 tx_time)— 请求 DLE 数据长度更新。int ble_comm_set_connection_data_phy(u16 conn_handle, u8 tx_phy, u8 rx_phy, u16 phy_options)— 请求 PHY 速率更新。
Server 层(le_gatt_common.h 第 234 行起)
void ble_gatt_server_init(gatt_server_cfg_t *server_cfg)/void ble_gatt_server_exit(void)— 初始化/退出 Server。ble_state_e ble_gatt_server_get_work_state(void)— 获取 Server 整体工作状态(广播中/已连接等)。ble_state_e ble_gatt_server_get_connect_state(u16 conn_handle)— 获取指定连接状态。int ble_gatt_server_adv_enable(u32 en)— 打开/关闭广播。void ble_gatt_server_module_enable(u8 en)— 使能/关闭 Server 模块(关闭时停止广播与连接)。void ble_gatt_server_disconnect_all(void)— 断开所有 Server 侧连接(如关机流程)。
协议栈 GATT 客户端 API(gatt.h)
uint8_t gatt_client_read_value_of_characteristic_using_value_handle(btstack_packet_handler_t callback, hci_con_handle_t con_handle, uint16_t value_handle)— 读特征值。uint8_t gatt_client_read_long_value_of_characteristic_using_value_handle_with_offset(...)— 带偏移读长特征值(Read Blob)。uint8_t gatt_client_write_value_of_characteristic(callback, con_handle, value_handle, value_length, uint8_t *data)— 写特征值(带应答)。uint8_t gatt_client_write_value_of_characteristic_without_response(con_handle, value_handle, value_length, uint8_t *value)— 无应答写(Write Command)。void gatt_client_request_can_send_now_event(hci_con_handle_t con_handle)— 请求CAN_SEND_NOW事件,用于无应答写流控。void gatt_client_deserialize_service/characteristic/characteristic_descriptor(const uint8_t *packet, int offset, ...)— 从 ATT 报文反序列化服务/特征/描述符结构。
Failure Modes、边界与并发
错误码语义
框架统一使用 gatt_op_ret_e 作为所有 API 的返回类型(见 le_gatt_common.h 第 94~109 行):
| 错误码 | 值 | 触发场景 |
|---|---|---|
GATT_OP_RET_SUCESS | 0 | 执行成功 |
GATT_CMD_RET_BUSY | -100 | 命令处理忙(协议栈正忙) |
GATT_CMD_PARAM_OVERFLOW | -101 | 传参数溢出(超长数据) |
GATT_CMD_OPT_FAIL | -102 | 操作失败 |
GATT_BUFFER_FULL | -103 | 发送缓存已满 |
GATT_BUFFER_ERROR | -104 | 缓存出错 |
GATT_CMD_PARAM_ERROR | -105 | 传参出错 |
GATT_CMD_STACK_NOT_RUN | -106 | 协议栈未运行 |
GATT_CMD_USE_CCC_FAIL | -107 | 未使能通知,导致 NOTIFY/INDICATE 发送失败 |
GATT_OP_ROLE_ERR | -200 | 角色错误(在错误的角色上下文中调用) |
典型边界与失败场景
- 未使能 CCC 就发送通知:
GATT_CMD_USE_CCC_FAIL是上层最常见的错误。主机必须先写 CCCD 使能 NOTIFY/INDICATE(Client 侧由auto_enable_ccc自动完成,Server 侧需在收到使能后标记状态),否则发送被拒。 - 缓存满与背压:
GATT_BUFFER_FULL表示cbuffer_size耗尽。正确做法是收到GATT_COMM_EVENT_CAN_SEND_NOW再继续发送,而非轮询重试;ble_comm_att_check_send可在发送前预判。 - 句柄/索引越界:
ble_comm_dev_get_index未命中返回INVAIL_INDEX((s8)-1);连接句柄 0 被定义为INVAIL_CONN_HANDLE。上层用返回索引访问数组前必须先判>= 0。 - MTU 与缓存约束:
cbuffer_size必须 ≥mtu_size,否则单包 ATT 数据无法放入缓存。MTU 交换完成后,单次通知最大长度受 MTU-3 限制,大 payload 需应用层分包。 - 建连超时:
GATT_COMM_EVENT_CREAT_CONN_TIMEOUT与GATT_COMM_EVENT_CONNECTION_COMPLETE_FAIL分别覆盖"发起连接超时"与"连接建立失败"。creat_state_timeout_ms=0时框架不自动重扫,需业务手动处理,避免无限重连。 - 定向广播超时:
GATT_COMM_EVENT_DIRECT_ADV_TIMEOUT表明目标设备未在定向广播窗口内连接,Server 应切换为无定向广播或降频重试。 - 并发/重入:协议栈事件回调(
event_packet_handler、att_read_cb、att_write_cb)运行在蓝牙任务上下文中,上层回调内不应执行阻塞操作(如长时间循环、等待信号量),否则会阻塞整条链路事件处理。状态位域(adv_ctrl_en、server_connection_update_index等)由蓝牙任务独占读写,未加锁——这既是性能优化,也意味着跨任务访问这些字段是不安全的。 - 多连接(多机):
multi_dev_flag与set_local_addr_tag=USE_SET_LOCAL_ADDRESS_TAG配合,允许每台设备指定自己的广播/扫描地址;连接句柄表按角色分槽,任一槽位独立管理状态,互不阻塞。
Performance 与运维注意事项
- 流控模型:上行数据采用"检查-发送-等待 CAN_SEND_NOW"的推拉结合模型,避免无应答写导致的协议栈缓冲溢出;
ble_comm_cbuffer_vaild_len可实时查询余量。 - 链路参数优化:Server 通过
server_connection_param_table在连接建立后按序请求更优的连接参数(更小 interval、合理 latency),从而降低延迟与功耗;Client 可用conn_update_accept接受对端请求。GATT_COMM_EVENT_CONNECTION_PHY_UPDATE_COMPLETE与GATT_COMM_EVENT_CONNECTION_DATA_LENGTH_CHANGE事件可确认 2M PHY/DLE 生效,是吞吐优化的依据。 - 内存分段:框架代码在
SUPPORT_MS_EXTENSIONS下被放置到专用段(.ble_app_bss/.ble_app_data/.ble_app_text等),便于链接脚本统一规划 RAM/Flash 布局,做 256KB 级裁剪时不会污染主程序段。 - 运行时能力探测:
config_le_hci_connection_num、config_le_sm_support_enable、config_le_gatt_server_num、config_le_gatt_client_num是协议栈导出的全局配置常量,配合STACK_IS_SUPPORT_GATT_SERVER()等宏(le_gatt_common.h第 44~47 行)可在运行时判断当前固件的 GATT 能力,防止调用未编译的功能。 - 看门狗:
le_gatt_server.c中引用了clr_wdt(),在长流程(如 OTA 大块发送)中需要喂狗,上层回调若耗时过长必须自行确保看门狗不被饿死。
Extension Points
- 自定义 Profile(ATT 表):通过
ble_gatt_server_profile_init/ble_comm_server_profile_init(le_gatt_common.c前向声明)注册自定义profile_data,即可扩展任意服务/特征;属性值的读写逻辑在att_read_cb/att_write_cb中实现。这是 HID、SPPLE、OTA/RCSP 等业务 Profile 的公共挂载点。 - 事件回调:
gatt_server_cfg_t::event_packet_handler与gatt_client_cfg_t::event_packet_handler是业务接收所有链路事件的钩子;ble_comm_register_state_cbk提供更细粒度的连接状态通知,适合驱动 UI 状态机。 - 配对行为:通过
sm_cfg_t的 4 个安全位域 +io_capabilities+authentication_req_flags组合即可定制 Just Works / SC / 需要 PIN 的配对流程,无需改动框架代码;GATT_COMM_EVENT_SM_PASSKEY_INPUT提供密钥输入入口(对应ble_gatt_server_passkey_input/ble_gatt_client_passkey_input)。 - OTA 集成:
server_ctl_t内置update_*钩子(状态回调、接收回调、发送唤醒),RCSP/OTA 模块可直接挂接,无需另起连接管理逻辑。 - 底层能力:
gatt.h的gatt_client_*原语是 Client 侧定制数据通路的最后一道接口,如需绕过框架的自动搜索逻辑可在此层直接调用。
Tests
在已阅读的源码范围内未发现针对 gatt_common 框架的独立单元测试或自动化测试用例(SDK 为嵌入式固件工程,测试以整机联调与协议分析仪抓包为主)。框架的验证主要依赖:LOG_TAG "[GATT_SERVER]" / "[GATT_COMM]" 日志系统(LOG_ERROR_ENABLE/LOG_DEBUG_ENABLE/LOG_INFO_ENABLE,le_gatt_server.c 第 44~50 行)在真机上观察事件时序,以及补丁包目录(补丁包/)中随版本发布的增量代码作为回归参考。建议新增 Profile 时,先以 GATT_COMM_EVENT_* 事件日志核对连接、MTU、搜索、CCC 使能的完整时序,再验证数据收发。
Related Links
- le_gatt_common.h(框架全部配置结构与 API 声明)
- le_gatt_common.c(公共层:句柄/角色/状态管理)
- le_gatt_server.c(GATT Server 子系统)
- le_gatt_client.c(GATT Client 子系统)
- gatt.h(btstack GATT 客户端底层 API)
相关能力请参见:BLE 协议栈与广播/扫描机制、SM 安全配对、HID/SPPLE 业务 Profile、OTA/RCSP 升级流程等兄弟页面。