☰
ESP32蓝牙BLE入门:MicroPython从零搭建到手机APP控制
2026/10/2 11:22:37 网站建设 项目流程

BLE 这玩意儿,刚上手的时候我也觉得挺玄乎。手机一搜就能连上,发几个字节过去,灯就亮了——看起来简单,但真到自己写代码的时候,各种 UUID、特征值、通知、写入权限,一堆名词砸过来,很容易懵。这篇内容就是把我自己从零折腾 ESP32 蓝牙 BLE 的过程完整梳理一遍,从环境搭建到手机 APP 控制,每一步都讲清楚为什么这么做、坑在哪里。不管你是刚买了一块 ESP32 开发板的新手,还是从 Arduino 转过来想试试 MicroPython 的老玩家,跟着走一遍,基本都能跑通。

1. 先搞清楚 BLE 到底是怎么回事

1.1 蓝牙经典 vs BLE:别被名字骗了

很多人第一次接触 ESP32 蓝牙,以为蓝牙就是蓝牙,连上就能传数据。实际上蓝牙协议分两大流派:经典蓝牙(Bluetooth Classic)和低功耗蓝牙(BLE)。它们虽然都叫蓝牙,但底层协议完全不同,互相之间不能直接通信。

经典蓝牙就是我们以前用来连蓝牙耳机、蓝牙音箱的那种,特点是持续连接、传输速率高,但功耗也大。BLE 是后来为了物联网场景设计的,特点是连接建立快、功耗极低,适合传感器数据采集、遥控指令这类小数据量场景。

ESP32 同时支持这两种模式,但你不能在一个应用里同时用经典蓝牙和 BLE 做同一件事。对于手机 APP 控制 ESP32 这个场景,BLE 是更合适的选择——手机端不需要配对码,连接速度快,而且 iOS 和 Android 对 BLE 的支持都很成熟。

注意:ESP32-S3 只支持 BLE,不支持经典蓝牙。如果你买的是 S3 系列,就不用纠结选哪个了。

1.2 GATT 协议:BLE 通信的数据组织方式

BLE 通信的核心是GATT(Generic Attribute Profile)。你可以把它想象成一个数据库,里面有很多张表,每张表有行有列。具体来说:

  • Service(服务):一个服务就是一组相关功能的集合。比如一个"设备信息服务"里可能包含设备名称、厂商名称、固件版本等。
  • Characteristic(特征值):服务下面的具体数据项。每个特征值有唯一的 UUID、有值、有权限(读、写、通知等)。
  • Descriptor(描述符):特征值的附加信息,比如单位、格式等。

手机 APP 和 ESP32 之间的通信,本质上就是手机去读写 ESP32 上某个特征值的过程。ESP32 把数据放在特征值里,手机来读;或者手机往特征值里写数据,ESP32 收到后执行相应操作。

1.3 为什么选 Nordic UART Service

BLE 的 UUID 分两种:16 位短 UUID 和 128 位长 UUID。16 位 UUID 是蓝牙技术联盟分配给标准服务的,比如电池服务、心率服务等。但如果你要自定义服务,就得用 128 位 UUID。

Nordic UART Service(NUS)是 Nordic 公司定义的一套自定义服务,虽然不是官方标准,但在创客圈子里用得非常多,几乎成了事实标准。它包含两个特征值:

  • RX 特征值:手机往这里写数据,ESP32 接收
  • TX 特征值:ESP32 往这里发数据,手机接收通知

用 NUS 的好处是,很多现成的手机 BLE 调试 APP 都内置了对它的支持,你不需要自己写手机端代码就能快速测试。后面我会推荐几个好用的 APP。

2. 开发环境搭建:MicroPython 还是 Arduino

2.1 两条路线的选择逻辑

ESP32 开发主要有两条路线:Arduino 框架和MicroPython。两者各有优劣,选哪个取决于你的背景和需求。

Arduino 框架用 C++ 编写,性能好、资源占用低,适合对实时性要求高的场景。但代码相对繁琐,编译烧录周期长。MicroPython 用 Python 语法,开发速度快,交互式 REPL 可以即时调试,特别适合快速原型验证。

对于"零基础学 ESP32 蓝牙"这个目标,我强烈建议从 MicroPython 入手。原因很简单:你不需要理解编译工具链、不需要处理复杂的 C++ 语法,写几行 Python 代码就能看到效果。等把 BLE 的通信逻辑摸清楚了,再转 Arduino 也不迟。

2.2 MicroPython 固件烧录步骤

如果你拿到的是全新 ESP32 开发板,里面通常没有 MicroPython 固件,需要先烧录。步骤如下:

第一步:安装 esptool

pip install esptool

第二步:下载固件

去 MicroPython 官网下载对应你芯片型号的固件文件(.bin 格式)。ESP32 和 ESP32-S3 的固件不一样,别下错了。

第三步:擦除 Flash

esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash

Windows 上端口号可能是 COM3、COM4 之类的,在设备管理器里能看到。

第四步:烧录固件

esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20230426-v1.20.0.bin

烧录完成后,用串口工具连接开发板,按一下复位键,如果看到>>>提示符,说明 MicroPython 已经跑起来了。

提示:烧录时如果一直失败,检查 USB 线是不是只能充电不能传数据的那种。这个坑我踩过,换了三根线才发现是线的问题。

2.3 Thonny IDE 配置

写 MicroPython 代码,我推荐用Thonny。它轻量、免费、自带串口终端,对新手非常友好。

安装好 Thonny 后,在"工具"→"选项"→"解释器"里选择"MicroPython (ESP32)",端口选你开发板对应的串口。配置好后,点"确定",如果底部 Shell 窗口出现>>>,就说明连接成功了。

Thonny 的好处是你可以直接在 Shell 里敲代码测试,不用每次都保存文件再运行。比如你想测试一个引脚电平,直接敲from machine import Pin然后操作就行,即时看到结果。

3. 写代码:让 ESP32 变成一个 BLE 设备

3.1 最小可运行 BLE 代码

先来看一段最精简的 BLE 广播代码,让 ESP32 能被手机搜到:

import bluetooth from ble_advertising import advertising_payload ble = bluetooth.BLE() ble.active(True) name = "ESP32_BLE" payload = advertising_payload(name=name) ble.gap_advertise(100000, adv_data=payload) print("BLE 已启动,设备名:", name)

这段代码做了三件事:初始化 BLE 模块、构建广播数据、开始广播。gap_advertise的第一个参数是广播间隔,单位是微秒,100000 就是 100 毫秒。间隔越短,手机搜到的速度越快,但功耗也越高。

ble_advertising这个模块不是 MicroPython 内置的,需要你自己创建一个ble_advertising.py文件。这个文件在 MicroPython 官方示例里能找到,主要功能是把设备名、UUID 等信息打包成 BLE 广播要求的二进制格式。

3.2 注册 NUS 服务

光能广播还不够,手机连上之后要能读写数据,就需要注册服务。下面是注册 Nordic UART Service 的代码:

from micropython import const import struct _IRQ_CENTRAL_CONNECT = const(1) _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_GATTS_WRITE = const(3) _FLAG_READ = const(0x0002) _FLAG_WRITE_NO_RESPONSE = const(0x0004) _FLAG_WRITE = const(0x0008) _FLAG_NOTIFY = const(0x0010) _UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") _UART_TX = ( bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_READ | _FLAG_NOTIFY, ) _UART_RX = ( bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE, ) _UART_SERVICE = (_UART_UUID, (_UART_TX, _UART_RX))

这里定义了三个 UUID:服务本身的 UUID、TX 特征值 UUID、RX 特征值 UUID。注意 TX 和 RX 的权限设置:TX 是READ | NOTIFY,因为 ESP32 要通过它主动通知手机;RX 是WRITE | WRITE_NO_RESPONSE,因为手机要往这里写数据。

3.3 处理连接和断开事件

BLE 是事件驱动的,你需要注册一个中断处理函数来响应连接、断开、写入等事件:

def _irq(self, event, data): if event == _IRQ_CENTRAL_CONNECT: conn_handle, _, _ = data self._connections.add(conn_handle) print("手机已连接") elif event == _IRQ_CENTRAL_DISCONNECT: conn_handle, _, _ = data self._connections.discard(conn_handle) print("手机已断开") self._advertise() elif event == _IRQ_GATTS_WRITE: conn_handle, attr_handle = data data = self._ble.gatts_read(attr_handle) print("收到数据:", data)

连接事件里记录连接句柄,断开事件里移除句柄并重新开始广播(否则手机断开后就搜不到设备了),写入事件里读取手机发来的数据。

3.4 完整代码整合

把上面的片段整合成一个完整的类,方便复用:

import bluetooth import struct from micropython import const from ble_advertising import advertising_payload class BLEUART: def __init__(self, ble, name="ESP32_BLE"): self._ble = ble self._ble.active(True) self._ble.irq(self._irq) ((self._tx_handle, self._rx_handle),) = self._ble.gatts_register_services((_UART_SERVICE,)) self._connections = set() self._payload = advertising_payload(name=name) self._advertise() def _irq(self, event, data): # 事件处理逻辑 pass def _advertise(self): self._ble.gap_advertise(100000, adv_data=self._payload) def send(self, data): for conn_handle in self._connections: self._ble.gatts_notify(conn_handle, self._tx_handle, data)

这个类封装了初始化、广播、发送数据的功能。实例化之后,调用send()方法就能往手机发数据。

4. 手机端 APP 选择与调试

4.1 推荐几款好用的 BLE 调试 APP

手机端不需要写代码,用现成的 BLE 调试工具就行。以下是我实测过的几款:

APP 名称平台特点
nRF ConnectiOS/AndroidNordic 官方出品,功能最全,支持 NUS
LightBlueiOS/Android界面简洁,适合快速测试
BLE DebuggerAndroid轻量,启动快
Serial Bluetooth TerminalAndroid支持自定义 UUID,适合串口透传

nRF Connect 是我最常用的,它能自动识别 Nordic UART Service,直接给你一个类似串口终端的界面,输入文字点发送就能传到 ESP32。

4.2 连接与数据收发测试

打开 nRF Connect,扫描设备,找到你设置的设备名(比如 ESP32_BLE),点击连接。连接成功后,展开服务列表,找到 Nordic UART Service,你会看到两个特征值:

  • 带"向上箭头"图标的 TX 特征值:点击旁边的三个点,选择"Enable notifications",这样 ESP32 发来的数据就能显示出来。
  • 带"向下箭头"图标的 RX 特征值:点击旁边的"向上箭头"图标,可以输入要发送的数据。

在 RX 特征值里输入 "hello",点发送。如果 ESP32 的串口终端打印出 "收到数据:b'hello'",说明通信链路已经打通了。

4.3 常见连接问题排查

手机搜不到设备?先确认 ESP32 的串口有没有报错,广播是否正常启动。如果代码跑起来了但搜不到,可能是广播间隔太长,改成 50000 试试。

连上了但发数据没反应?检查 RX 特征值的权限是否包含 WRITE,以及中断处理函数里是否正确读取了数据。

iOS 上搜不到?iOS 对 BLE 广播数据格式要求比较严格,确保advertising_payload函数正确打包了数据。另外 iOS 会缓存设备信息,如果改了设备名,可能需要在手机设置里忽略旧设备再重新搜索。

5. 实战:用手机控制 LED 亮灭

5.1 硬件连接

拿一个 LED 和一个电阻,LED 长脚接 ESP32 的 GPIO2,短脚经过电阻接 GND。GPIO2 是 ESP32 开发板上通常自带 LED 的引脚,如果你用的是自带 LED 的板子,这一步可以省略。

5.2 代码实现

在之前 BLEUART 类的基础上,加入 LED 控制逻辑:

from machine import Pin led = Pin(2, Pin.OUT) def _irq(self, event, data): if event == _IRQ_GATTS_WRITE: conn_handle, attr_handle = data cmd = self._ble.gatts_read(attr_handle).decode().strip() if cmd == "on": led.value(1) self.send("LED 已打开") elif cmd == "off": led.value(0) self.send("LED 已关闭") else: self.send("未知命令:" + cmd)

手机端在 RX 特征值里发送 "on",LED 点亮,同时手机收到 "LED 已打开" 的通知。发送 "off",LED 熄灭。

5.3 扩展思路

这个框架可以扩展到很多场景:把 LED 换成继电器,就能控制家电;换成电机驱动,就能做小车;读取传感器数据后通过 TX 特征值发给手机,就是一个无线传感器节点。

如果你想做更复杂的交互,可以定义一套简单的协议,比如用 JSON 格式传输数据:{"cmd":"led","value":1}。ESP32 端解析 JSON,手机端也按同样格式发送。这样扩展性更好,加新功能不用改通信层。

6. 踩过的坑和实用技巧

6.1 广播数据长度限制

BLE 广播数据最大 31 字节,超过就会报错。设备名太长、UUID 太多都可能导致超限。解决办法是把不必要的信息放到扫描响应里,或者缩短设备名。

6.2 连接间隔与功耗的平衡

BLE 连接建立后,手机和 ESP32 会协商一个连接间隔。间隔越短,响应越快,但功耗越高。对于 LED 控制这种场景,间隔设成 100ms 左右就够了。如果做电池供电的项目,可以适当加大间隔。

6.3 数据粘包问题

BLE 的写入操作不保证每次写入的数据边界和你的预期一致。如果手机快速连续发送多条命令,ESP32 可能一次收到多条拼接在一起的数据。解决办法是在协议里加分隔符,比如每条命令以\n结尾,ESP32 端按行解析。

6.4 断开后自动重连

手机离开范围后 BLE 会断开,ESP32 需要重新开始广播,否则手机回来搜不到设备。在断开事件里调用_advertise()是必须的,这个坑我一开始没注意,调试了半天以为是手机的问题。

6.5 MicroPython 内存限制

ESP32 的 MicroPython 可用内存有限,如果代码里大量使用字符串拼接、频繁创建大对象,可能会触发内存不足错误。建议把固定字符串定义为常量,避免在中断处理函数里做复杂操作。

7. 从 BLE 控制到完整项目

7.1 加入传感器数据上报

把 LED 控制扩展成传感器数据上报,只需要在 TX 特征值上定期发送数据。比如接一个 DHT11 温湿度传感器,每隔几秒读取一次,通过send()方法发给手机。手机端 nRF Connect 开启通知后就能看到实时数据。

7.2 多服务注册

一个 BLE 设备可以注册多个服务。比如你既要做 UART 透传,又要做 OTA 固件升级,可以分别注册两个服务。gatts_register_services接受一个服务列表,返回的句柄也是按顺序排列的。

7.3 与 WiFi 共存

ESP32 的蓝牙和 WiFi 共用射频模块,同时开启时会有一定的性能影响。如果项目里既要 BLE 配置又要 WiFi 联网,建议分时使用:BLE 只在配网阶段开启,配网完成后关闭 BLE,启动 WiFi。

7.4 低功耗优化

如果项目用电池供电,BLE 的功耗优化很关键。主要手段包括:增大广播间隔、增大连接间隔、减少通知频率、在空闲时进入 light sleep 模式。实测下来,优化得当的话,一颗 18650 电池可以撑好几周。

我在实际项目里最深的体会是,BLE 调试一定要有耐心。手机端和 ESP32 端的日志都要看,很多时候问题出在你想不到的地方——比如手机 APP 缓存了旧的 GATT 表,或者广播数据格式不对导致 iOS 直接忽略。多试几款 APP,多打日志,问题总能定位到。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询