## 一、ESP32 蓝牙 BLE 协议栈概览 ESP32 的 BLE 协议栈基于 Bluedroid 和 NimBLE 两种实现。Bluedroid 是官方默认方案,功能全面但资源占用较高;NimBLE 则更轻量,适合资源受限场景。本文以 Bluedroid 为例,通过 ESP-IDF 框架进行开发。 BLE 协议栈分层如下: - **物理层(PHY)**:2.4GHz 频段,支持 1Mbps/2Mbps 速率 - **链路层(LL)**:负责连接、广播、扫描等底层操作 - **主机控制接口(HCI)**:连接控制器与主机 - **逻辑链路控制与适配协议(L2CAP)**:数据封装与复用 - **属性协议(ATT)**:定义数据交互的规则 - **通用属性协议(GATT)**:基于 ATT,定义服务、特征等高级数据结构 - **通用访问协议(GAP)**:管理设备发现、连接和安全性 ## 二、广播(Advertising)开发 广播是 BLE 设备向外发送数据的过程,用于让周围设备发现自己。广播数据分为广播包(Advertising Packet)和扫描响应包(Scan Response),前者必须发送,后者可选。 ### 2.1 广播参数配置 主要参数包括: - 广播间隔(adv_interval):范围 20ms~10.24s,间隔越短功耗越高 - 广播类型:可连接非定向、可连接定向、不可连接、可扫描等 - 广播信道:默认使用 37、38、39 三个信道 ### 2.2 广播数据格式 广播数据由多个 AD Structure 组成,每个结构包含长度、类型和数据。常见类型有: - 0x01:标志位(LE 通用可发现模式等) - 0x02/0x03:16 位服务 UUID 列表 - 0xFF:制造商特定数据 ### 2.3 代码示例:配置广播 ```c #include "esp_bt.h" #include "esp_bt_main.h" #include "esp_gap_ble_api.h" static esp_ble_adv_params_t adv_params = { .adv_int_min = 0x20, // 40ms .adv_int_max = 0x40, // 80ms .adv_type = ADV_TYPE_IND, // 可连接非定向 .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .channel_map = ADV_CHNL_ALL, .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, }; static uint8_t adv_data[] = { 0x02, 0x01, 0x06, // 标志:LE 通用可发现模式 0x03, 0x03, 0xFF, 0x00, // 16位服务 UUID 0x00FF 0x05, 0x09, 'E', 'S', 'P', '3', '2' // 完整本地名称 "ESP32" }; void ble_adv_start(void) { esp_ble_gap_set_device_name("ESP32"); esp_ble_gap_config_adv_data(sizeof(adv_data), adv_data); esp_ble_gap_start_advertising(&adv_params); } ``` ## 三、扫描(Scanning)开发 扫描是发现周围 BLE 设备的过程。扫描分为主动扫描和被动扫描,主动扫描会发送扫描请求以获取扫描响应数据。 ### 3.1 扫描参数配置 - 扫描间隔(scan_interval):两次扫描之间的间隔 - 扫描窗口(scan_window):每次扫描持续的时间 - 扫描类型:主动(0x01)或被动(0x00) ### 3.2 扫描结果处理 扫描结果通过 GAP 回调函数返回,包括设备地址、RSSI、广播数据等。需要解析广播数据以获取设备名称、服务 UUID 等信息。 ### 3.3 代码示例:发起扫描 ```c static esp_ble_scan_params_t scan_params = { .scan_type = BLE_SCAN_TYPE_ACTIVE, // 主动扫描 .scan_interval = 0x50, // 50ms .scan_window = 0x30, // 30ms .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, }; static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: if (param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { uint8_t *adv_data = param->scan_rst.ble_adv; uint8_t adv_len = param->scan_rst.adv_data_len; // 解析广播数据,提取设备名称等 printf("Found device, RSSI: %d\n", param->scan_rst.rssi); } break; default: break; } } void ble_scan_start(void) { esp_ble_gap_set_scan_params(&scan_params); esp_ble_gap_start_scanning(10); // 扫描10秒 } ``` ## 四、GATT 服务开发 GATT 定义了数据交互的结构,包括服务(Service)、特征(Characteristic)和描述符(Descriptor)。服务是一组特征的集合,特征包含值和属性,描述符用于描述特征。 ### 4.1 GATT 服务架构 - **服务**:由 UUID 标识,如 0x180D 表示心率服务 - **特征**:包含值(Value)和属性(Properties),如读、写、通知等 - **描述符**:可选,用于配置特征,如 CCCD(客户端特征配置描述符) ### 4.2 创建 GATT 服务步骤 1. 注册 GATT 回调函数 2. 创建服务表(包括服务、特征、描述符) 3. 启动服务 4. 处理读写请求和通知发送 ### 4.3 完整代码示例:自定义 GATT 服务 ```c #include "esp_gatts_api.h" #define GATTS_TAG "GATTS" #define SERVICE_UUID 0x00FF #define CHAR_UUID 0xFF01 static uint8_t char_value = 0; static esp_gatt_srvc_id_t service_id = { .id = { .uuid = {.len = ESP_UUID_LEN_16, .uuid = {.uuid16 = SERVICE_UUID}}, .inst_id = 0, }, .is_primary = true, }; static esp_attr_value_t char_val = { .attr_max_len = 1, .attr_len = 1, .attr_value = &char_value, }; static esp_gatts_attr_db_t gatts_db[] = { // 服务声明 { .attr_control = {.auto_rsp = ESP_GATT_AUTO_RSP}, .att_desc = { .uuid_length = ESP_UUID_LEN_16, .uuid_p = (uint8_t *)&SERVICE_UUID, .perm = ESP_GATT_PERM_READ, .max_length = 0, .length = 0, .value = NULL, } }, // 特征声明 { .attr_control = {.auto_rsp = ESP_GATT_AUTO_RSP}, .att_desc = { .uuid_length = ESP_UUID_LEN_16, .uuid_p = (uint8_t *)&CHAR_UUID, .perm = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, .max_length = 1, .length = 1, .value = &char_value, } }, }; static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_REG_EVT: esp_ble_gatts_create_service(gatts_if, &service_id, 2); break; case ESP_GATTS_CREATE_EVT: esp_ble_gatts_start_service(param->create.service_handle); break; case ESP_GATTS_WRITE_EVT: char_value = param->write.value[0]; printf("Write value: %d\n", char_value); break; default: break; } } void gatts_init(void) { esp_ble_gatts_register_callback(gatts_event_handler); esp_ble_gatts_app_register(0); } ``` ## 五、注意事项 - **内存管理**:BLE 协议栈占用较多 RAM,在内存受限时考虑使用 NimBLE - **功耗优化**:合理设置广播间隔和扫描窗口,避免频繁唤醒 - **安全机制**:生产环境应启用配对和加密,防止数据泄露 - **错误处理**:检查每个 API 的返回值,处理超时和异常情况 - **多连接**:ESP32 支持最多 9 个 BLE 连接,但需注意资源分配 ## 六、总结 本文从 BLE 协议栈基础出发,详细介绍了 ESP32 的广播、扫描和 GATT 服务开发。通过代码示例,你可以快速上手并构建自己的 BLE 应用。实际项目中,还需结合具体需求调整参数和架构,并深入理解协议细节。希望这篇文章能成为你 BLE 开发路上的有力参考。