ESP32 蓝牙 BLE 开发实战:广播、扫描与 GATT 服务构建
👁 2 阅读 · 2026-08-20 · ESP32 蓝牙
ESP32 作为物联网领域的明星芯片,其内置的蓝牙 BLE 功能为低功耗无线通信提供了强大支持。本文将深入剖析 ESP32 的 BLE 协议栈架构,详细讲解广播、扫描以及 GATT 服务开发的原理与实现。通过完整的代码示例,你将学会如何配置广播数据、发起扫描并建立 GATT 服务,为你的智能硬件项目打下坚实基础。
## 一、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 开发路上的有力参考。