资讯动态

零基础入门ESP32蓝牙BLE:MicroPython与手机APP实战

发布时间:2026/10/3 6:54:01 来源:尧图企业网站定制
1. 为什么我建议零基础从蓝牙BLE切入ESP32很多人拿到ESP32开发板的第一反应是连WiFi、点灯、跑Web服务器。但如果你手上只有一块板子、一根数据线、一部手机想快速做出一个“能感知到成果”的小项目蓝牙BLE其实是最短的路径。原因很简单WiFi方案通常需要一个路由器、一个局域网环境还要处理IP地址、端口、防火墙这些额外变量而BLE是点对点的手机和ESP32直接对话不需要任何中间设备通电就能连连上就能控。我最初接触ESP32的时候试过先折腾WiFi Web服务器结果卡在手机浏览器访问不了开发板IP上排查了半天才发现是手机连的是5G频段、开发板只支持2.4G。这种坑对新手来说非常消耗热情。后来换成BLE同样的“手机控制开发板”需求从烧录固件到手机点灯前后不到二十分钟。这不是说WiFi不好而是说BLE的入门门槛确实更低反馈更即时。这篇文章面向的是完全没有嵌入式开发经验、但想用ESP32做点实际东西的人。我会用MicroPython作为开发语言因为它比C语言友好太多不需要编译工具链不需要理解复杂的内存管理写几行代码就能跑。手机端我会用现成的通用BLE调试APP不需要你写安卓或iOS程序。整条链路是电脑写代码、USB烧录进ESP32、手机APP连接、发送指令、ESP32执行动作。你跟着走一遍就能理解BLE通信的基本模型后面想扩展成控制继电器、读取传感器、做蓝牙遥控车都是在这个骨架上加东西。核心关键词我先点一下ESP32、蓝牙BLE、手机APP、MicroPython。这四个词贯穿全文你只要抓住它们之间的关系剩下的都是细节。2. 动手之前的整体设计思路拆解2.1 为什么选MicroPython而不是Arduino或ESP-IDFESP32的开发方式主要有三条路Arduino IDE、ESP-IDF、MicroPython。Arduino生态最成熟库最多但它的编译上传流程对零基础来说还是偏重而且C的语法门槛摆在那里。ESP-IDF是官方框架功能最全但配置项多如牛毛新手光看menuconfig就能劝退。MicroPython的好处是交互性强你可以通过串口终端一行一行地输入命令立刻看到结果这种即时反馈对学习非常重要。另一个实际考虑是代码修改成本。用Arduino改一个引脚号要重新编译、重新上传几十秒就过去了。MicroPython改一个引脚号直接在终端里改完回车就行调试效率完全不是一个量级。对于“快速上手”这个目标来说MicroPython是更合理的选择。当然MicroPython也有代价运行速度比C慢内存占用更高某些底层功能不支持。但做BLE控制这种应用性能完全够用你不用担心。2.2 BLE的通信模型理解GATT才能理解代码BLE的通信模型和经典蓝牙不一样它不是简单的串口透传。BLE的核心概念是GATT全称是Generic Attribute Profile。你可以把它想象成一个数据库ESP32是服务端里面存着若干“服务”每个服务下面有若干“特征值”。手机作为客户端去读或者写这些特征值从而实现数据交换。这个模型一开始听起来抽象但用起来很直观。举个例子你想用手机控制ESP32上的LED灯。你可以在ESP32上定义一个服务服务里放一个特征值这个特征值是可写的。手机往这个特征值里写“1”ESP32收到后就把灯点亮写“0”就熄灭。整个过程就是“手机写特征值ESP32读特征值并执行”。还有一类特征值是可通知的。ESP32可以主动往这个特征值里写数据手机订阅之后就能收到推送。比如你把温度传感器的读数放到通知特征值里手机就能实时显示温度变化。理解了这个模型你看任何BLE代码都不会晕。服务就是一个容器特征值就是数据通道UUID就是每个服务和特征值的身份证号。2.3 手机端方案为什么用通用调试APP而不是自己开发自己写一个手机APP来控制ESP32听起来很酷但对零基础来说完全不现实。安卓开发要装Android Studio学Java或KotliniOS开发要装Xcode学Swift任何一个的学习成本都远超ESP32本身。通用BLE调试APP是更好的起点。这类APP在应用商店里有很多功能大同小异扫描附近的BLE设备、连接、列出所有服务和特征值、读写特征值、订阅通知。你不需要写一行手机端代码就能完成所有调试工作。等你把ESP32端的逻辑跑通了再考虑要不要做专属APP。我常用的几款通用BLE调试工具界面都比较直观连接之后会自动解析GATT结构你点几下就能找到对应的特征值。具体名字我就不提了应用商店搜“BLE调试”就能找到一堆选下载量高、评分好的就行。2.4 整体数据流从手机点击到LED亮起把整条链路串起来是这样的ESP32上电后启动BLE服务广播自己的设备名称。手机打开BLE调试APP扫描到这个名称发起连接。连接建立后APP读取ESP32的服务列表找到我们定义的控制服务再找到里面的可写特征值。用户在APP里输入“1”并发送数据通过BLE协议传到ESP32。ESP32的MicroPython程序在回调函数里收到数据判断内容控制对应引脚的电平。LED亮起用户看到反馈。整个过程涉及的知识点包括BLE广播、GATT服务定义、特征值读写、MicroPython的BLE库API、GPIO控制。听起来多但代码量其实很小核心逻辑不到五十行。3. 环境搭建与核心细节解析3.1 硬件准备一块ESP32开发板就够了你需要的硬件非常简单一块ESP32开发板市面上常见的ESP32-WROOM-32或者ESP32-S3都可以、一根USB数据线注意要是数据线不是纯充电线、一台电脑。开发板上通常自带一颗LED接在某个GPIO上不同板子引脚不一样常见的如GPIO2。如果你不确定可以外接一颗LED到任意空闲GPIO上串联一个220欧姆到1K欧姆的限流电阻。这里有个细节有些ESP32开发板的USB转串口芯片是CH340有些是CP2102。Windows上可能需要装对应的驱动Mac和Linux通常免驱。如果你插上板子后电脑识别不到串口先检查驱动。3.2 固件烧录把MicroPython刷进ESP32ESP32出厂时通常不带MicroPython固件你需要自己刷。步骤不复杂但有几个关键点容易出错。第一步是下载固件。去MicroPython官网的下载页面找到ESP32对应的固件文件通常是一个.bin文件。注意要选对型号ESP32和ESP32-S3的固件不通用。第二步是安装烧录工具。我用的是esptool一个Python命令行工具通过pip就能装。命令是pip install esptool。装完之后用esptool.py chip_id确认电脑能识别到开发板。第三步是擦除闪存。命令是esptool.py erase_flash。这一步很多人会跳过但如果你的板子之前刷过其他固件不擦除可能会导致新固件运行异常。第四步是烧录。命令格式是esptool.py --chip esp32 --port 串口名 write_flash -z 0x1000 固件文件名.bin。串口名在Windows上是COMx在Mac上是/dev/tty.usbserial-xxxx在Linux上是/dev/ttyUSBx。烧录过程中不要拔线等进度条走完。注意烧录时如果一直提示“Connecting...”可以尝试按住开发板上的BOOT键再执行命令进入下载模式后再松开。3.3 串口终端和ESP32对话的窗口固件刷好后你需要一个串口终端工具来和ESP32交互。Windows上可以用PuTTY或者mpremoteMac和Linux上直接用screen或者mpremote。我推荐mpremote它是MicroPython官方工具安装简单功能全。连接命令是mpremote connect 串口名。连上之后你会看到一个提示符这就是MicroPython的REPL环境。你可以直接在这里输入Python代码并立即执行。比如输入import machine然后machine.freq()就能看到当前CPU频率。这个REPL环境是MicroPython最大的优势之一。你不需要每次都写完整文件再上传可以边试边写确认逻辑正确后再保存成文件。3.4 BLE库的选择bluetooth模块的基本用法MicroPython内置了bluetooth模块专门用于BLE开发。这个模块的API设计比较底层但足够用。核心类有两个bluetooth.BLE用于管理BLE适配器bluetooth.UUID用于定义UUID。使用流程大致是先创建BLE实例然后配置GATT服务注册服务后开始广播。当手机连接并写入特征值时MicroPython会触发一个中断回调你在回调里处理数据。这里有个容易混淆的点UUID有16位和128位两种。16位UUID是官方定义的标准UUID比如电池服务、心率服务。128位UUID是自定义的你可以随便编只要不和其他服务冲突就行。做自定义控制的时候用128位UUID更灵活。3.5 手机APP的选择与连接要点手机端我建议用支持GATT完整解析的APP。连接时注意几点先确保ESP32已经广播APP扫描时能看到设备名称连接后如果看不到服务列表可能是APP没有请求服务发现手动刷新一下写入特征值时注意选择正确的写入类型有些APP默认是“Write Without Response”有些是“Write”后者会等待ESP32确认更可靠。还有一个小坑部分安卓手机在系统蓝牙设置里连接了ESP32后BLE调试APP反而连不上。这是因为系统占用了连接。解决方法是先在系统蓝牙设置里取消配对再在APP里连接。4. 完整实操过程与核心代码实现4.1 第一步定义GATT服务结构我们先规划一下要定义的服务和特征值。为了演示清楚我定义两个服务一个是控制服务包含一个可写特征值用于接收指令另一个是状态服务包含一个可通知特征值用于上报状态。UUID我随便编两组128位的只要不重复就行。控制服务的UUID设为6E400001-B5A3-F393-E0A9-E50E24DCCA9E控制特征值设为6E400002-B5A3-F393-E0A9-E50E24DCCA9E。状态服务的UUID设为6E400003-B5A3-F393-E0A9-E50E24DCCA9E状态特征值设为6E400004-B5A3-F393-E0A9-E50E24DCCA9E。这些UUID看起来很长但你可以理解为身份证号只要保证在同一个设备里不重复就行。实际写代码的时候用bluetooth.UUID把它们转成UUID对象。4.2 第二步编写MicroPython BLE服务端代码下面是我实际跑通的代码你可以直接复制到文件里保存为main.py然后上传到ESP32。import bluetooth import machine import time # 定义UUID _CONTROL_SERVICE_UUID bluetooth.UUID(6E400001-B5A3-F393-E0A9-E50E24DCCA9E) _CONTROL_CHAR_UUID bluetooth.UUID(6E400002-B5A3-F393-E0A9-E50E24DCCA9E) _STATUS_SERVICE_UUID bluetooth.UUID(6E400003-B5A3-F393-E0A9-E50E24DCCA9E) _STATUS_CHAR_UUID bluetooth.UUID(6E400004-B5A3-F393-E0A9-E50E24DCCA9E) # 定义LED引脚根据你的板子修改 led machine.Pin(2, machine.Pin.OUT) # 定义特征值 control_char (bluetooth.UUID(_CONTROL_CHAR_UUID), bluetooth.FLAG_WRITE | bluetooth.FLAG_READ) status_char (bluetooth.UUID(_STATUS_CHAR_UUID), bluetooth.FLAG_NOTIFY | bluetooth.FLAG_READ) # 定义服务 control_service (bluetooth.UUID(_CONTROL_SERVICE_UUID), (control_char,)) status_service (bluetooth.UUID(_STATUS_SERVICE_UUID), (status_char,)) # 初始化BLE ble bluetooth.BLE() ble.active(True) # 注册服务 ((control_handle,), (status_handle,)) ble.gatts_register_services((control_service, status_service)) # 存储连接句柄 connections set() # 中断回调函数 def ble_irq(event, data): if event 1: # 连接事件 conn_handle, addr_type, addr data connections.add(conn_handle) print(设备已连接) elif event 2: # 断开事件 conn_handle, addr_type, addr data connections.discard(conn_handle) print(设备已断开) # 断开后重新广播 ble.gap_advertise(100000, adv_dataadv_payload) elif event 3: # 写入事件 conn_handle, attr_handle data if attr_handle control_handle: value ble.gatts_read(control_handle) print(收到指令:, value) if value b1: led.value(1) ble.gatts_notify(conn_handle, status_handle, bLED_ON) elif value b0: led.value(0) ble.gatts_notify(conn_handle, status_handle, bLED_OFF) # 注册中断 ble.irq(ble_irq) # 构造广播数据 name ESP32_BLE_LED adv_payload bytearray() adv_payload.append(0x02) adv_payload.append(0x01) adv_payload.append(0x06) adv_payload.append(len(name) 1) adv_payload.append(0x09) adv_payload.extend(name.encode()) # 开始广播 ble.gap_advertise(100000, adv_dataadv_payload) print(BLE服务已启动等待连接...) # 主循环 while True: time.sleep(1)这段代码的逻辑很清晰先定义UUID和服务结构然后初始化BLE、注册服务、设置中断回调、开始广播。中断回调里处理连接、断开和写入事件。收到“1”就点亮LED并通知手机收到“0”就熄灭并通知。4.3 第三步上传代码并运行把上面的代码保存为main.py用mpremote上传到ESP32。命令是mpremote connect 串口名 fs cp main.py :main.py。上传完成后按一下开发板上的复位键代码就会自动运行。你也可以在REPL里直接粘贴代码测试但保存成文件的好处是断电重启后自动执行。MicroPython默认会运行main.py所以文件名不能错。运行后串口终端会打印“BLE服务已启动等待连接...”。这时候打开手机BLE调试APP扫描设备应该能看到名为“ESP32_BLE_LED”的设备。4.4 第四步手机端操作与验证在APP里连接“ESP32_BLE_LED”后找到控制服务再找到可写特征值。点击写入输入“1”发送。如果一切正常ESP32板上的LED会亮起同时APP会收到“LED_ON”的通知。再写入“0”LED熄灭收到“LED_OFF”。如果LED没反应先检查代码里的引脚号是否和你的板子一致。ESP32-WROOM-32通常GPIO2接LED但有些板子用的是GPIO5或者其他引脚。你可以先用万用表量一下或者查板子的原理图。4.5 参数计算与选择依据这里涉及几个参数需要说明。广播间隔我设的是100000微秒也就是100毫秒。这个值影响手机扫描到设备的速度和功耗。间隔越短扫描越快但功耗越高。100毫秒是一个比较平衡的值手机通常一两秒内就能扫到。特征值的FLAG我用了FLAG_WRITE | FLAG_READ表示这个特征值既可写也可读。如果你只需要写可以去掉READ。状态特征值用了FLAG_NOTIFY | FLAG_READNOTIFY表示支持主动推送。LED限流电阻的计算假设LED正向压降2VESP32 GPIO输出3.3V目标电流5mA那么电阻是(3.3-2)/0.005260欧姆。实际用220欧姆或330欧姆都可以LED亮度差异不大。5. 常见问题与排查技巧实录5.1 手机搜不到ESP32的BLE设备这是最常见的问题。排查顺序如下先确认串口终端有没有打印“BLE服务已启动”如果没有说明代码没跑起来检查是不是文件名不对或者有语法错误。如果打印了但手机搜不到检查手机蓝牙是否开启APP是否获取了蓝牙权限。安卓6.0以上需要位置权限才能扫描BLE设备这个坑很多人踩。还有一个可能是广播数据格式不对。我上面的代码里广播数据是手动拼的如果长度或类型字段写错手机可能解析不了。你可以先用最简单的广播方式测试ble.gap_advertise(100000, adv_dataname)只广播名称看看能不能搜到。5.2 连接后看不到服务列表有些APP连接后需要手动触发服务发现。如果列表是空的找一下APP里有没有“发现服务”或“刷新”按钮。另外确认代码里gatts_register_services的返回值有没有正确解包。我上面的代码用了嵌套解包如果服务数量不对解包会报错。5.3 写入特征值后ESP32没反应先确认写入的特征值句柄是否正确。在APP里看特征值的UUID和代码里的_CONTROL_CHAR_UUID对比。如果UUID对但没反应检查中断回调里的attr_handle判断逻辑。有时候APP写入时会带额外的描述符导致句柄偏移。还有一个可能是写入类型不对。有些APP默认用“Write Without Response”这种写入不会触发ESP32的中断回调。改成“Write”再试。5.4 ESP32反复重启或报内存错误MicroPython在ESP32上的内存比较紧张如果代码里有大数组或者频繁创建对象可能会内存不足。我上面的代码已经比较精简了如果你要加功能注意及时用gc.collect()回收内存。另外广播数据不要超过31字节这是BLE协议的限制。5.5 常见问题速查表问题现象可能原因解决方法手机搜不到设备广播未启动或格式错误检查串口输出简化广播数据连接后无服务服务未注册或APP未发现检查注册返回值手动刷新服务写入无反应句柄错误或写入类型不对核对UUID改用Write类型频繁重启内存不足或代码异常精简代码加gc.collect()LED不亮引脚号错误或硬件问题查原理图外接LED测试实操心得调试BLE的时候串口打印是你的最好朋友。在每个关键节点加print比如连接时、收到数据时、执行动作时。这样一旦出问题你能快速定位是哪一环断了。5.6 进阶避坑断开重连的处理BLE连接断开后ESP32需要重新开始广播否则手机再也搜不到。我上面的代码在断开事件里调用了gap_advertise重新广播。但有个细节重新广播前最好加一个小延时比如100毫秒否则某些手机可能来不及释放连接就搜不到。这个坑我踩过加了延时之后稳定很多。另外如果手机主动断开ESP32的中断回调会收到断开事件。但如果手机直接关蓝牙或者走出范围断开事件可能延迟几秒才触发。这是正常现象不用慌。6. 从点灯扩展到实际项目6.1 控制继电器让ESP32开关电器把LED换成继电器模块就能控制台灯、风扇这些小电器。继电器模块的输入端接ESP32的GPIO输出端接电器。代码逻辑完全一样只是把led.value(1)换成对应GPIO的输出。注意继电器模块通常需要5V供电ESP32的3.3V可能驱动不了要单独供电。6.2 读取传感器把数据推送到手机把状态特征值用起来定期读取温度传感器比如DS18B20的数据通过gatts_notify推送到手机。手机APP订阅通知后就能实时看到温度变化。这个模式可以扩展到湿度、光照、加速度等各种传感器。6.3 蓝牙遥控小车双轮差速控制如果你在做蓝牙遥控小车BLE的控制特征值可以接收方向指令比如“F”前进、“B”后退、“L”左转、“R”右转。ESP32解析指令后控制两个电机的PWM输出。这里的关键是PWM频率和占空比的调节频率一般用1kHz到5kHz占空比决定速度。6.4 低功耗优化让电池撑更久如果项目用电池供电BLE的广播间隔可以调大比如500毫秒甚至1秒。连接间隔也可以协商让手机和ESP32约定一个较长的通信间隔。另外在不通信的时候可以让ESP32进入轻睡眠模式需要时再唤醒。这些优化能把功耗降到毫安级别。6.5 固件OTA不插线更新代码ESP32支持OTA升级你可以通过BLE或者WiFi把新的main.py传到开发板上。BLE OTA的速度比较慢适合小文件。WiFi OTA更快但需要先连上网络。对于已经装在壳子里的项目OTA非常实用不用拆开插线。7. 我在实际操作中积累的几个关键体会第一个体会是BLE的UUID不用记但一定要统一管理。我一开始随便编UUID后来服务多了自己都搞混。建议在代码开头用常量定义所有UUID后面引用常量改起来方便。第二个体会是手机APP的兼容性差异很大。同一个ESP32有的APP能正常读写有的APP连服务列表都刷不出来。遇到这种情况不要怀疑自己的代码换一个APP试试。我一般备两三个不同的调试工具交叉验证。第三个体会是MicroPython的BLE模块在不同版本间有API变化。如果你看的教程和我的代码有出入先确认固件版本。用import bluetooth; print(bluetooth.__version__)可以查看版本号。老版本的API可能不支持某些FLAG或者参数格式不同。第四个体会是调试BLE的时候串口终端的print输出比任何调试工具都管用。因为BLE是无线通信你没法用逻辑分析仪抓包只能靠ESP32端的日志来判断数据有没有收到、处理逻辑有没有执行。所以养成加print的习惯能省很多时间。第五个体会是不要一上来就做复杂功能。先把“手机发1LED亮”这个最小闭环跑通再逐步加传感器、加通知、加多服务。每加一个功能就测试一次确保没有引入新问题。我见过太多人一开始就写几百行代码结果一个地方出错整条链路都跑不起来排查起来非常痛苦。最后分享一个小技巧如果你在REPL里调试BLE代码每次修改后需要重新初始化BLE。但BLE实例不能重复创建需要先ble.active(False)再重新创建。或者直接按复位键重启更省事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑