简介:这份源码包面向物联网与嵌入式开发者,聚焦乐鑫ESP32-C3在Arduino框架下的BLE无线通信能力,并配套微信小程序端实现,解决硬件控制与移动端交互打通的问题。项目适合智能家居、健康监测、智能穿戴等需要近距离无线连接与微信生态联动的场景,对初学者和进阶开发者均有参考价值。压缩包共713个文件,约32.22MB,包含22个ino主程序、10个cpp与10个h源文件、21个js与9个wxml、13个wxss等小程序页面文件,以及json配置、png与jpg界面素材、mp4演示视频和bin固件等,覆盖从硬件编程到用户界面设计的完整链路。目前已有250人学习下载。借助这份较新的技术资料,读者可快速理解BLE服务与特征值定义、小程序端设备发现与数据收发流程,并参考现成的目录组织与工程配置,减少从零搭建的试错成本。
1. 从一份源码包说起:ESP32-C3 的 BLE 和微信小程序怎么接上头
很多人第一次拿到“ESP32-C3 BLE 配微信小程序”这类源码包,第一反应是打开压缩包找main.ino,然后直接编译烧录,结果小程序搜不到设备,或者连上了发指令没反应。问题往往不在代码本身,而在于没有先理清这条链路:ESP32-C3 跑的是 Arduino 框架下的 BLE 外设,微信小程序跑的是 BLE 中心设备,两者之间靠 GATT 服务与特征值通信。标题里的“源码”不是让你照抄,而是让你理解这套最小系统的骨架——广播怎么发、服务怎么建、小程序怎么发现和订阅。适合已经上手过 Arduino、想用小程序做 BLE 调试或产品原型的开发者。下面按“先跑通、再调参、后避坑”的顺序拆开讲。
2. 环境搭起来:ESP32-C3 在 Arduino 框架下的 BLE 最小工程
2.1 开发板选型与 Arduino 环境配置
ESP32-C3 和经典 ESP32 在 BLE 库上有差异,不能直接套用ESP32 BLE Arduino的旧例程。当前 Arduino-ESP32 核心包已经内置了BLEDevice库,但 C3 只支持 BLE 4.2/5.0 的部分特性,不支持经典蓝牙。配置步骤:
- 在 Arduino IDE 的“开发板管理器”中安装
esp32包,版本选 2.0.14 以上(低于此版本 C3 的 BLE 编译会报undefined reference)。 - 开发板选择
ESP32C3 Dev Module,USB CDC 开启,Flash 选4MB,分区默认。 - 安装
ESP32 BLE Arduino库不是必须的,因为核心包已带BLEDevice.h,但如果你要用BLE2902描述符,需要确认库路径下存在BLE2902.cpp。
一个容易翻车的地方:C3 的 GPIO 编号和经典 ESP32 不同,板载 LED 通常在 GPIO8 或 GPIO9,但 BLE 初始化不依赖 GPIO,所以先不用管。编译前把Upload Speed降到 115200,C3 的 USB 转串口在高速下容易握手失败。
2.2 一个能跑起来的最小 BLE 外设代码
下面这段代码在 ESP32-C3 上创建一个名为C3_BLE的设备,暴露一个可读可写的特征值,用来接收小程序发来的字符串并回显。
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> BLECharacteristic *pCharacteristic; bool deviceConnected = false; // 自定义服务与特征 UUID,小程序端必须一致 #define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" #define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; Serial.println("Central connected"); } void onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("Central disconnected, restart advertising"); pServer->startAdvertising(); // 断开后重新广播,否则小程序再也搜不到 } }; class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pChar) { String value = pChar->getValue(); if (value.length() > 0) { Serial.print("Received: "); Serial.println(value.c_str()); pChar->setValue("echo:" + value); // 回写数据 pChar->notify(); // 主动通知小程序 } } }; void setup() { Serial.begin(115200); BLEDevice::init("C3_BLE"); // 广播名称 BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristic->addDescriptor(new BLE2902()); // 通知描述符,小程序订阅用 pCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // 影响连接间隔,太小会不稳 pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("BLE advertising started"); } void loop() { delay(2000); }逻辑说明:BLEDevice::init设置广播名,createServer建立 GATT 服务端,createCharacteristic创建特征值并赋予读、写、通知三种属性。BLE2902描述符是微信小程序能订阅通知的关键,没有它小程序调用notifyBLECharacteristicValueChange会失败。onDisconnect里重新调用startAdvertising是血泪经验——很多示例没写这句,导致手机断开后必须重启 ESP32 才能再次连接。
参数说明:setMinPreferred(0x06)和0x12分别对应最小和最大连接间隔,单位是 1.25ms。0x06 约 7.5ms,0x12 约 22.5ms。间隔太小会增加功耗,太大则小程序端响应变慢。如果只是传指令,保持默认即可;如果要传传感器数据流,建议设为 0x0C 到 0x18 之间。
2.3 编译烧录时最容易卡住的三个点
第一,串口监视器打开后按复位键没有输出。检查USB CDC On Boot是否设为Enabled,C3 的 USB 外设需要这个选项才能把Serial映射到 USB。第二,编译报BLE2902.h: No such file。这是因为核心包版本太老,升级到 2.0.14 以上即可。第三,烧录成功但广播搜不到。用手机上的通用 BLE 调试 App 先扫一遍,如果通用 App 也搜不到,说明广播没起来,检查BLEDevice::init是否在setup最前面调用,以及是否在createServer之前调用了其他 BLE 函数。
3. 微信小程序端:从搜索设备到收发数据的完整链路
3.1 小程序 BLE API 的调用顺序与权限
微信小程序的 BLE 接口是异步回调式,顺序错了就搜不到。标准流程是:openBluetoothAdapter→startBluetoothDevicesDiscovery→onBluetoothDeviceFound→createBLEConnection→getBLEDeviceServices→getBLEDeviceCharacteristics→notifyBLECharacteristicValueChange→writeBLECharacteristicValue。其中openBluetoothAdapter失败最常见的原因是手机蓝牙没开或小程序没有蓝牙权限。从基础库 2.9.0 开始,iOS 和 Android 都需要在app.json里声明requiredPrivateInfos: ["getBluetoothDevices"],否则onBluetoothDeviceFound不返回数据。
3.2 搜索、连接与订阅通知的代码实现
// 初始化蓝牙适配器 wx.openBluetoothAdapter({ success: () => { wx.startBluetoothDevicesDiscovery({ services: ['6E400001-B5A3-F393-E0A9-E50E24DCCA9E'], // 过滤目标服务 success: () => { wx.onBluetoothDeviceFound((res) => { res.devices.forEach(device => { if (device.name === 'C3_BLE') { wx.createBLEConnection({ deviceId: device.deviceId }); } }); }); } }); } }); // 连接成功后获取服务与特征 wx.onBLEConnectionStateChange((res) => { if (res.connected) { wx.getBLEDeviceServices({ deviceId: res.deviceId, success: (svcRes) => { svcRes.services.forEach(svc => { if (svc.uuid.indexOf('6E400001') !== -1) { wx.getBLEDeviceCharacteristics({ deviceId: res.deviceId, serviceId: svc.uuid, success: (charRes) => { charRes.characteristics.forEach(ch => { if (ch.uuid.indexOf('6E400002') !== -1) { // 订阅通知 wx.notifyBLECharacteristicValueChange({ deviceId: res.deviceId, serviceId: svc.uuid, characteristicId: ch.uuid, state: true }); // 监听数据回传 wx.onBLECharacteristicValueChange((v) => { const data = new Uint8Array(v.value); console.log('收到:', String.fromCharCode(...data)); }); } }); } }); } }); } }); } }); // 发送字符串 function sendCmd(deviceId, serviceId, charId, str) { const buffer = new ArrayBuffer(str.length); const dataView = new DataView(buffer); for (let i = 0; i < str.length; i++) { dataView.setUint8(i, str.charCodeAt(i)); } wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId: charId, value: buffer }); }逻辑说明:startBluetoothDevicesDiscovery的services参数传入 ESP32 广播的服务 UUID,可以大幅减少搜索到的无关设备。onBLEConnectionStateChange里做服务发现,注意getBLEDeviceServices返回的 UUID 可能是 128 位全写,也可能是 16 位短写,用indexOf匹配更稳。notifyBLECharacteristicValueChange必须在write之前调用,否则收不到回传。发送数据时小程序只接受ArrayBuffer,不能直接传字符串。
参数说明:deviceId在 iOS 和 Android 上格式不同,iOS 是 UUID 字符串,Android 是 MAC 地址,不要硬编码。state: true表示开启通知,关闭用false。如果发送的数据超过 20 字节,需要分包,因为 BLE 默认 MTU 是 23,有效载荷 20 字节。ESP32 端可以在BLEDevice::init后调用BLEDevice::setMTU(512)协商更大 MTU,但小程序端也要在连接后调用setBLEMTU,且 iOS 对 MTU 有额外限制。
3.3 小程序端调试的实用技巧
真机调试时打开vConsole,把onBluetoothDeviceFound的res完整打印出来,看deviceId和name是否匹配。如果搜不到,先确认手机蓝牙列表里能不能看到C3_BLE,如果系统蓝牙能看到但小程序搜不到,多半是services过滤写错了。另外,Android 上createBLEConnection偶尔会超时,加一个 3 秒重试逻辑能省很多事。iOS 上连接后如果 10 秒内没有调用getBLEDeviceServices,系统会自动断开,所以服务发现要放在连接成功的回调里立刻执行。
4. 避坑与排查:BLE 连接不稳、数据收不到、小程序搜不到设备
4.1 现象:小程序能搜到设备但连接失败
原因通常是广播间隔和连接参数不匹配。ESP32-C3 默认广播间隔约 100ms,如果小程序端在onBluetoothDeviceFound里立刻调用createBLEConnection,而设备还在广播态,连接请求可能被丢弃。解决方法是搜到设备后延迟 200ms 再连接,或者在 ESP32 端把setMinPreferred调大,让广播更稳定。另一个原因是 Android 的deviceId在每次扫描后可能变化,不要缓存旧的deviceId。
4.2 现象:连接成功但写数据后 ESP32 没反应
先检查特征值的属性是否包含PROPERTY_WRITE,如果只写了PROPERTY_WRITE_NR(无响应写),小程序端writeBLECharacteristicValue会失败。其次检查onWrite回调里getValue()返回的长度,如果小程序发送的是ArrayBuffer但 ESP32 端按String处理,中文字符会乱码。建议统一用 ASCII 或 UTF-8 编码,ESP32 端用std::string接收原始字节。还有一个隐蔽问题:BLE2902描述符没有添加时,notify调用不会报错但小程序收不到数据。
4.3 现象:ESP32 断开后无法再次连接
这是最典型的坑。onDisconnect回调里必须调用pServer->startAdvertising(),否则广播停止,手机再也搜不到。但注意不要在onDisconnect里直接调用BLEDevice::startAdvertising(),因为此时 server 还在清理连接,直接调用可能返回ESP_ERR_INVALID_STATE。正确做法是在onDisconnect里设置一个标志位,在loop里检测到标志位后延迟 500ms 再重新广播。另外,如果小程序端没有主动调用closeBLEConnection,ESP32 端会一直认为连接存在,所以小程序页面卸载时要记得断开连接。
4.4 现象:通知数据偶尔丢失或延迟
BLE 通知是“发后即忘”的,没有重传机制。如果 ESP32 端在notify后立刻又写特征值,前一个通知可能被覆盖。解决方法是每次notify后加delay(10),或者用pCharacteristic->setValue后等待onStatus回调再发下一条。小程序端onBLECharacteristicValueChange的回调频率也有限制,Android 上大约每 50ms 一次,iOS 更宽松。如果数据量大,建议改成分包读取而不是依赖通知。
4.5 现象:编译通过但运行几秒后重启
ESP32-C3 的 BLE 协议栈占用约 40KB RAM,如果同时开了 WiFi 和 BLE,内存不够会触发看门狗重启。检查setup里是否调用了WiFi.begin,如果不需要 WiFi 就注释掉。另外,BLEDevice::init之后不要重复调用,否则会内存泄漏。用ESP.getFreeHeap()打印剩余内存,低于 20KB 时就要精简代码。
5. 进阶技巧:用 BLE 做 OTA 触发与数据透传的边界控制
5.1 用特征值写操作触发 OTA 升级
在最小工程基础上,可以增加一个特征值专门接收 OTA 指令。当小程序写入"OTA"字符串时,ESP32 端解析后调用Update.begin并进入升级流程。关键点是 OTA 期间要暂停 BLE 通知,否则协议栈和 Flash 写入会抢资源导致失败。代码片段:
if (value == "OTA") { pCharacteristic->setValue("ready"); pCharacteristic->notify(); delay(100); // 此处调用 OTA 逻辑,注意先断开 BLE 连接 pServer->disconnect(0); // 断开所有连接 // startOTA(); }参数说明:disconnect(0)的参数是连接 ID,0 表示第一个连接。断开后广播会停止,OTA 完成后需要手动重启并重新初始化 BLE。这个方案适合小固件升级,大固件建议用 WiFi OTA。
5.2 数据透传的 MTU 协商与分包策略
ESP32-C3 支持 MTU 最大 517 字节,但实际可用取决于手机。Android 上可以在连接后调用BLEDevice::setMTU(247),小程序端调用wx.setBLEMTU({ mtu: 247 })。协商成功后,单次可发 244 字节。超过这个长度需要分包,建议在协议头加 2 字节长度字段,接收端按长度拼接。注意 iOS 的 MTU 协商是系统自动的,小程序端setBLEMTU在 iOS 上可能不生效,所以透传协议要兼容 20 字节的默认 MTU。
5.3 验证连接稳定性的一个土办法
拿两个手机,一个开小程序连接 ESP32,另一个开通用 BLE 调试 App 同时连接。如果第二个手机连不上,说明 ESP32 的广播参数只允许一个连接,检查BLEDevice::getAdvertising()->setMaxConnections(1)之类的设置。如果两个都能连上但数据互相干扰,说明特征值的notify没有区分连接 ID。这个办法能快速判断是协议栈限制还是代码逻辑问题。
5.4 我踩过的一个坑:UUID 大小写导致匹配失败
微信小程序返回的 UUID 是全小写,而 ESP32 端定义的是大写。用indexOf匹配时如果不转成统一大小写,就会漏掉。我一般会在小程序端把svc.uuid.toLowerCase()后再比较,ESP32 端也统一用小写定义。这个坑不报错,只是搜不到服务,排查起来很费时间。希望帮到你。
本文还有配套的精品资源,点击获取