ESP IoT Solution BLE L2CAP CoC Central 示例详解:NimBLE 面向连接通道的主动扫描与数据传输
2026/9/19 14:20:16 网站建设 项目流程

ESP IoT Solution BLE L2CAP CoC Central 示例详解:NimBLE 面向连接通道的主动扫描与数据传输

【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution

导读

本篇文章围绕 esp-iot-solution 仓库中的 BLE L2CAP CoC Central 示例(examples/bluetooth/ble_l2cap_coc/l2cap_coc_central)展开,讲解如何基于ble_conn_mgr连接管理组件实现一个完整的 BLE Central 角色程序:执行被动扫描并打印扫描结果、按配置的设备名称自动匹配并连接外围设备、建立 L2CAP Connection-Oriented Channel(CoC)通道,并在通道上周期性发送数据。读完本文,你将掌握 L2CAP CoC 在 ESP32 系列芯片上的核心概念、工程配置方法、源码调用链以及它与同目录 Peripheral 示例的配对调试方式。

一、背景:为什么需要 L2CAP CoC

传统 BLE 应用主要依靠 GATT(Generic Attribute Profile)传输数据,每次读写都伴随着 attribute 操作开销,且面向大数据块传输时并不高效。L2CAP Connection-Oriented Channel(CoC)是 Bluetooth 4.2 引入的 L2CAP 增强特性,它允许在 LE 链路上建立面向连接的逻辑通道,以"流式"方式传输 SDU(Service Data Unit),吞吐量和数据封装开销都优于 GATT 的 notification/indication 方案,非常适合音频流、批量数据下发、自定义协议承载等场景。

在 ESP-IDF 的 NimBLE 协议栈中,L2CAP CoC 由CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM等配置控制。本示例将 CoC 的创建、连接、收发统一封装进 ble_conn_mgr 组件,上层应用只需关注业务回调,无需直接面对 NimBLE 的底层 API。

二、示例功能概览

根据 l2cap_coc_central/README.md,该 Central 示例的核心行为为:

  1. 被动扫描:执行 BLE passive scan,打印每个扫描结果的地址、类型、RSSI 与广播数据;
  2. 按名称匹配:解析广播数据中的设备名称,与配置项Peer Device Name(默认BLE_L2CAP_COC_PERIPH)比对,命中后停止扫描并发起连接;
  3. 建立 CoC 通道:连接建立并完成服务发现后,以配置的 PSM(默认0x00EF)向对端发起 L2CAP CoC 连接;
  4. 周期性发送数据:通道建立成功后,独立发送任务每 2 秒向对端发送一次满长度 SDU 数据;
  5. 接收与重注册:收到对端数据后打印接收日志,并通过recv_ready重新提供接收缓冲,维持双向通信。

三、支持的芯片与硬件要求

README 的目标芯片支持矩阵如下:

Supported TargetsESP32ESP32-C3ESP32-C2ESP32-S3ESP32-H2

硬件准备:

  • 一块搭载 ESP32 / ESP32-C3 / ESP32-C2 / ESP32-S3 / ESP32-H2 SoC 的开发板;
  • 一根用于供电与烧录的 USB 线。

由于本示例是 Central 角色,实际联调时还需要另一块开发板运行同目录下的 l2cap_coc_peripheral 示例充当外设。

四、工程结构与关键文件

示例位于 examples/bluetooth/ble_l2cap_coc/l2cap_coc_central,其目录结构如下:

l2cap_coc_central/ ├── main/ │ ├── CMakeLists.txt # 组件构建描述,依赖 bt 与 nvs_flash │ ├── Kconfig.projbuild # Example Configuration 菜单(设备名、PSM 等) │ ├── app_main.c # 示例全部业务逻辑 │ └── idf_component.yml # 组件依赖声明 ├── CMakeLists.txt ├── README.md └── sdkconfig.defaults # 默认 sdkconfig 片段(NimBLE/CoC 相关开关)

关键依赖关系(idf_component.yml):

dependencies: idf: ">=4.3" ble_conn_mgr: version: "~1.*" override_path: "../../../../../components/bluetooth/ble_conn_mgr"

示例直接复用仓库内的 ble_conn_mgr 组件(通过override_path指向本地路径),该组件同时支持 Bluedroid 与 NimBLE 双协议栈(src/esp_bluedroid.c、src/esp_nimble.c),本示例基于 NimBLE。

五、核心代码调用链解析

5.1 入口初始化流程

app_main.c 中的app_main依次完成:

esp_ble_conn_config_t config = { .device_name = CONFIG_EXAMPLE_BLE_DEVICE_NAME, .broadcast_data = CONFIG_EXAMPLE_BLE_SUB_ADV, }; // 1. 初始化 NVS nvs_flash_init(); // 2. 创建默认事件循环并注册 BLE 连接管理事件处理器 esp_event_loop_create_default(); esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL); // 3. 初始化 BLE 连接管理器 esp_ble_conn_init(&config); // 4. 初始化 L2CAP CoC 内存池(关键!) esp_ble_conn_l2cap_coc_mem_init(); // 5. 注册扫描回调 esp_ble_conn_register_scan_callback(app_ble_conn_scan_cb, NULL); // 6. 启动连接管理器 esp_ble_conn_start();

其中esp_ble_conn_l2cap_coc_mem_init()是 CoC 功能可用的前提,它负责为 CoC 通道分配内存池;如果启动失败,示例会依次调用esp_ble_conn_l2cap_coc_mem_release()esp_ble_conn_deinit()回滚初始化并报错退出。对应 API 声明见 esp_ble_conn_mgr.h。

5.2 扫描回调:按名称匹配并触发连接

扫描回调app_ble_conn_scan_cb实现了"被动扫描 → 解析广播数据 → 匹配设备名 → 停止扫描并连接"的完整逻辑:

static bool app_ble_conn_scan_cb(const esp_ble_conn_scan_result_t *result, void *arg) { // 打印扫描结果:地址、地址类型、RSSI、广播数据长度 ESP_LOGI(TAG, "scan result: addr=" BLE_CONN_MGR_ADDR_STR " type=%u rssi=%d adv_len=%u", ...); ESP_LOG_BUFFER_HEX(TAG, result->adv_data, result->adv_data_len); // 优先解析 Complete Local Name,失败再尝试 Shortened Local Name if (esp_ble_conn_parse_adv_data(..., ESP_BLE_CONN_ADV_TYPE_NAME_COMPLETE, ...) != ESP_OK) { if (esp_ble_conn_parse_adv_data(..., ESP_BLE_CONN_ADV_TYPE_NAME_SHORT, ...) != ESP_OK) { return false; } } // 与 CONFIG_EXAMPLE_PEER_NAME 比对 if (strcmp(peer_name, CONFIG_EXAMPLE_PEER_NAME) == 0) { s_target_found = true; esp_ble_conn_scan_stop(); // 停止扫描 return true; // 返回 true 触发连接 } return false; }

关键点:根据 esp_ble_conn_register_scan_callback 的注释,当回调返回true时,连接管理器会自动对该对端发起连接;且同一时间只允许一个外连尝试,若已有连接尝试在进行中,后续扫描结果会被跳过。esp_ble_conn_parse_adv_data用于从原始广播数据中按 AD type 提取字段(如ESP_BLE_CONN_ADV_TYPE_NAME_COMPLETE表示完整名称)。

5.3 连接事件驱动:服务发现后建立 CoC 通道

通用事件处理器app_ble_conn_event_handler监听BLE_CONN_MGR_EVENTS事件:

  • ESP_BLE_CONN_EVENT_CONNECTED:通过esp_ble_conn_get_conn_handle()esp_ble_conn_get_peer_addr()获取连接句柄和对端地址并打印;
  • ESP_BLE_CONN_EVENT_DISC_COMPLETE服务发现完成后发起 CoC 连接,这是关键时序点——必须等 GATT 服务发现结束后再建立 CoC 通道:
esp_err_t rc = esp_ble_conn_l2cap_coc_connect(s_conn_handle, L2CAP_COC_PSM, L2CAP_COC_MTU, L2CAP_COC_MTU, app_ble_conn_l2cap_coc_event_handler, NULL);

对应 API 签名(esp_ble_conn_mgr.h):

esp_err_t esp_ble_conn_l2cap_coc_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu, uint16_t sdu_size, esp_ble_conn_l2cap_coc_event_cb_t cb, void *cb_arg);

参数含义:conn_handle为已建立的 LE 链路句柄;psm为对端监听的 Protocol/Service Multiplexer(LE 下有效范围 0x0001–0x00FF);mtu为本端使用的最大 SDU 大小(23–512 字节);sdu_size首个 SDU 的接收缓冲区大小(23–65535 字节),API 注释明确指出应用通常应将其设置为期望的对端 CoC MTU。示例中将 MTU 与 SDU 大小都设置为CONFIG_BLE_CONN_MGR_L2CAP_COC_MTU

  • ESP_BLE_CONN_EVENT_DISCONNECTED:清空连接句柄并复位s_target_found,使程序可再次进入扫描匹配流程。

5.4 CoC 通道事件处理

L2CAP CoC 专用回调app_ble_conn_l2cap_coc_event_handler处理四类事件:

事件类型含义示例处理
ESP_BLE_CONN_L2CAP_COC_EVENT_CONNECTEDCoC 通道建立打印通道信息,创建发送任务
ESP_BLE_CONN_L2CAP_COC_EVENT_DISCONNECTED通道断开清空通道句柄,删除发送任务
ESP_BLE_CONN_L2CAP_COC_EVENT_DATA_RECEIVED收到对端 SDU打印接收日志,重新注册接收缓冲
ESP_BLE_CONN_L2CAP_COC_EVENT_TX_UNSTALLED发送阻塞解除打印状态,供发送逻辑重试

CONNECTED分支中,示例调用esp_ble_conn_l2cap_coc_get_chan_info()获取通道详情并打印:

esp_ble_conn_l2cap_coc_chan_info_t chan_info = {0}; esp_ble_conn_l2cap_coc_get_chan_info(event->connect.chan, &chan_info); // 打印 psm / scid / dcid / our_mps / our_mtu / peer_mps / peer_mtu s_peer_sdu_size = chan_info.peer_coc_mtu; // 以对端 CoC MTU 作为发送长度

这里s_peer_sdu_size = chan_info.peer_coc_mtu非常关键:发送方以对端的 CoC MTU 作为 SDU 长度,保证单帧即可完整传输而不被分片

DATA_RECEIVED分支中有一段重要的使用约束注释(原文):

event->receive.sdu.data buffer is managed internally and will be freed immediately after this callback returns. Do not save the pointer for later use. If asynchronous processing is needed, copy the data before returning.

即接收数据缓冲区由组件内部管理,回调返回后立即释放,不能保存指针后续使用,如需异步处理必须先拷贝。随后示例调用esp_ble_conn_l2cap_coc_recv_ready(event->receive.chan, L2CAP_COC_MTU)为下一个 SDU 重新提供接收缓冲——这是 NimBLE CoC 流控下的标准做法,不调用则无法继续接收。

5.5 周期发送任务

通道建立成功后创建l2cap_coc_send_task(栈 4096 字节、优先级 10),每 2 秒发送一次:

static void app_ble_conn_l2cap_coc_test_send(esp_ble_conn_l2cap_coc_chan_t *chan) { uint8_t *payload = (uint8_t *)malloc(s_peer_sdu_size); for (uint16_t i = 0; i < s_peer_sdu_size; i++) { payload[i] = (uint8_t)(i & 0xFF); // 填充 0x00,0x01,... 便于对端校验 } esp_ble_conn_l2cap_coc_sdu_t sdu = { .data = payload, .len = s_peer_sdu_size, }; esp_err_t rc = esp_ble_conn_l2cap_coc_send(*chan, &sdu); if (rc != ESP_OK) { ESP_LOGW(TAG, "L2CAP CoC send failed: %s", esp_err_to_name(rc)); } free(payload); }

esp_ble_conn_l2cap_coc_send()的流控语义(esp_ble_conn_mgr.h):

  • 数据缓冲区不归协议栈所有,函数返回后即可释放(示例正是这样做的);
  • 若通道处于 stalled 状态(缓冲区满),返回ESP_ERR_NOT_FINISHED,调用方应等待ESP_BLE_CONN_L2CAP_COC_EVENT_TX_UNSTALLED事件后重试;
  • 若内存池耗尽,返回ESP_ERR_NO_MEM,应稍后重试。

六、工程配置详解

6.1 设置目标芯片

编译前必须先设置正确的芯片目标:

idf.py set-target <chip_name>

其中<chip_name>可为esp32esp32c3esp32c2esp32s3esp32h2之一(需与开发板对应)。

6.2 menuconfig 配置

运行idf.py menuconfig打开配置菜单。

Example Configuration 菜单(定义于 Kconfig.projbuild):

配置项默认值说明
Device nameBLE_L2CAP_COC_CENT本设备广播/被识别使用的设备名
Subsequent advertisement dataSUB_ADV后续广播数据内容
Peer Device NameBLE_L2CAP_COC_PERIPH要连接的外设名称,须与外设端广告名称一致
L2CAP CoC PSM0x00EF面向连接通道的协议/服务复用器值

PSM 的有效范围为0x0001–0x00FF,Kconfig 中同时给出了分段说明:

  • 0x0001 – 0x007F:蓝牙 SIG 定义的固定服务;
  • 0x0080 – 0x00FF:动态自定义服务(本示例的 0x00EF 落在此段)。

要求 Central 与 Peripheral 两端必须使用相同的 PSM 值,否则无法建立通道。

Component Config 菜单(README 明确要求的两项):

  1. Component config → Bluetooth → NimBLE Options → L2CAP → Maximum number of connection oriented channels:设为大于 0的值(本示例sdkconfig.defaults中为 1),否则 NimBLE 协议栈根本不支持 CoC;
  2. Component config → Bluetooth → NimBLE Options → Memory Settings → MSYS_1 Block Size:设为512,以支持最大 SDU 传输。

6.3 sdkconfig.defaults 中的默认开关

sdkconfig.defaults 在编译前就固定了关键选项:

CONFIG_BT_ENABLED=y # 启用蓝牙 CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y # 控制器仅 BLE 模式 CONFIG_BT_NIMBLE_ENABLED=y # 使用 NimBLE 主机协议栈 CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1 # 最大面向连接通道数 > 0 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30 # MSYS_1 内存块数量 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=512 # MSYS_1 块大小 512 字节 CONFIG_BLE_CONN_MGR_ROLE_CENTRAL=y # 连接管理器角色:Central

其中CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM与 README 中"Maximum number of connection oriented channels 大于 0"的菜单要求一一对应;CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=512对应"MSYS_1 Block Size 设为 512"。CONFIG_BLE_CONN_MGR_ROLE_CENTRAL对应 ble_conn_mgr/Kconfig 中的角色选择(Peripheral / Central / Both,默认 Peripheral)。

6.4 连接管理器的相关配置

ble_conn_mgr/Kconfig 中还定义了与 CoC 相关的系统级参数:

  • BLE_CONN_MGR_L2CAP_COC_MTUL2CAP CoC SDU MTU,范围 23–512,默认 512,用于 L2CAP CoC 内存池的默认 SDU 大小——正是示例中L2CAP_COC_MTU宏的来源;
  • BLE_CONN_MGR_MAX_CONNECTIONS:连接管理器管理的最大 BLE 链路数(默认 2),不能超过底层协议栈的配置;
  • BLE_CONN_MGR_WAIT_DURATION:等待完成时长(10–31,默认 31),设置足够大以避免重传。

七、编译、烧录与运行

l2cap_coc_central目录下执行:

idf.py -p PORT flash monitor

该命令会依次完成构建、烧录并打开串口监视器(退出监视器按Ctrl-])。

建议与 l2cap_coc_peripheral 示例配对使用:外设端启动广播(广告名称默认BLE_L2CAP_COC_PERIPH),Central 端运行后即可在串口日志中观察到:

  1. 扫描结果逐条打印(地址、RSSI、广播数据十六进制);
  2. 命中BLE_L2CAP_COC_PERIPH后打印Matched peer name: ...并停止扫描;
  3. GATT 服务发现完成后打印Service discovery complete, connect L2CAP CoC
  4. CoC 通道建立后打印通道信息(psm/scid/dcid/our_mps/our_mtu/peer_mps/peer_mtu);
  5. 每 2 秒出现一条[--TX--]L2CAP CoC data sent (N bytes),同时外设端可见对应[--RX--]接收日志。

八、与 Peripheral 示例的对称设计

同目录的 l2cap_coc_peripheral 扮演"仅广播的广告者"角色:启动广播后等待 Central 连接,连接建立后通过esp_ble_conn_l2cap_coc_create_server()创建 CoC 服务端(PSM 默认同样为0x00EF,范围 0x0001–0x00FF,要求与 Central 一致),并处理通道事件。两端在配置上需要对齐:

  • PSM 必须相同
  • 外设广告名称必须等于 Central 的Peer Device Name
  • 均需开启Maximum number of connection oriented channels > 0并将MSYS_1 Block Size设为 512。

esp_ble_conn_l2cap_coc_create_server()的接口说明(esp_ble_conn_mgr.h)指出:服务端回调上下文对该 PSM 是持久化的,会在多个入站连接间复用,直到 L2CAP CoC 内存池被释放;而客户端回调上下文在连接失败(CONNECTED事件携带非零 status)或DISCONNECTED后会被自动释放。

九、常见问题与注意事项

  1. 发送失败ESP_ERR_NOT_FINISHED:通道处于 stalled 状态,说明发送缓冲已满或对端未及时recv_ready。正确做法是等待ESP_BLE_CONN_L2CAP_COC_EVENT_TX_UNSTALLED事件后重试,而不是盲目重发。
  2. 发送失败ESP_ERR_NO_MEM:CoC 内存池耗尽,可稍后重试,或检查是否有未完成的操作占用缓冲。
  3. 接收回调中的数据指针不可保存DATA_RECEIVED事件的 SDU 缓冲在回调返回后被释放,异步处理必须拷贝。
  4. 每次接收后必须调用esp_ble_conn_l2cap_coc_recv_ready():否则无法继续接收后续 SDU。
  5. PSM 与 MTU 范围:LE 下 PSM 限定 0x0001–0x00FF,SDU MTU 限定 23–512 字节(见 esp_ble_conn_mgr.h 中各 API 的参数注释),越界会返回ESP_ERR_INVALID_ARG
  6. 扫描回调触发连接是单实例的:同一时间只允许一个外连尝试,连接完成或失败前后续匹配结果会被忽略(esp_ble_conn_mgr.h)。

结语

通过本文的讲解可以看到,ble_conn_mgr组件把 NimBLE 中较为繁琐的 L2CAP CoC 通道管理抽象为"初始化内存池 → 发起连接/创建服务 → 事件回调 → 发送/接收"四个步骤,Central 与 Peripheral 示例则为开发者提供了可直接复用的完整模板。基于 l2cap_coc_central 的代码骨架,你可以快速改造出支持自定义协议、批量数据传输乃至音频流传输的 BLE 应用。

【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询