BLE 低功耗蓝牙
AC79NN SDK 的 BLE(低功耗蓝牙)子系统:基于 BTstack 协议栈的 GATT Server/Client 框架、WiFi 配网、透明数据传输、HID/HOGP、穿戴协议、Central 角色、2.4G 共存处理以及 BLE Mesh 节点实现。本文从应用层 Demo 到协议栈与硬件射频,端到端说明其架构、控制流、配置与扩展方式。
Purpose and Scope
本页覆盖 apps/common/ble/ 目录下的 BLE 应用层实现,以及与之配套的 third_party/common/ble_user.h 用户 API、btstack 协议栈接口和 btcontroller_modules.h 控制器接口。内容包括:
- BLE Demo 的整体组织方式与编译期选择机制(
TCFG_BLE_DEMO_SELECT) - 各 Demo 的职责:WiFi 配网(
le_net_cfg.c)、透明传输(le_trans_data.c)、HOGP(le_hogp.c)、穿戴协议(le_mambo.c)、Central 配网(le_net_cfg_central.c)、2.4G 共存(le_24g_deal.c)、BLE Mesh(apps/common/ble/mesh/) - GATT 数据收发路径、ATT 缓冲区布局、连接参数更新与流控机制
- 配置选项、核心 API、失败模式与扩展点
经典蓝牙(BR/EDR)的 A2DP/HFP 等属于独立话题,不在本页展开;BLE Mesh 的各个 vendor 模型示例(AliGenie/Tuya 等)仅做总览级介绍,详细模型文档见对应的 Mesh 页面。
Overview
AC79NN 是杰理(Jieli)面向 AIoT 场景的蓝牙 SoC 系列,其 BLE 子系统承担三类核心职责:
- 配网通道:设备作为 GATT Server,通过手机 App 写入 WiFi SSID/密码完成一键配网(
le_net_cfg.c及各家云端配网变体le_net_cfg_dui.c、le_net_cfg_tencent.c、le_net_cfg_qyai.c、le_net_cfg_turing.c)。 - 数据通道:为上层应用提供双向透传(
le_trans_data.c),支持 MTU 协商、发送缓冲、流控与速率/音频上传测试。 - 外围角色:HID 键盘鼠标(HOGP)、智能穿戴私有协议(Mambo)、Central 主动连接、以及智能家居 BLE Mesh 节点。
所有应用层 Demo 共用同一套底层框架:ble_user.h 提供用户级 API,BTstack 提供 GATT/ATT/SMP/L2CAP 协议实现,btcontroller_modules.h 连接链路层控制器,最终与经典蓝牙共享同一颗 2.4GHz 射频,因此需要 le_24g_deal.c 之类的共存处理。编译期通过 TCFG_BLE_DEMO_SELECT 宏从多个 DEF_BLE_DEMO_* 值中选定一个 Demo 编入固件,两个已核实的取值是 DEF_BLE_DEMO_NET_CFG(配网)与 DEF_BLE_DEMO_TRANS_DATA(透传)。
Architecture
flowchart TD
subgraph sg_App["应用层 (apps/common/ble)"]
NetCfg["le_net_cfg.c<br/>WiFi 配网 GATT Server"]
NetCfgDui["le_net_cfg_dui.c 等<br/>各家云端配网变体"]
TransData["le_trans_data.c<br/>透明数据传输"]
Hogp["le_hogp.c<br/>HID over GATT"]
Mambo["le_mambo.c<br/>穿戴私有协议"]
NetCentral["le_net_central.c<br/>Central 主动配网"]
Deal24G["le_24g_deal.c<br/>2.4G 共存处理"]
Mesh["mesh/examples<br/>BLE Mesh 节点"]
end
subgraph sg_Stack["协议栈层"]
BleUser["third_party/common/ble_user.h<br/>用户 API 封装"]
Btstack["btstack/btstack_task.h<br/>BlueKitchen BTstack"]
Controller["btcontroller_modules.h<br/>链路层控制器"]
end
subgraph sg_HW["硬件层"]
RF["2.4GHz RF<br/>(与经典蓝牙 BR/EDR 共享)"]
end
NetCfg --> BleUser
NetCfgDui --> NetCfg
TransData --> BleUser
Hogp --> BleUser
Mambo --> BleUser
NetCentral --> BleUser
Deal24G --> BleUser
Mesh --> BleUser
BleUser --> Btstack
Btstack --> Controller
Controller --> RF
架构图说明:
- 应用层:每个
le_*.c文件是一个独立 Demo,全部以#if (TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_XXX)编译开关包裹,一次固件只编译其中一个。它们负责定义 GATT 服务/特征值、广播数据、连接参数表,并通过回调把上层数据接入协议栈。 - 用户 API 层:
ble_user.h是所有 Demo 共同包含的头文件,屏蔽 BTstack 细节,向上层提供收发与状态回调注册接口。 - 协议栈层:BTstack 处理 GATT/ATT/SMP/L2CAP/LL 标准协议;
btcontroller_modules.h提供控制器模块接口(如 ATT 控制块、流控使能)。 - 硬件层:BLE 与经典蓝牙共用 2.4GHz 射频,
le_24g_deal.c专门处理两者同时工作时的调度与共存问题。
从源码可见,两个 Demo 都同样包含 btstack/btstack_task.h、btstack/bluetooth.h、btcontroller_modules.h、bt_common.h 与 third_party/common/ble_user.h,印证了"应用层 Demo 全部构建在统一协议栈框架之上"的设计。这样做的好处是:新增一个业务 Demo 只需复制现有 le_*.c 模板、修改 GATT 特征值与回调逻辑,协议栈与射频部分完全复用。
模块结构与 Demo 选择机制
编译期 Demo 开关
BLE 应用层以"一文件一 Demo"的方式组织,每个文件开头都有同样的编译门控。以配网 Demo 为例:
#if (TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_NET_CFG)
...
#endif
Source: le_net_cfg.c
透明传输 Demo 使用同样的模式:
#if (TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_TRANS_DATA)
...
#endif
Source: le_trans_data.c
TCFG_BLE_DEMO_SELECT 是工程级配置宏(在 app_config.h 或板级配置中定义),DEF_BLE_DEMO_NET_CFG / DEF_BLE_DEMO_TRANS_DATA 等取值定义了可选的 Demo 枚举。这种设计把"选择哪个业务"从代码中抽离为配置项:同一份 SDK 固件镜像模板,只需改一个宏即可切换成配网设备、透传设备或 HID 设备,降低多产品线维护成本。
ATT 缓冲区布局(所有 Server Demo 共用)
无论配网还是透传,GATT Server 都采用同一套内存分配策略:把"ATT 控制块 + 本地负载 + 发送环形缓冲"合并为一块 4 字节对齐的静态数组:
#define ATT_LOCAL_PAYLOAD_SIZE (128) //note: need >= 20
#define ATT_SEND_CBUF_SIZE (512) //note: need >= 20,缓存大小,可修改
#define ATT_RAM_BUFSIZE (ATT_CTRL_BLOCK_SIZE + ATT_LOCAL_PAYLOAD_SIZE + ATT_SEND_CBUF_SIZE)
static u8 att_ram_buffer[ATT_RAM_BUFSIZE] __attribute__((aligned(4)));
...
#if (ATT_RAM_BUFSIZE < 64)
#error "adv_data & rsp_data buffer error!!!!!!!!!!!!"
#endif
#define adv_data &att_ram_buffer[0]
#define scan_rsp_data &att_ram_buffer[32]
Source: le_net_cfg.c
设计意图:把广播包(adv_data)和扫描应答(scan_rsp_data)从同一块 RAM 中切片出来,减少静态内存碎片;__attribute__((aligned(4))) 保证控制器 DMA 访问安全;#error 在编译期拦截过小配置(广播包 31 字节 + 应答包 31 字节的最小需求,加上控制块后不足 64 字节必然出错)。注释中的 need >= 20 表明负载长度至少应覆盖经典 20 字节 ATT 默认 MTU 的写入上限。
广播与连接参数
配网 Demo 的广播间隔与连接参数如下:
#define ADV_INTERVAL_MIN (160)
//连接参数设置
static const uint8_t connection_update_enable = 1; ///0--disable, 1--enable
static uint8_t connection_update_cnt = 0; //
static const struct conn_update_param_t connection_param_table[] = {
{16, 24, 10, 600},//11
{12, 28, 10, 600},//3.7
{8, 20, 10, 600},
};
#define CONN_PARAM_TABLE_CNT (sizeof(connection_param_table)/sizeof(struct conn_update_param_t))
Source: le_net_cfg.c
ADV_INTERVAL_MIN = 160(单位 0.625ms,即 100ms)是广播间隔下限,配网场景下保证手机能快速发现设备。- 连接参数表是一个降级协商序列:首选
{16, 24, 10, 600}(间隔 20ms、从机延迟 10、超时 600),若对端拒绝则依次尝试更宽松的组合。connection_update_cnt记录协商次数,connection_update_enable是总开关,注释标注了每个组合对应的估算连接间隔(11ms / 3.7ms)。
WiFi 配网实现详解(le_net_cfg.c)
角色与数据流
设备作为 GATT Server,广播设备名为 gap_device_name[] = "wl80_ble_test"(实际名称运行时可由 bt_get_local_name() 覆盖)。手机 App(GATT Client)发现 ae82 服务后,向特征值 ATT_CHARACTERISTIC_ae82_01_VALUE_HANDLE 写入 JSON 配网指令;设备解析后调用系统 WiFi 配网接口,完成后通过 app_send_user_data 回复带杰理私有帧头的应答帧。
应答帧格式(rsp_cmd0)
//配网成回复命令
static const u8 rsp_cmd0[] = {
0x4A, 0x4C, 0x00, 0x0e, 0x10, 0x02,
'{', '"', 's', 't', 'a', 't', 'u', 's', '"', ':', '0', '}',
0x7C, 0xC3, 0xFF,
};
Source: le_net_cfg.c
帧结构解读:0x4A 0x4C("JL")是杰理私有协议魔数头;0x00 0x0e 为长度字段(0x0e = 14,即后续命令+载荷长度);0x10 0x02 是命令字(配网结果通知);载荷为 JSON 文本 {"status":0},status:0 表示配网成功;0x7C 0xC3 0xFF 是帧尾校验魔数。这种"魔数头 + 长度 + 命令 + JSON 载荷 + 帧尾"的封装便于手机端协议栈快速解帧,同时让载荷保持人类可读。
回调接口与互斥
配网 Demo 向上层暴露三个回调槽位,并配一把互斥锁保护并发访问:
static u8 ble_work_state = 0;
static u8 adv_ctrl_en;
static OS_MUTEX mutex;
static void (*app_recieve_callback)(void *priv, void *buf, u16 len) = NULL;
static void (*app_ble_state_callback)(void *priv, ble_state_e state) = NULL;
static void (*ble_resume_send_wakeup)(void) = NULL;
static u32 channel_priv;
static int app_send_user_data_check(u16 len);
static int app_send_user_data_do(void *priv, u8 *data, u16 len);
static int app_send_user_data(u16 handle, const u8 *data, u16 len, u8 handle_type);
Source: le_net_cfg.c
app_recieve_callback:收到手机下发的数据(如配网 JSON)时回调,业务侧在此解析并执行配网动作。app_ble_state_callback:连接/断开等 BLE 状态变化通知,ble_state_e定义状态枚举。ble_resume_send_wakeup:发送缓冲从满恢复为空时唤醒阻塞发送方(与下方流控配合)。app_send_user_data_check→app_send_user_data_do→app_send_user_data构成三级发送路径:先查缓冲余量、再执行发送、最终按句柄类型写 ATT 通道。OS_MUTEX mutex:保护共享的发送缓冲与状态变量,防止协议栈线程与业务线程并发写造成数据错乱。
配网是典型的"事件驱动 + 回调"模型:BLE 协议栈事件(连接、收到写请求)在栈线程触发,业务逻辑(WiFi 连接)在业务上下文执行,两者通过回调槽位解耦。这也是 BTstack 类协议栈的标准集成方式。
透明数据传输实现详解(le_trans_data.c)
大 MTU 与测试开关
透传 Demo 的核心诉求是吞吐,因此把 ATT 本地 MTU 提到 200 字节(远超经典 20 字节限制),并预留多个测试开关:
#define TEST_RECEIVE_DATA_RATE 0 /*测试记录接收数据速度*/
#define TEST_TRANS_TIMER_MS 500
static u32 trans_recieve_test_count;
#define TEST_SEND_DATA_RATE 0 //测试上行发送数据
#define TEST_SEND_HANDLE_VAL ATT_CHARACTERISTIC_ae02_01_VALUE_HANDLE
#define EXT_ADV_MODE_EN 0
#define TEST_AUDIO_DATA_UPLOAD 0 //测试文件上传
//ATT发送的包长, note: 20 <=need >= MTU
#define ATT_LOCAL_MTU_SIZE (200) //
//ATT缓存的buffer大小, note: need >= 20,可修改
#define ATT_SEND_CBUF_SIZE (512) //
//共配置的RAM
#define ATT_RAM_BUFSIZE (ATT_CTRL_BLOCK_SIZE + ATT_LOCAL_MTU_SIZE + ATT_SEND_CBUF_SIZE)
static u8 att_ram_buffer[ATT_RAM_BUFSIZE] __attribute__((aligned(4)));
Source: le_trans_data.c
ATT_LOCAL_MTU_SIZE = 200:本地 MTU 越大,单包承载数据越多,吞吐越高;注释20 <= need >= MTU强调不能低于 20 字节默认 MTU。TEST_RECEIVE_DATA_RATE/TEST_SEND_DATA_RATE/TEST_AUDIO_DATA_UPLOAD三个开关分别用于下行速率统计、上行速率统计和文件上传压力测试,量产固件置 0 关闭以省资源。EXT_ADV_MODE_EN控制是否使用扩展广播(LE Extended Advertising),关闭时使用传统广播以兼容老手机。
ATT 流控机制
透传场景最怕"发送方写爆接收缓冲",源码注释明确给出了流控用法:
/*
打开流控使能后,确定使能接口 att_server_flow_enable 被调用
然后使用过程 通过接口 att_server_flow_hold 来控制流控开关
注意:流控只能控制对方使用带响应READ/WRITE等命令方式
例如:ATT_WRITE_REQUEST = 0x12
*/
Source: le_trans_data.c
要点:先调用 att_server_flow_enable 使能流控,之后用 att_server_flow_hold 暂停/恢复对端带响应操作(如 ATT_WRITE_REQUEST = 0x12)。流控只对带响应的 ATT 命令生效——因为这类命令对端会等待应答,设备可以借"不回复/延迟回复"天然限速;对无响应的 Write Command(0x52)则无法阻止对端持续灌包,只能靠本地缓冲吸收,缓冲溢出即丢包,这也是 ATT_SEND_CBUF_SIZE = 512 偏大的原因。理解这一点对设计可靠透传协议至关重要:应用层协议必须自带重传/确认,不能依赖链路层兜底。
其他 BLE 角色与 Demo
apps/common/ble/ 目录还包含以下同级模块(均遵循上述统一框架):
| 文件 | 角色 | 典型应用 |
|---|---|---|
le_hogp.c | HID over GATT Profile(GATT Server) | BLE 键盘、鼠标、遥控器 |
le_mambo.c | 杰理穿戴私有协议 | 手表/手环与手机 App 数据同步 |
le_net_central.c | Central(主机)配网 | 设备作为主机主动扫描并连接 AP 配网 |
le_net_cfg_dui.c / le_net_cfg_tencent.c / le_net_cfg_qyai.c / le_net_cfg_turing.c | 各家云端配网变体 | DUI / 腾讯 / 启英 / 图灵语音平台一键配网 |
le_24g_deal.c | 2.4G 共存调度 | BLE 与经典蓝牙(BR/EDR)同时工作 |
mesh/examples/ | BLE Mesh 节点示例 | 智能家居(灯、插座、风扇、开关、provisioner) |
其中 le_hogp.c 复用同样的 GATT Server 框架,只是把特征值替换为 HID 报告特征并实现报告发送;le_net_cfg_*.c 系列是 le_net_cfg.c 的协议变体——配网流程完全一致,仅 JSON 载荷格式与云端接入命令不同,验证了"模板复用 + 协议替换"的扩展模式。Mesh 示例则独立成 mesh/examples/ 子目录,包含 AliGenie 灯/插座/风扇、Tuya 灯、generic onoff client/server、light lightness server、onoff provision、provisioner 与 vendor client,覆盖了 Mesh 节点端(provisionee)与配网端(provisioner)两端角色。
另外,SDK 还提供独立的 GATT Client 示例:apps/common/example/bluetooth/ble/bt_gatt_client/ble.c,用于设备主动连接手机或传感器等外围设备,与上面以 Server 为主的 Demo 互补。
Core Flow
配网主流程(GATT Server 视角)
sequenceDiagram
participant Phone as 手机 App (GATT Client)
participant BleS as le_net_cfg (GATT Server)
participant Wifi as WiFi 系统
Phone->>BleS: 扫描广播 (ADV_INTERVAL_MIN=160)
BleS-->>Phone: 广播/扫描应答 (设备名)
Phone->>BleS: 发起连接
BleS->>Phone: 连接建立
Note over Phone,BleS: 连接参数表逐档协商 {16,24,10,600} → {12,28,10,600} → {8,20,10,600}
Phone->>BleS: GATT 发现 ae82 服务/特征
Phone->>BleS: Write 配网 JSON (SSID/密码)
BleS->>BleS: app_recieve_callback 解析载荷
BleS->>Wifi: 下发配网指令
Wifi-->>BleS: 配网结果 (status)
BleS-->>Phone: 回复 rsp_cmd0 {"status":0} (JL 私有帧)
Phone->>BleS: 断开/保持连接
透传数据发送路径与流控
flowchart TD
App["业务层调用 app_send_user_data"] --> Check["app_send_user_data_check<br/>检查缓冲余量"]
Check -->|"缓冲满"| Wake["等待 ble_resume_send_wakeup<br/>唤醒(发送完成中断)"]
Wake --> Check
Check -->|"有余量"| Do["app_send_user_data_do<br/>写 ATT 通道"]
Do --> Flow{"流控使能?"}
Flow -->|"是"| Hold["att_server_flow_hold<br/>暂停对端带响应读写"]
Hold --> Done["数据到达手机端"]
Flow -->|"否"| Done
Done --> Notify["att_server_flow_enable 恢复<br/>下一条数据继续"]
流程说明:发送侧先查缓冲(app_send_user_data_check),满则挂起等待 ble_resume_send_wakeup 唤醒;写入 ATT 通道后若开了流控,用 att_server_flow_hold 限制对端灌入速率,防止 ATT_SEND_CBUF_SIZE 缓冲溢出。整个路径在协议栈线程与业务线程之间通过互斥锁与回调完成同步,是理解 BLE 吞吐上限与丢包行为的关键路径。
Usage Examples
以下示例均直接取自 SDK 源码,展示如何在自己的产品中复用这套 BLE 框架。
示例 1:选择 Demo 并配置 ATT 缓冲(新建配网类产品的最小模板)
#if (TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_NET_CFG)
//------
#define ATT_LOCAL_PAYLOAD_SIZE (128) //note: need >= 20
#define ATT_SEND_CBUF_SIZE (512) //note: need >= 20,缓存大小,可修改
#define ATT_RAM_BUFSIZE (ATT_CTRL_BLOCK_SIZE + ATT_LOCAL_PAYLOAD_SIZE + ATT_SEND_CBUF_SIZE) //note:
static u8 att_ram_buffer[ATT_RAM_BUFSIZE] __attribute__((aligned(4)));
//---------------
Source: le_net_cfg.c
用法说明:把工程配置 TCFG_BLE_DEMO_SELECT 设为 DEF_BLE_DEMO_NET_CFG 即启用该模块;ATT_LOCAL_PAYLOAD_SIZE 按业务单包最大载荷调整(下限 20 字节),ATT_SEND_CBUF_SIZE 决定发送缓冲深度,两者共同决定 att_ram_buffer 占用的静态 RAM。
示例 2:配置连接参数协商表(控制连接间隔与功耗)
//连接参数设置
static const uint8_t connection_update_enable = 1; ///0--disable, 1--enable
static uint8_t connection_update_cnt = 0; //
static const struct conn_update_param_t connection_param_table[] = {
{16, 24, 10, 600},//11
{12, 28, 10, 600},//3.7
{8, 20, 10, 600},
};
#define CONN_PARAM_TABLE_CNT (sizeof(connection_param_table)/sizeof(struct conn_update_param_t))
Source: le_net_cfg.c
用法说明:每个元组按 {连接间隔, 从机延迟, 监督超时, 未知/保留字段} 排列(字段语义以 struct conn_update_param_t 定义为准)。首档间隔最小(约 20ms 实际连接事件),吞吐最高但耗电;后续档位逐级放宽以兼容对端设备的能力。connection_update_enable = 0 可完全关闭连接参数更新。
示例 3:透传 Demo 的测试开关(用于吞吐评估与压力测试)
#define TEST_RECEIVE_DATA_RATE 0 /*测试记录接收数据速度*/
#define TEST_TRANS_TIMER_MS 500
static u32 trans_recieve_test_count;
#define TEST_SEND_DATA_RATE 0 //测试上行发送数据
#define TEST_SEND_HANDLE_VAL ATT_CHARACTERISTIC_ae02_01_VALUE_HANDLE
#define EXT_ADV_MODE_EN 0
#define TEST_AUDIO_DATA_UPLOAD 0 //测试文件上传
Source: le_trans_data.c
用法说明:开发期把 TEST_RECEIVE_DATA_RATE / TEST_SEND_DATA_RATE 置 1,配合 TEST_TRANS_TIMER_MS = 500 周期打印速率统计;TEST_AUDIO_DATA_UPLOAD = 1 可模拟音频文件上传压力。量产固件必须全部置 0,避免定时器与统计代码占用 CPU 和 RAM。TEST_SEND_HANDLE_VAL 指定上行发送使用的特征值句柄(对应自定义 GATT 服务的 ae02_01 特征)。
Configuration Options
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_BLE_DEMO_SELECT | 宏 | 由工程定义 | 选择编译哪个 BLE Demo(如 DEF_BLE_DEMO_NET_CFG、DEF_BLE_DEMO_TRANS_DATA),一次固件只编译一个 |
ATT_LOCAL_PAYLOAD_SIZE | 宏 | 128(配网)/ ATT_LOCAL_MTU_SIZE 200(透传) | GATT Server 本地 ATT 负载长度,须 ≥ 20 |
ATT_SEND_CBUF_SIZE | 宏 | 512 | 发送缓冲大小,决定上行数据突发能力 |
ATT_RAM_BUFSIZE | 宏 | 由上式求和 | 静态 RAM 总量(ATT_CTRL_BLOCK_SIZE + 负载 + 缓冲),小于 64 触发 #error |
ADV_INTERVAL_MIN | 宏 | 160(≈100ms) | 广播间隔下限,越小越易被发现、越耗电 |
connection_update_enable | 静态变量 | 1 | 是否允许更新连接参数(0 关闭) |
connection_param_table | 静态表 | {16,24,10,600} 等三档 | 连接参数协商降级序列 |
gap_device_name | 字符串 | "wl80_ble_test" | 广播设备名,可被 bt_get_local_name() 覆盖 |
EXT_ADV_MODE_EN | 宏 | 0 | 是否启用扩展广播(透传 Demo) |
TEST_RECEIVE_DATA_RATE | 宏 | 0 | 下行速率统计测试开关 |
TEST_SEND_DATA_RATE | 宏 | 0 | 上行速率统计测试开关 |
TEST_AUDIO_DATA_UPLOAD | 宏 | 0 | 文件/音频上传压力测试开关 |
TEST_TRANS_TIMER_MS | 宏 | 500 | 速率统计定时周期(ms) |
API Reference
以下接口在 le_net_cfg.c 中声明,是 BLE 业务层与协议栈之间的核心发送接口(static 局部声明,具体实现在文件后续部分):
app_send_user_data(u16 handle, const u8 *data, u16 len, u8 handle_type)
发送数据到对端 GATT Client。
参数:
handle(u16):目标特征值句柄(如ATT_CHARACTERISTIC_ae82_01_VALUE_HANDLE)data(const u8*):待发送数据指针len(u16):数据长度handle_type(u8):句柄类型(区分 Value/CCCD 等)
返回: int,非 0 表示发送结果状态。
app_send_user_data_check(u16 len)
参数: len (u16) — 欲发送的数据长度。
返回: int — 缓冲是否容纳得下本次发送(满则需等待唤醒)。
app_send_user_data_do(void *priv, u8 *data, u16 len)
参数: priv (void*) 通道私有参数、data (u8*) 数据、len (u16) 长度。
返回: int 发送结果。
调用约定:业务侧统一走 app_send_user_data →(内部先 app_send_user_data_check 再 app_send_user_data_do),发送被阻塞时由 ble_resume_send_wakeup 唤醒重试。
回调注册槽位(static 函数指针)
app_recieve_callback(void *priv, void *buf, u16 len):接收对端数据(如配网 JSON),业务在此解析。app_ble_state_callback(void *priv, ble_state_e state):连接状态变化通知。ble_resume_send_wakeup(void):发送缓冲恢复空间时的唤醒钩子。
ATT 流控接口(透传 Demo,由协议栈提供)
att_server_flow_enable:使能流控。att_server_flow_hold:暂停/恢复对端带响应读写(ATT_WRITE_REQUEST = 0x12),仅对带响应命令有效。
Failure Modes、边界情况与并发
缓冲溢出与编译期防护
ATT_RAM_BUFSIZE 过小时编译直接失败(#error "adv_data & rsp_data buffer error"),从源头杜绝广播缓冲越界。运行期溢出则由发送路径防护:app_send_user_data_check 在写入前检查余量,满时挂起等待 ble_resume_send_wakeup。若对端使用无响应 Write Command(0x52)灌包,流控无法生效,ATT_SEND_CBUF_SIZE 是唯一防线——超过即丢包,因此上层协议必须自带确认/重传,不能假设链路可靠。
连接参数协商失败
connection_param_table 提供三档降级序列,但若对端(如某些老旧手机或兼容性差的第三方设备)拒绝全部档位,连接参数更新将以 connection_update_cnt 计数退出,设备保持默认参数工作。此时表现为吞吐偏低或延迟偏高,可通过调大 connection_update_enable 前的等待时间或增加表项缓解。
并发访问
OS_MUTEX mutex 保护共享的 att_ram_buffer、ble_work_state、adv_ctrl_en 等状态。协议栈线程(接收/事件回调)与业务线程(发送/配网逻辑)可能同时触碰这些变量,任何新增的共享状态都应纳入同一把锁,否则会出现广播数据与发送缓冲被撕裂的偶发问题。
2.4G 共存
BLE 与经典蓝牙(BR/EDR)共用 2.4GHz 射频,同时工作时存在收发时隙竞争。le_24g_deal.c 负责两者调度;在未正确处理共存的产品上,典型故障是 BLE 连接频繁掉线或音频卡顿。若产品同时使用 BLE 与经典蓝牙,必须包含该共存模块并验证时隙分配。
Performance 与运维注意事项
- 吞吐上限:受
ATT_LOCAL_MTU_SIZE(配网 128 / 透传 200)、连接间隔(首档约 20ms)与ATT_SEND_CBUF_SIZE(512)共同约束。透传 Demo 的TEST_*_RATE开关可直接测量实际吞吐,TEST_AUDIO_DATA_UPLOAD用于长时压力验证。 - 功耗:广播间隔
ADV_INTERVAL_MIN = 160属于偏快配置(便于被发现),量产省电场景可适当增大;连接参数表首档间隔较小,可把首档调大换取低功耗。 - 静态内存:
att_ram_buffer为静态分配,每个 Demo 独占一块(配网约 128+512+控制块,透传约 200+512+控制块),RAM 紧张的工程应裁剪ATT_SEND_CBUF_SIZE。 - 调试手段:两个 Demo 顶部都有
log_info/log_info_hexdump(映射到printf/printf_buf)以及被注释的LOG_TAG_CONST、LOG_TAG、LOG_ERROR_ENABLE等调试宏,打开后可用debug.h分级日志排查收发问题。
Extension Points
- 新增业务 Demo:复制
le_net_cfg.c模板,替换 GATT 特征值句柄(如ATT_CHARACTERISTIC_ae82_01_VALUE_HANDLE改为自定义 UUID 特征),在app_recieve_callback中实现业务解析,并在TCFG_BLE_DEMO_SELECT的取值枚举中新增DEF_BLE_DEMO_XXX。 - 自定义应答帧:参照
rsp_cmd0的0x4A 0x4C + 长度 + 命令 + JSON + 0x7C 0xC3 0xFF帧格式,扩展更多命令字与 JSON 字段(如配网失败原因、设备能力查询)。 - 云端配网变体:
le_net_cfg_dui.c/le_net_cfg_tencent.c等展示了在通用配网流程上替换载荷协议的做法——保持 GATT 服务与回调框架不变,仅改 JSON 结构与云对接逻辑。 - HID / 穿戴 / Central:
le_hogp.c、le_mambo.c、le_net_central.c分别示范了报告特征发送、私有协议封装与主机角色 API,可作为对应品类的起点。 - Mesh 节点:
mesh/examples/中的 vendor client/server 提供自定义模型模板,可在其上定义厂商专属 opcode 实现私有智能家居协议。
Related Links
- 经典蓝牙(BR/EDR):A2DP/HFP 等经典蓝牙协议栈与音频应用(与 BLE 共用 2.4G 射频,涉及
le_24g_deal.c共存逻辑)。 - BLE Mesh:Mesh 各模型(AliGenie/Tuya、generic onoff、light lightness、provisioner)的详细实现。
- 配网相关:
le_net_cfg.c、le_net_cfg_dui.c、le_net_cfg_tencent.c、le_net_cfg_qyai.c、le_net_cfg_turing.c、le_net_central.c - 透传相关:le_trans_data.c
- 角色示例:le_hogp.c、le_mambo.c、le_24g_deal.c
- GATT Client 示例:apps/common/example/bluetooth/ble/bt_gatt_client/ble.c