1. 初识ESP32蓝牙通信BLE与经典蓝牙的区别第一次接触ESP32的开发者常会被它的蓝牙功能搞晕——为什么文档里同时存在Bluetooth Classic和BLE两种模式这得从蓝牙4.0标准说起。2010年蓝牙技术联盟推出蓝牙4.0时在传统蓝牙基础上新增了BLEBluetooth Low Energy这个分支就像手机有了省电模式。我实测发现经典蓝牙平均功耗在1W左右而BLE可以做到0.01-0.5W这就是为什么智能手环能用小电池撑一周。具体到ESP32上经典蓝牙适合传输音频这类大数据流比如连接蓝牙音箱而BLE更适合传感器数据这类小包传输。有个很形象的比喻经典蓝牙像持续流动的自来水管BLE则是定时滴灌系统。在ESP-IDF开发环境中两者API前缀也不同经典蓝牙使用esp_bt_开头的APIBLE使用esp_ble_开头的API实际项目中我建议优先考虑BLE除非你有音频传输需求。去年做个智能门锁项目时就因错误选用经典蓝牙导致续航缩水严重后来改用BLE后电池寿命延长了3倍。2. BLE广播实战从参数配置到地址类型选择2.1 广播参数深度解析广播相当于BLE设备的自我介绍核心配置都在esp_ble_adv_params_t这个结构体里。第一次用时我被十几个参数吓到其实关键就这几个typedef struct { uint16_t adv_int_min; // 最小广播间隔(单位0.625ms) uint16_t adv_int_max; // 最大广播间隔 esp_ble_adv_type_t adv_type; // 广播类型 esp_ble_addr_type_t own_addr_type; // 地址类型 esp_ble_addr_type_t peer_addr_type; // 对端地址类型 uint8_t channel_map; // 信道映射 esp_ble_adv_filter_t adv_filter_policy; // 过滤策略 } esp_ble_adv_params_t;广播间隔设置有个坑实测发现设得太短20ms会导致手机端扫描不到设备。这是因为手机蓝牙扫描是周期性的就像两个人转盘相亲转太快反而碰不上。建议范围在100-500ms之间。2.2 地址类型的选择艺术ESP32支持三种地址类型这个选择直接影响设备识别和连接PUBLIC地址像身份证号全球唯一且固定adv_params.own_addr_type BLE_ADDR_TYPE_PUBLIC;适合需要固定标识的场景比如共享单车锁RANDOM静态地址像化名重启不变adv_params.own_addr_type BLE_ADDR_TYPE_RANDOM;去年做医疗设备时就用这种既保护隐私又避免重复配对RANDOM可解析地址像动态密码定期变化adv_params.own_addr_type BLE_ADDR_TYPE_RPA;最安全但实现复杂需要配合IRK(Identity Resolution Key)使用有次客户投诉设备频繁断连最后发现是固件升级后误改了地址类型。所以记住地址类型要和配套APP同步修改3. GATT服务构建从理论到代码实现3.1 GATT服务架构解剖GATT服务像是个分层货架Service大分类如健康监测Characteristic具体商品如心率数据Descriptor商品标签如单位次/分钟用ESP-IDF创建服务时需要先定义属性表。这个表就像购物清单static const esp_gatts_attr_db_t gatt_db[] { // 服务声明 [IDX_SVC] {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)primary_service_uuid, ESP_GATT_PERM_READ, sizeof(heart_rate_svc_uuid), sizeof(heart_rate_svc_uuid), (uint8_t *)heart_rate_svc_uuid}}, // 特征声明 [IDX_CHAR_A] {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)char_prop_read}}, // 特征值 [IDX_CHAR_VAL_A] {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)heart_rate_meas_uuid, ESP_GATT_PERM_READ, sizeof(heart_rate), sizeof(heart_rate), (uint8_t *)heart_rate}}, // 客户端特征配置描述符 [IDX_CHAR_CFG_A] {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(uint16_t), (uint8_t *)notify_enable}}, };3.2 通知(Notify)与指示(Indicate)的妙用这两个特性是服务端主动推送数据的利器Notify像群发短信不保证送达esp_ble_gatts_send_indicate(gatts_if, conn_id, char_handle, sizeof(data), data, false);Indicate像挂号信需要客户端确认esp_ble_gatts_send_indicate(gatts_if, conn_id, char_handle, sizeof(data), data, true);做智能秤项目时我发现连续Notify会导致iOS设备丢包。后来改用Indicate20ms间隔稳定性大幅提升。记住Indicate会占用更多资源要根据业务需求选择。4. 实战中的性能优化技巧4.1 MTU协商的艺术MTU就像快递包裹的最大尺寸默认23字节往往不够用。通过MTU协商可以扩容到最多517字节// 服务端设置 esp_ble_gatt_set_local_mtu(150); // 客户端请求 esp_ble_gattc_send_mtu_req(gattc_if, conn_id);但要注意Android和iOS对MTU的支持不同。实测发现部分Android机型超过247字节会连接不稳定而iOS设备能稳定支持512字节大包。4.2 连接参数调优连接参数就像两个人对话的节奏esp_ble_conn_update_params_t params { .min_int 16, // 最小间隔(单位1.25ms) .max_int 32, // 最大间隔 .latency 0, // 从机跳过次数 .timeout 400 // 超时(单位10ms) }; esp_ble_gap_update_conn_params(params);有个智能家居项目曾因参数设置不当导致设备发热严重原参数min_int8, max_int16 → 功耗高但响应快优化后min_int40, max_int80 → 功耗降低60%仍满足需求记住黄金法则在满足业务需求的前提下尽量增大间隔值。5. 安全机制与配对模式5.1 配对方式选择ESP32支持四种配对模式安全级别递增Just Works像公共场所WiFi无验证esp_ble_auth_req_t auth_req ESP_LE_AUTH_NO_BOND;Passkey Entry输入6位密码esp_ble_auth_req_t auth_req ESP_LE_AUTH_BOND | ESP_LE_AUTH_REQ_MITM;Numeric Comparison双方确认相同数字OOB通过NFC等外部方式交换密钥去年做个门禁项目客户要求防中继攻击最终选择Passkey Entry绑定模式既安全又不影响用户体验。5.2 数据加密实战启用加密后所有通信数据都会自动加密// 设置安全参数 esp_ble_io_cap_t iocap ESP_IO_CAP_NONE; esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, iocap, sizeof(iocap)); // 注册安全回调 esp_ble_gap_register_callback(gap_event_handler);在事件处理函数中需要响应加密请求case ESP_GAP_BLE_SEC_REQ_EVT: esp_ble_confirm_reply(param-ble_security.ble_req.bd_addr, true); break;踩过的坑部分Android设备在加密连接时会额外要求绑定(Bonding)需要在代码中处理绑定相关事件否则会导致连接意外断开。