1. ESP32蓝牙通信基础解析
1.1 蓝牙协议栈架构与GATT详解
蓝牙低功耗(BLE)协议栈采用分层设计,ESP32作为从设备时主要工作在GATT(通用属性协议)层。GATT定义了服务(Service)、特征(Characteristic)和描述符(Descriptor)的层级关系:
- Profile:应用场景规范(如心率监测)
- Service:功能集合(如电池服务)
- Characteristic:数据载体(如电池电量值)
- Descriptor:特性说明(如通知使能标志)
在ESP-IDF中,每个GATT服务通过属性表(Attribute Table)实现。以示例代码中的gatts_table_creat_demo.c为例,其属性表包含:
c复制static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] = {
[IDX_SVC] = { /* 服务声明 */ },
[IDX_CHAR_A] = { /* 特征A声明 */ },
[IDX_CHAR_VAL_A] = { /* 特征A值 */ },
[IDX_CHAR_CFG_A] = { /* 客户端配置描述符 */ }
};
1.2 ESP32蓝牙工作流程
1.2.1 初始化阶段
c复制void app_main() {
// 1. 初始化NVS存储
esp_err_t ret = nvs_flash_init();
// 2. 释放经典蓝牙内存
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
// 3. 初始化BLE控制器
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
// 4. 初始化Bluedroid协议栈
esp_bluedroid_init();
esp_bluedroid_enable();
// 5. 注册回调函数
esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);
// 6. 注册GATT应用
esp_ble_gatts_app_register(ESP_APP_ID);
}
1.2.2 广播与连接
- 广播配置:通过
raw_adv_data设置设备名称、服务UUID等
c复制static uint8_t raw_adv_data[] = {
0x02, 0x01, 0x06, // Flags
0x02, 0x0A, 0xEB, // 发射功率
0x03, 0x03, 0xFF, 0x00, // 服务UUID
0x0F, 0x09, 'P','O','L','Y','P','L','A','Y','_','P','O','I','N','T' // 设备名
};
- 连接参数:建议值如下(单位1.25ms)
c复制static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 40ms
.adv_int_max = 0x40, // 80ms
.adv_type = ADV_TYPE_IND // 可连接的非定向广播
};
2. GATT服务实现详解
2.1 属性表构建
示例代码创建了包含6个特征的服务:
c复制static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_TEST_F = 0xFF06; // 通知特征
每个特征包含:
- 特征声明(属性类型、权限、UUID)
- 特征值(可读/写的数据)
- 描述符(如CCC用于通知使能)
2.2 关键事件处理
2.2.1 写操作处理
c复制case ESP_GATTS_WRITE_EVT:
if (!param->write.is_prep) {
// 处理即时写入
if (param->write.handle == heart_rate_handle_table[IDX_CHAR_CFG_F]) {
// 检查CCC描述符写入值
uint16_t ccc_value = *(uint16_t*)param->write.value;
notify_enabled = (ccc_value == ESP_GATT_CLIENT_CONFIG_NOTIFY);
}
} else {
// 处理准备写入
example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
}
break;
2.2.2 通知发送机制
当特征F的CCC描述符被设置为1时,ESP32可主动发送通知:
c复制void send_notification(uint8_t *data, size_t length) {
if (notify_enabled) {
esp_ble_gatts_send_indicate(
notify_gatts_if,
notify_conn_id,
heart_rate_handle_table[IDX_CHAR_VAL_F],
length,
data,
false // 不需要确认
);
}
}
3. Uniapp蓝牙交互实现
3.1 Uniapp端关键代码
3.1.1 初始化蓝牙
javascript复制// 初始化蓝牙模块
uni.openBluetoothAdapter({
success: (res) => {
this.startDiscovery();
},
fail: (err) => {
console.error('蓝牙初始化失败', err);
}
});
3.1.2 设备发现与连接
javascript复制// 开始搜索设备
startDiscovery() {
uni.startBluetoothDevicesDiscovery({
services: ['FF00'], // 目标服务UUID
success: (res) => {
uni.onBluetoothDeviceFound(this.deviceFound);
}
});
}
// 设备发现回调
deviceFound(devices) {
if (devices.devices[0].name === 'POLYPLAY_POINT') {
uni.createBLEConnection({
deviceId: devices.devices[0].deviceId,
success: (res) => {
this.getServices();
}
});
}
}
3.2 数据交互实现
3.2.1 特征订阅
javascript复制// 启用通知
enableNotify() {
uni.notifyBLECharacteristicValueChange({
deviceId: this.deviceId,
serviceId: 'FF00',
characteristicId: 'FF06',
state: true,
success: (res) => {
uni.onBLECharacteristicValueChange(this.handleNotify);
}
});
}
3.2.2 数据接收处理
javascript复制// 通知数据回调
handleNotify(res) {
const value = new Uint8Array(res.value);
console.log('收到数据:', Array.from(value));
// 数据解析逻辑...
}
4. 实战调试技巧
4.1 常见问题排查
4.1.1 连接不稳定
- 现象:频繁断开连接
- 解决方案:
- 调整连接参数:
c复制esp_ble_conn_update_params_t params = { .min_int = 16, // 20ms .max_int = 32, // 40ms .latency = 0, .timeout = 400 // 4s }; esp_ble_gap_update_conn_params(¶ms);- 检查天线匹配电路
4.1.2 数据包丢失
- 现象:通知数据不完整
- 解决方案:
- 增加MTU大小(默认23字节):
c复制esp_ble_gatt_set_local_mtu(500); // 设置本地MTU- 实现数据分包协议
4.2 性能优化建议
-
广播间隔:平衡功耗与连接速度
- 快速连接:
adv_int_min=0x20(40ms),adv_int_max=0x40(80ms) - 低功耗:
adv_int_min=0x400(1.28s),adv_int_max=0x800(2.56s)
- 快速连接:
-
数据吞吐量:
- 使用
ESP_GATT_CHAR_PROP_BIT_WRITE_NR实现无响应写入 - 批量数据采用准备写入+执行写入方式
- 使用
-
功耗控制:
c复制esp_ble_set_scan_rsp_data(false); // 关闭扫描响应 esp_bt_controller_disable(); // 空闲时关闭蓝牙
5. 进阶开发指导
5.1 安全增强
5.1.1 配对绑定
c复制esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(auth_req));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(iocap));
5.1.2 数据加密
c复制static void encrypt_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
if (event == ESP_GAP_BLE_SEC_REQ_EVT) {
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
}
}
5.2 多协议共存
5.2.1 蓝牙+WiFi共存
c复制esp_coex_preference_t coex_pref = ESP_COEX_PREFER_BALANCED;
esp_err_t ret = esp_coex_preference_set(coex_pref);
5.2.2 双模蓝牙配置
c复制esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
bt_cfg.mode = ESP_BT_MODE_BTDM; // 双模
esp_bt_controller_init(&bt_cfg);
在实际项目中,我曾遇到ESP32S3同时运行BLE和WiFi时吞吐量下降的问题。通过调整共存优先级和优化天线布局,最终实现了稳定的双模通信。关键发现是:
- 设置
ESP_COEX_PREFER_WIFI可提高WiFi吞吐 - BLE通信期间短暂降低WiFi速率可减少冲突
- 使用PCB天线时需确保50欧姆阻抗匹配