ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32蓝牙BLE实战:MicroPython+手机APP控制LED双向通信

ESP32蓝牙BLE实战:MicroPython+手机APP控制LED双向通信 1. 为什么我推荐用蓝牙BLE做第一个ESP32实战项目很多人拿到ESP32开发板之后第一个念头是连WiFi、点灯、跑个Web服务器。这些当然都能做但如果你问我哪个方向最适合零基础快速建立信心我会毫不犹豫地说蓝牙BLE。原因很简单WiFi项目你得配路由器、查IP地址、处理网络环境差异中间任何一个环节出问题新手都会卡住。而BLE不一样手机就在你手里开发板插上USB就能跑中间没有路由器、没有网线、没有IP配置链路短、变量少出问题容易定位。我当初第一次接触ESP32的时候就是先用MicroPython写了一个BLE广播的小脚本手机装个调试APP五分钟之内就看到了开发板发出来的数据。那种“通了”的感觉比折腾半天网络配置要爽快得多。这篇文章就是把我从零开始摸索ESP32蓝牙BLE的完整过程拆开来讲包括方案选型、环境搭建、代码编写、手机端调试、常见问题排查以及一些我踩过的坑。不管你是完全没碰过单片机的纯小白还是从Arduino转过来的老玩家都能找到可以直接抄作业的内容。核心关键词先摆出来ESP32、蓝牙BLE、手机APP、MicroPython。这四个词贯穿全文你只要跟着走最后一定能做到用手机APP控制ESP32上的LED开关并且能双向通信——手机发指令ESP32执行ESP32上报数据手机显示。提示本文所有操作基于ESP32开发板通用型如ESP32-WROOM-32模组和MicroPython固件。如果你用的是ESP32-S3或其他变种大部分步骤通用但引脚编号和部分固件细节需要对照官方文档调整。2. 方案选型为什么是MicroPython加BLE而不是Arduino或ESP-IDF2.1 三种主流开发方式的对比在开始动手之前有必要把ESP32的开发方式理一理。市面上主流的路子有三条Arduino框架、ESP-IDF乐鑫官方框架、MicroPython。三条路都能实现蓝牙BLE功能但体验差别很大。对比维度Arduino框架ESP-IDFMicroPython上手难度中等较高低代码量BLE示例约80-120行约200-300行约40-60行调试便利性需要编译烧录需要编译烧录直接REPL交互蓝牙协议栈封装较好最底层最灵活封装好够用适合人群有C基础专业嵌入式开发零基础/快速验证修改代码后重新编译烧录重新编译烧录直接运行我选MicroPython的理由很直接改一行代码立刻能看到效果。BLE调试过程中经常需要改广播名称、改UUID、改特征值如果用C语言框架每次改完都要编译烧录一轮下来少说一分钟。MicroPython在REPL里直接粘贴运行几秒钟就能验证。对于零基础学习者来说这种即时反馈太重要了。2.2 BLE和经典蓝牙的区别为什么选BLEESP32同时支持经典蓝牙BR/EDR和低功耗蓝牙BLE。很多人会问我到底该用哪个这里给一个简单的判断标准。经典蓝牙适合持续传输大量数据的场景比如音频流、文件传输。BLE适合小数据量、间歇性通信、低功耗的场景比如传感器读数上报、控制指令下发。我们要做的“手机APP控制ESP32”本质上就是发几个字节的开关指令BLE完全够用而且手机端兼容性更好——iOS和Android对BLE的支持都很完善。还有一个实际原因MicroPython对BLE的支持比经典蓝牙更成熟。在MicroPython固件里bluetooth模块的BLE功能是官方重点维护的API稳定文档齐全。经典蓝牙在MicroPython上的支持反而没那么完善。所以从工具链成熟度考虑BLE也是更稳妥的选择。2.3 手机端APP的选择思路手机APP这块我不建议一上来就自己写Android或iOS原生应用。零基础阶段先用通用的BLE调试APP把通信跑通理解数据格式和交互流程然后再考虑做定制APP。通用调试APP我用过几个功能都差不多扫描设备、连接、查看服务、读写特征值、订阅通知。你随便选一个评分高的就行关键是理解里面的概念。等你把ESP32端的代码调通了手机端想怎么玩都行。可以用MIT App Inventor拖拽做一个简单界面也可以用Flutter或React Native写跨平台应用甚至可以用微信小程序通过BLE接口通信。但那是下一步的事先把底层通信打通。3. 环境搭建从零到能跑第一行代码3.1 硬件准备清单先把东西备齐免得做到一半发现缺东少西。ESP32开发板一块通用型即可比如ESP32-WROOM-32核心板。注意有些板子标称ESP32但用的是其他模组买之前确认一下。USB数据线一根这里有个大坑很多USB线只能充电不能传数据。如果你插上电脑后设备管理器里看不到串口第一件事就是换线。电脑一台Windows、macOS、Linux都行。我下面以Windows为主讲macOS和Linux的操作逻辑一样只是串口名称不同。手机一部Android或iOS都可以用来装BLE调试APP。3.2 烧录MicroPython固件ESP32出厂时通常不带MicroPython需要我们自己烧录。步骤如下。第一步下载固件。去MicroPython官网的下载页面找到ESP32对应的固件文件通常是一个.bin文件。注意选择适合你开发板Flash大小的版本一般4MB Flash的板子选标准版就行。第二步安装烧录工具。Windows上常用的是esptool通过pip安装pip install esptool第三步擦除Flash并烧录。先确认串口号Windows在设备管理器里看比如COM3。然后执行# 擦除Flash esptool.py --chip esp32 --port COM3 erase_flash # 烧录固件 esptool.py --chip esp32 --port COM3 --baud 460800 write_flash -z 0x1000 esp32-xxxx.bin注意烧录时如果一直失败按住开发板上的BOOT键再执行命令进入下载模式后再松开。这是ESP32烧录的经典操作很多人第一次都会卡在这里。3.3 连接REPL并测试烧录完成后用串口工具连接开发板。我推荐用mpremote或Thonny。Thonny对新手更友好图形界面直接选串口就能连。连上之后你会看到一个提示符这就是MicroPython的REPL。输入下面这行测试一下print(hello esp32)如果能看到输出说明环境没问题。接下来导入蓝牙模块试试import bluetooth print(bluetooth.BLE)没有报错就说明固件里的BLE模块可用。4. BLE核心概念用生活化类比一次讲透4.1 GATT、服务、特征值到底是什么BLE的通信模型围绕GATT通用属性配置文件展开。刚接触的人会被一堆术语绕晕我用一个类比帮你理清。把ESP32想象成一栋办公楼。办公楼里有很多服务Service每个服务就像一个部门。比如“环境监测部”负责温度湿度“灯光控制部”负责开关灯。每个部门里有若干特征值Characteristic特征值就是具体可操作的项。比如灯光控制部里有一个“开关状态”特征值你可以读它看灯现在开还是关也可以写它控制灯开关。每个服务和特征值都有一个UUID通用唯一标识符相当于部门的门牌号和具体窗口的编号。UUID有16位的短UUID和128位的长UUID。标准服务用短UUID自定义服务用长UUID。我们自己定义的服务通常用128位UUID避免和标准服务冲突。4.2 广播、连接、通知的流程BLE设备通信分几个阶段。ESP32上电后先广播就像办公楼在门口挂个牌子说“我在这里我叫什么名字”。手机扫描到广播后可以发起连接。连接建立后手机可以读取特征值、写入特征值或者订阅通知。通知Notify这个机制特别有用。传统方式是手机不停地问“灯的状态变了吗”这叫轮询费电又低效。通知是反过来ESP32主动告诉手机“状态变了”。你订阅了某个特征值的通知之后只要ESP32那边一改值手机立刻收到推送。做传感器数据上报的时候通知是首选方式。4.3 一个最小可用的服务设计我们要实现“手机控制LED”设计可以很简洁。定义一个服务里面放两个特征值LED控制特征值手机写入ESP32读取。写入1开灯写入0关灯。状态上报特征值ESP32写入手机订阅通知。ESP32把当前LED状态或传感器数据推给手机。UUID我习惯这样编服务用6E400001-B5A3-F393-E0A9-E50E24DCCA9E控制特征值用6E400002-...上报特征值用6E400003-...。这套UUID是Nordic的经典模板很多例程都用兼容性好。5. 代码实操从广播到双向通信的完整实现5.1 最小BLE广播代码先让ESP32能被手机扫到。新建一个main.py写入以下代码import bluetooth import time ble bluetooth.BLE() ble.active(True) # 设置广播内容 name ESP32_LED ble.config(gap_namename) # 构造广播数据 def advertising_payload(nameNone): payload bytearray() if name: name_bytes name.encode() payload bytes([len(name_bytes) 1, 0x09]) name_bytes return payload ble.gap_advertise(100000, adv_dataadvertising_payload(name)) print(BLE广播已启动设备名, name)把这段代码保存到开发板并运行。手机打开BLE调试APP扫描设备应该能看到ESP32_LED。这一步成功说明广播链路通了。实操心得如果手机扫不到设备先确认ESP32的BLE是否真的激活了。在REPL里输入ble.active()看返回值。另外有些手机扫描时需要开启定位权限这是Android系统的要求不是ESP32的问题。5.2 定义服务和特征值广播通了之后加上GATT服务。完整代码如下import bluetooth import struct # UUID定义 _SERVICE_UUID bluetooth.UUID(6E400001-B5A3-F393-E0A9-E50E24DCCA9E) _LED_CHAR_UUID bluetooth.UUID(6E400002-B5A3-F393-E0A9-E50E24DCCA9E) _STATUS_CHAR_UUID bluetooth.UUID(6E400003-B5A3-F393-E0A9-E50E24DCCA9E) # 特征值控制用可写状态用可通知 LED_CHAR (_LED_CHAR_UUID, bluetooth.FLAG_WRITE | bluetooth.FLAG_READ) STATUS_CHAR (_STATUS_CHAR_UUID, bluetooth.FLAG_NOTIFY | bluetooth.FLAG_READ) SERVICE (_SERVICE_UUID, (LED_CHAR, STATUS_CHAR)) ble bluetooth.BLE() ble.active(True) # 注册服务拿到句柄 ((led_handle, status_handle),) ble.gatts_register_services((SERVICE,)) # 连接回调 _connections set() def on_ble_event(event, data): if event 1: # 连接 conn_handle, _, _ data _connections.add(conn_handle) print(设备已连接) elif event 2: # 断开 conn_handle, _, _ data _connections.discard(conn_handle) print(设备已断开) # 断开后重新广播 ble.gap_advertise(100000, adv_dataadvertising_payload(ESP32_LED)) elif event 3: # 写入 conn_handle, attr_handle data if attr_handle led_handle: value ble.gatts_read(led_handle) print(收到控制指令, value) # 这里执行LED控制逻辑 # 并把状态通过通知发回去 ble.gatts_notify(conn_handle, status_handle, value) ble.irq(on_ble_event) def advertising_payload(name): payload bytearray() name_bytes name.encode() payload bytes([len(name_bytes) 1, 0x09]) name_bytes return payload ble.gap_advertise(100000, adv_dataadvertising_payload(ESP32_LED)) print(BLE服务已启动)这段代码注册了一个服务、两个特征值并且设置了事件回调。手机连接后往控制特征值写数据ESP32会收到并打印同时把同样的值通过状态特征值通知回手机。5.3 加上真实的LED控制上面代码里LED控制还是注释现在加上真实的GPIO操作。ESP32开发板上通常有一个板载LED不同板子引脚不同。常见的是GPIO2。如果你外接LED记得串一个限流电阻一般220欧姆到1K欧姆都行。from machine import Pin led Pin(2, Pin.OUT) # 在写入回调里替换成 if attr_handle led_handle: value ble.gatts_read(led_handle) if value b\x01: led.value(1) print(LED已打开) elif value b\x00: led.value(0) print(LED已关闭) ble.gatts_notify(conn_handle, status_handle, value)手机端往控制特征值写01LED亮写00LED灭。同时手机能通过订阅状态特征值收到反馈。5.4 手机端操作流程打开BLE调试APP按这个顺序操作扫描设备找到ESP32_LED并连接。进入服务列表找到UUID为6E400001-...的服务。展开服务看到两个特征值。点击控制特征值6E400002-...选择写入输入01或00。点击状态特征值6E400003-...开启通知订阅。再次写入控制特征值观察通知区域是否收到数据。如果一切正常你会看到LED跟着指令亮灭手机端也能收到状态回传。到这一步核心目标就达成了。6. 常见问题与排查技巧实录6.1 手机搜不到设备这是最常见的问题。排查顺序如下确认ESP32的BLE已激活REPL里没有报错。确认广播数据格式正确特别是名称长度字节有没有算对。手机蓝牙是否开启定位权限是否给了APP。有些手机扫描缓存严重关掉蓝牙重开或者换一个APP试试。如果之前连接过同名设备手机可能直接连旧记录忽略新广播。在手机蓝牙设置里“忽略此设备”再扫。6.2 连接后立刻断开连接不稳定通常有几个原因。一是广播间隔设置太短ESP32处理不过来把gap_advertise的第一个参数调大一些比如从100000调到200000。二是手机端APP的问题换一个APP验证。三是供电不足USB口电流不够换一个USB口或加外部供电。6.3 写入特征值没反应先确认你写的是正确的特征值句柄。在代码里打印led_handle和status_handle的值和手机APP里显示的句柄对照。另外写入的数据格式要对MicroPython里gatts_read返回的是bytes比较的时候用b\x01而不是1。6.4 通知订阅后收不到数据检查特征值的FLAG是否包含了FLAG_NOTIFY。有些APP订阅通知需要先写CCCD客户端特征配置描述符通用调试APP一般会自动处理但如果你自己写APP就要注意。另外gatts_notify的第一个参数是连接句柄确保你传的是当前活跃连接的句柄。问题现象可能原因解决方法搜不到设备广播未启动/权限不足检查BLE激活状态开启手机定位权限连接后断开广播间隔太短/供电不足调大广播间隔换USB口写入无反应句柄错误/数据格式不对打印句柄对照用bytes比较通知收不到FLAG未设置/未订阅检查FLAG_NOTIFY确认订阅操作烧录失败未进下载模式/线材问题按住BOOT键换数据线避坑技巧调试BLE的时候串口打印是你的最好朋友。在回调函数里多打印事件类型和句柄值手机端每操作一步看串口输出什么很快就能定位问题在哪一环。7. 进阶方向从能用到好用7.1 数据格式的规范化前面我们直接传01和00简单场景够用。但如果你要传传感器数据比如温度值就需要约定数据格式。常见做法是用struct打包import struct # 打包一个浮点数温度值 temp 25.6 data struct.pack(f, temp) ble.gatts_notify(conn_handle, status_handle, data)手机端收到4个字节按小端浮点数解析就能还原。这种二进制格式效率高但调试时不如文本直观。折中方案是用JSON字符串可读性好代价是数据量大一些。小数据量场景下JSON完全可接受。7.2 低功耗优化BLE的优势是低功耗但默认配置不一定省电。几个优化点广播间隔拉长从100ms调到500ms甚至1s连接间隔参数在连接后可以协商让手机端同意更长的间隔不用的时候直接关闭BLE需要时再开。如果你做电池供电的项目这些优化能把续航从几小时拉到几天。7.3 多设备连接与绑定ESP32的BLE支持多个连接但MicroPython下同时连接的设备数有限一般三四个就到头了。如果你要做多手机控制需要在回调里管理连接句柄集合给每个连接单独发通知。另外绑定bonding功能可以让设备记住配对信息下次连接不用重新配对这个在MicroPython里的支持程度因固件版本而异需要实测。7.4 从调试APP到定制APP通信跑通之后你可以用MIT App Inventor拖一个简单界面一个开关按钮一个状态显示标签。App Inventor有现成的BLE组件拖拽配置就能用不需要写太多代码。再进一步用Flutter的flutter_blue_plus库写跨平台APP界面完全自定义。但不管用什么框架底层逻辑都是一样的扫描、连接、找服务、找特征值、读写、订阅通知。把本文的流程走通换任何前端都是水到渠成的事。8. 我踩过的几个坑和最后的小建议第一个坑是USB线。我一开始用了一根看起来很好的线结果电脑死活认不到串口换了三根线才找到一根能传数据的。这个坑太隐蔽了新手很容易以为是驱动问题或者板子坏了。第二个坑是UUID大小写。BLE的UUID在代码里和APP里显示的大小写可能不一样但它们是同一个UUID。我一度以为对不上后来发现只是显示格式差异。比较的时候统一转成小写或大写再比。第三个坑是广播数据长度。BLE广播包有31字节限制名称太长会放不下。如果你的设备名超过十几个字符可能要改用扫描响应包来放名称。我建议设备名控制在8个字符以内省事。第四个坑是REPL和运行模式的冲突。在REPL里粘贴代码运行和把代码存成main.py上电自动运行行为可能不一样。调试阶段用REPL没问题但最终部署一定要测试main.py自动运行的效果。我遇到过REPL里跑得好好的存成main.py后因为初始化顺序问题起不来的情况。最后分享一个小技巧用LED做状态指示。BLE连接状态、数据收发状态都可以用板载LED的不同闪烁模式来表示。调试的时候不用一直盯着串口看一眼LED就知道程序跑到哪一步了。这个习惯在脱离电脑、用充电宝供电测试的时候特别有用。代码这东西看十遍不如跑一遍。把上面的代码复制过去改改引脚号连上手机试一次你对BLE的理解会比看任何教程都深。
返回列表