ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ESP32-C3 BLE与微信小程序GATT双向通信实战

2026/9/10 7:23:40 拓冰建站 浏览量
ESP32-C3 BLE与微信小程序GATT双向通信实战 简介本资源是一套完整的乐鑫ESP32-C3 BLE与微信小程序双向通信开发源码面向物联网初学者及嵌入式开发者解决硬件端BLE外设开发与小程序端低门槛无线交互的集成难题。项目涵盖Arduino框架下的ESP32-C3固件代码.ino/.cpp/.h、微信小程序前端代码.wxml/.wxss/.js、配套JSON配置、设备调试日志与多组示例sample、说明文档md/readme及部分编译中间文件.o/.d/.bin共713个文件总大小32.22MB结构完整、开箱即用。目前已有242人学习下载适合快速搭建智能家居、健康监测等BLE小程序原型系统。开发者可直接复用硬件控制逻辑、小程序蓝牙API调用封装、服务/特征值定义规范及跨平台通信调试方法同时获得含注释的完整工程目录与典型场景实现如设备发现、连接、数据收发、状态同步显著降低BLE小程序联调门槛。1. 用 ESP32-C3 做 BLE 设备再让微信小程序连上它——不是“配网”也不是“扫码”而是真正在 GATT 层双向通信你手头有一块 ESP32-C3 开发板想让它广播一个服务暴露温度、开关状态或自定义控制特征同时你希望用户打开微信小程序不装 App、不配 Wi-Fi、不输密码就能实时读取传感器数据、点击按钮下发指令——这正是标题里「乐鑫 ESP32-C3 BLE基于 Arduino 框架和微信小程序」要解决的典型场景。它绕开了传统 IoT 的复杂配网流程直击 BLE GATT 协议栈与小程序蓝牙 API 的衔接痛点。适合嵌入式初学者快速验证无线交互逻辑也适合硬件产品经理做最小可行性原型MVP用 Arduino IDE 写固件、用微信开发者工具调试前端两端开发节奏完全解耦。注意这不是模拟器跑通就完事——ESP32-C3 的 BLE 射频稳定性、Arduino Core for ESP32 的 BLE 库版本兼容性、微信小程序在 iOS/Android 上对 BLE 广播包解析差异都会在真实连接时暴露。下文将从协议选型开始逐层落地到可复现的编译命令、小程序 JS 接口调用链、以及三个必查的连接失败日志位置。2. 为什么选 ESP32-C3 Arduino 而非 ESP-IDF 或 nRF52BLE 广播与 GATT 服务设计必须匹配小程序能力边界2.1 选型依据C3 的 RISC-V 核心 硬件 BLE 5.0 是微信小程序支持的最低可靠基线微信小程序蓝牙 APIwx.openBluetoothAdapter→wx.startBluetoothDevicesDiscovery→wx.getConnectedBluetoothDevices对底层 BLE 设备有明确约束必须使用ADV_IND 广播类型不可用 ADV_NONCONN_IND广播包中需包含Complete Local Name和Service UUIDs16-bit 或 128-bit且不能依赖扫描响应包Scan Response传递关键服务信息——因为 iOS 微信客户端默认不主动请求 Scan Response。ESP32-C3 在 Arduino Core v2.0.11 中已原生支持BLEDevice::advertise()的完整广播配置而早期 ESP32-S2 或 ESP8266 则因广播包长度限制或协议栈缺陷常导致小程序扫描不到设备。对比 nRF52 系列C3 的优势在于 Arduino 生态成熟BLEDevice,BLEUtils,BLEService,BLECharacteristic四个类封装清晰无需手动拼接 ATT PDU且BLECharacteristic::setValue()自动触发 Notify 事件与小程序wx.onBLEConnectionStateChange的状态机天然对齐。提示不要用BLEDevice::setScanResponseData()补充服务信息。微信小程序尤其 iOS 版本在wx.startBluetoothDevicesDiscovery阶段仅解析广播包Advertising Data忽略 Scan Response。所有服务 UUID 必须塞进BLEDevice::setAdvertisingData()的advData结构体中。2.2 定义 GATT 服务用 128-bit UUID 避免 Android/iOS 缓存冲突特征值权限必须显式声明微信小程序要求每个BLECharacteristic必须明确声明PROPERTY_READ/PROPERTY_WRITE/PROPERTY_NOTIFY否则wx.writeBLECharacteristicValue会静默失败。以下代码定义了一个标准控制服务0x1810 Environmental Sensing Service 的简化变体其中CONTROL_SERVICE_UUID使用 128-bit 格式生成避免与系统预定义 UUID 冲突#include BLEDevice.h #include BLEUtils.h #include BLEServer.h // 生成唯一 128-bit UUID使用 https://www.uuidgenerator.net/ 生成粘贴后去掉横线 #define CONTROL_SERVICE_UUID 0000abcd000040008000000000000000 #define CONTROL_CHAR_UUID 0000efgh000040008000000000000000 #define STATUS_CHAR_UUID 0000ijkl000040008000000000000000 void setup() { Serial.begin(115200); BLEDevice::init(ESP32-C3-LED); // 设备名必须与小程序 wx.startBluetoothDevicesDiscovery 的 nameFilter 匹配 BLEDevice::setEncryptionLevel(ESP_BLE_SEC_NONE); // 微信小程序不支持配对必须设为无加密 BLEAdvertising *pAdvertising BLEDevice::getAdvertising(); BLEDevice::setAdvertisingType(ADV_TYPE_ADV_IND); // 强制设置为可连接广播类型 // 构造广播数据必须包含设备名 服务 UUID BLEAdvertisementData advData; advData.setName(ESP32-C3-LED); advData.setUUIDList({CONTROL_SERVICE_UUID}); // 关键服务 UUID 必须在此处声明 pAdvertising-setAdvertisingData(advData); // 创建 GATT 服务 BLEServer *pServer BLEDevice::createServer(); BLEService *pService pServer-createService(CONTROL_SERVICE_UUID); // 创建可写特征值接收小程序指令 BLECharacteristic *pControlChar pService-createCharacteristic( CONTROL_CHAR_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR // Write Without Response降低延迟 ); pControlChar-setValue(0); // 初始化值 // 创建可通知特征值向小程序推送状态 BLECharacteristic *pStatusChar pService-createCharacteristic( STATUS_CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY ); pStatusChar-setValue(OFF); pService-start(); pAdvertising-start(); Serial.println(BLE device advertising...); }2.2.1 关键参数说明BLEDevice::setEncryptionLevel(ESP_BLE_SEC_NONE)微信小程序蓝牙 API 不支持配对流程若设为ESP_BLE_SEC_UNAUTHENTICATED会导致 iOS 端连接后立即断开advData.setUUIDList({CONTROL_SERVICE_UUID})此行决定小程序能否发现该设备。若遗漏wx.getConnectedBluetoothDevices()返回空数组PROPERTY_WRITE_NR小程序调用wx.writeBLECharacteristicValue时默认使用 Write Without Response避免等待 ACK 增加操作延迟pStatusChar-setValue(OFF)初始值必须设置否则小程序wx.readBLECharacteristicValue读取时返回空缓冲区。2.3 Arduino IDE 环境配置Core 版本、端口识别与上传超时的三重校验ESP32-C3 在 Windows 上常出现「端口一会识别一会不识别」本质是 CP210x 驱动与 USB 描述符协商失败。必须执行以下三步安装最新 CP210x 驱动v6.10.0从 Silicon Labs 官网下载卸载旧驱动后重启Arduino Core for ESP32 版本锁定为 2.0.11在 Arduino IDE →文件 首选项 附加开发板管理器网址中添加https://raw.githubusercontent.com/espressif/arduino-esp32/2.0.11/package_esp32_index.json然后通过工具 开发板 开发板管理器安装对应版本2.0.12 存在 BLE 广播包截断 Bug上传前强制进入下载模式按住 BOOT 键再按一次 RESET 键松开 RESET 后再松开 BOOT —— 此操作可绕过 C3 的 USB CDC 自动识别不稳定问题。注意若仍报错Failed to connect with ESP32: Timed out waiting for packet header检查工具 端口是否显示COMx (Silicon Labs CP210x USB to UART Bridge)而非(Standard Serial over Bluetooth link)。后者是 Windows 自动绑定的错误端口。3. 微信小程序端从wx.openBluetoothAdapter到wx.notifyBLECharacteristicValueChange的完整调用链与错误捕获3.1 初始化与设备发现nameFilter与services双重过滤是稳定连接的前提小程序必须在app.js全局初始化蓝牙适配器并在页面onLoad中启动设备发现。关键点在于不能只依赖wx.getConnectedBluetoothDevices()必须主动扫描因为 ESP32-C3 默认不自动连接且微信未提供「后台持续监听」能力。// pages/index/index.js Page({ data: { connectedDeviceId: , statusValue: OFF, isConnecting: false }, onLoad() { this.initBluetooth(); }, initBluetooth() { wx.openBluetoothAdapter({ success: () { console.log(蓝牙适配器开启成功); this.startDiscovery(); }, fail: (err) { console.error(开启蓝牙失败, err); wx.showToast({ title: 请开启手机蓝牙, icon: none }); } }); }, startDiscovery() { // nameFilter 必须与 ESP32-C3 的 BLEDevice::init(ESP32-C3-LED) 完全一致 // services 必须填入服务 UUID 字符串128-bit 格式小写无横线 wx.startBluetoothDevicesDiscovery({ powerLevel: high, // 强制高功率扫描提升 C3 广播包接收率 nameFilter: ESP32-C3-LED, services: [0000abcd000040008000000000000000], // 与固件中 CONTROL_SERVICE_UUID 严格对应 success: () { console.log(开始扫描设备); this.listenForFoundDevices(); } }); }, listenForFoundDevices() { wx.onBluetoothDeviceFound((res) { console.log(发现设备:, res.devices); const targetDevice res.devices.find(d d.name ESP32-C3-LED d.services?.includes(0000abcd000040008000000000000000) ); if (targetDevice) { this.connectToDevice(targetDevice.deviceId); } }); } });3.1.1 为什么nameFilter和services必须同时设置nameFilter过滤广播包中的设备名减少无效设备数量services过滤广播包中的 Service UUIDs 字段避免扫描到其他 BLE 设备如耳机、手环若只设nameFilteriOS 微信可能因广播包解析延迟漏掉设备若只设servicesAndroid 可能匹配到同 UUID 的其他设备。3.2 连接与特征值操作wx.createBLEConnection后必须wx.getBLEDeviceServiceswx.getBLEDeviceCharacteristics连接成功后不能直接读写特征值。必须先获取服务列表再获取特征值列表否则wx.writeBLECharacteristicValue报错invalid characteristic。connectToDevice(deviceId) { this.setData({ isConnecting: true }); wx.createBLEConnection({ deviceId, success: () { console.log(连接成功); this.setData({ connectedDeviceId: deviceId }); this.discoverServicesAndCharacteristics(deviceId); }, fail: (err) { console.error(连接失败, err); this.setData({ isConnecting: false }); } }); }, discoverServicesAndCharacteristics(deviceId) { wx.getBLEDeviceServices({ deviceId, success: (res) { console.log(服务列表:, res.services); const controlService res.services.find(s s.uuid.toLowerCase() 0000abcd000040008000000000000000 ); if (controlService) { this.discoverCharacteristics(deviceId, controlService.uuid); } } }); }, discoverCharacteristics(deviceId, serviceUuid) { wx.getBLEDeviceCharacteristics({ deviceId, serviceId: serviceUuid, success: (res) { console.log(特征值列表:, res.characteristics); const controlChar res.characteristics.find(c c.uuid.toLowerCase() 0000efgh000040008000000000000000 ); const statusChar res.characteristics.find(c c.uuid.toLowerCase() 0000ijkl000040008000000000000000 ); if (controlChar statusChar) { this.startNotify(deviceId, serviceUuid, statusChar.uuid); this.setData({ controlChar: { deviceId, serviceId: serviceUuid, uuid: controlChar.uuid }, statusChar: { deviceId, serviceId: serviceUuid, uuid: statusChar.uuid } }); } } }); }, startNotify(deviceId, serviceId, characteristicId) { wx.notifyBLECharacteristicValueChange({ state: true, deviceId, serviceId, characteristicId, success: () { console.log(Notify 已启用); // 监听特征值变化 wx.onBLECharacteristicValueChange((res) { const value new Uint8Array(res.value); const str String.fromCharCode(...value); this.setData({ statusValue: str }); }); } }); } });3.2.1wx.notifyBLECharacteristicValueChange的隐含前提必须在wx.getBLEDeviceCharacteristics成功后调用characteristicId必须与固件中pStatusChar-getUUID().toString()输出的字符串完全一致小写、无横线若固件未调用pStatusChar-notify()小程序不会收到任何数据——需在 ESP32-C3 代码中定时或事件触发pStatusChar-setValue(ON); pStatusChar-notify();。3.3 控制指令下发wx.writeBLECharacteristicValue的 buffer 构造与 iOS 兼容性处理小程序向 ESP32-C3 发送指令时必须将字符串转为ArrayBuffer且长度需与固件pControlChar-setValue()的缓冲区匹配。常见错误是直接传字符串导致 iOS 端写入失败。sendCommand(command) { const { controlChar } this.data; if (!controlChar) return; // 构造 ArrayBuffer长度固定为 1 字节内容为 1 或 0 const buffer new ArrayBuffer(1); const dataView new DataView(buffer); dataView.setUint8(0, command ON ? 0x31 : 0x30); // ASCII 1 or 0 wx.writeBLECharacteristicValue({ deviceId: controlChar.deviceId, serviceId: controlChar.serviceId, characteristicId: controlChar.uuid, value: buffer, success: () { console.log(指令发送成功); }, fail: (err) { console.error(指令发送失败, err); // 常见错误码-1未知错误、10005特征值不可写、10007连接已断开 if (err.errCode 10005) { wx.showToast({ title: 设备不支持写入, icon: none }); } } }); } });提示iOS 微信对value的 ArrayBuffer 长度极其敏感。若固件pControlChar定义为setValue(0)长度 1则小程序 buffer 长度必须为 1。若传入长度为 2 的 bufferiOS 会静默丢弃。4. 排查连接失败的三大日志源串口输出、微信开发者工具调试器、手机系统蓝牙日志4.1 ESP32-C3 串口日志定位广播与连接状态机卡点在loop()中添加状态打印可快速判断是广播未发出还是连接被拒绝void loop() { // 检查是否已有客户端连接 BLEDevice::poll(); // 必须调用否则 notify 不生效 if (BLEDevice::getConnectedCount() 0) { Serial.print(Connected clients: ); Serial.println(BLEDevice::getConnectedCount()); // 模拟状态更新每 5 秒切换一次 LED 状态并通知 static unsigned long lastNotify 0; if (millis() - lastNotify 5000) { static bool ledOn false; ledOn !ledOn; pStatusChar-setValue(ledOn ? ON : OFF); pStatusChar-notify(); lastNotify millis(); Serial.printf(Notified: %s\n, ledOn ? ON : OFF); } } else { Serial.println(No client connected); } delay(1000); }4.1.1 关键日志含义No client connected小程序未发起连接检查wx.createBLEConnection是否调用Connected clients: 1连接已建立但小程序未收到 notify检查wx.notifyBLECharacteristicValueChange是否启用若串口无任何输出说明BLEDevice::init()失败检查 Arduino Core 版本与 Board 设置ESP32-C3 DevKitC-1Flash Mode QIOPartition Scheme Default。4.2 微信开发者工具调试器抓取蓝牙 API 调用链与错误码映射在开发者工具中打开「调试器」→「Network」→「Bluetooth」标签页可查看每次 API 调用的完整请求/响应。重点关注错误码含义解决方案10000未开启蓝牙适配器调用wx.openBluetoothAdapter后未等 success 回调就执行后续操作10001设备未找到nameFilter与固件BLEDevice::init()名称不一致或广播包未包含 Service UUID10005特征值不可写固件中pControlChar未设置PROPERTY_WRITE或小程序传入 buffer 长度不匹配10007连接已断开小程序侧未监听wx.onBLEConnectionStateChange或固件主动调用pServer-disconnect()注意开发者工具的 Bluetooth 日志仅模拟 Android 行为iOS 真机测试必须用「iPhone 设置 隐私与安全性 分析与改进 共享 iPhone 分析」开启日志然后在「设置 蓝牙」中长按设备名查看连接详情。4.3 手机系统级日志Android Logcat 与 iOS Console 的交叉验证Android 端需开启 USB 调试# 过滤 BLE 相关日志 adb logcat -s BluetoothAdapter:B BluetoothDevice:B BluetoothGatt:B关注D/BluetoothGatt: connect() - device: XX:XX:XX:XX:XX:XX后是否出现onClientConnectionState() - status0 clientIf5status0 表示连接成功。iOS 端需 Mac Xcode连接 iPhoneXcode →Window Devices and Simulators选择设备 →Open Console过滤关键词CBManager、CBPeripheral成功连接时会出现CBPeripheral connected失败时显示CBPeripheral connection failed: Error DomainCBErrorDomain Code6表示设备忙或拒绝连接。5. 优化 BLE 通信可靠性MTU 协商、Notify 频率控制与小程序页面生命周期管理5.1 主动协商 MTU避免长消息被截断提升单次传输效率ESP32-C3 默认 MTU 为 23 字节而微信小程序wx.writeBLECharacteristicValue最大支持 512 字节 buffer。若需传输 JSON 数据如{ temp: 25.3, hum: 60 }必须协商更大 MTU// 在 setup() 中连接建立后调用 class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { Serial.println(Client connected); // 主动请求 MTU 协商 pServer-getConnectedClient()-requestMTU(256); } void onMTUChange(uint16_t mtu) { Serial.printf(MTU changed to: %d\n, mtu); } };小程序侧无需额外操作wx.writeBLECharacteristicValue自动适配协商后的 MTU。但注意wx.readBLECharacteristicValue仍受限于固件pStatusChar-getValue().length()若固件未调用pStatusChar-setValue()更新内容读取结果为空。5.2 Notify 频率控制用delay(20)替代delay(1000)防止 iOS 限频iOS 系统对 Notify 频率有严格限制约 20Hz 上限。若 ESP32-C3 每 100ms 调用pStatusChar-notify()iOS 微信会丢弃部分通知。实测有效方案是// 在 loop() 中改为 static unsigned long lastNotify 0; if (millis() - lastNotify 20) { // 20ms 间隔 ≈ 50HziOS 实际接受约 20~25Hz pStatusChar-notify(); lastNotify millis(); }5.3 小程序页面生命周期管理onHide时断开连接onShow时重连微信小程序切后台时蓝牙连接可能被系统回收。必须在onHide中主动断开并在onShow中重建onHide() { const { connectedDeviceId } this.data; if (connectedDeviceId) { wx.closeBLEConnection({ deviceId: connectedDeviceId, success: () { console.log(连接已关闭); this.setData({ connectedDeviceId: , statusValue: DISCONNECTED }); } }); } }, onShow() { const { connectedDeviceId } this.data; if (connectedDeviceId) { // 重新启用 Notify this.startNotify(connectedDeviceId, this.data.statusChar.serviceId, this.data.statusChar.uuid); } }此逻辑确保用户从微信聊天页返回小程序时状态同步不中断。若省略onHide断开再次进入时wx.createBLEConnection可能返回already connected错误需捕获err.errCode 10006并忽略。本文还有配套的精品资源点击获取