先分清两种蓝牙
ESP32 的 2.4 GHz 射频同时支持 WiFi 和蓝牙,但蓝牙分两套体系,用途和代价完全不同:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 经典蓝牙(BR/EDR) | 持续连接、可传音频(A2DP)、功耗高、体积大 | 蓝牙音箱、串口透传(SPP) |
| BLE(低功耗蓝牙) | 短包广播与连接、微安级休眠功耗、不能传音频 | 传感器、手环、信标、设备配网 |
重要提醒:ESP32-S3 与 ESP32-C3 不支持经典蓝牙,只支持 BLE。新项目一律建议走 BLE,生态和工具链都更现代。本页也只讲 BLE。
BLE 的核心概念
- 广播(Advertising):BLE 设备周期性发出小数据包,广播设备名、MAC 地址、服务 UUID。扫描方无需连接就能收到,这是被动扫描的基础
- 服务(Service)与特征值(Characteristic):连接后设备以「服务→特征值」的层级暴露数据,每个都有唯一 UUID,特征值可设读、写、通知三种属性
- 角色:广播数据的叫服务端(Server/Peripheral),扫描并连接的叫客户端(Client/Central)
- RSSI:接收信号强度,单位 dBm,数值越接近 0 越近。-50 左右通常在 2 米内,-80 以下基本隔着墙
Arduino-ESP32 核心自带 BLE 库(BLEDevice.h),无需额外安装。对内存敏感的项目可换社区公认的 NimBLE-Arduino 库,接口相近、Flash 占用小一半以上。
实战一:扫描周边 BLE 设备
把 ESP32 变成一个 BLE 扫描器,列出附近所有广播中的设备:
#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
int scanTime = 5; // 每轮扫描 5 秒
BLEScan* pBLEScan;
class MyCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice dev) {
Serial.print("发现设备:");
Serial.print(dev.getAddress().toString().c_str());
Serial.printf(" RSSI: %d dBm", dev.getRSSI());
if (dev.haveName()) {
Serial.print(" 名称: ");
Serial.print(dev.getName().c_str());
}
Serial.println();
}
};
void setup() {
Serial.begin(115200);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyCallbacks());
pBLEScan->setActiveScan(true); // 主动扫描,能多拿到一些数据
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // 须小于等于 setInterval
}
void loop() {
BLEScanResults results = pBLEScan->start(scanTime, false);
Serial.printf("共发现 %d 台设备\n\n", results.getCount());
pBLEScan->clearResults(); // 释放内存,务必调用
delay(3000);
}
烧录后打开串口监视器,手机、手环、耳机都会现形。输出 RSSI 值可以粗略判断距离,这就是蓝牙信标、人员计数、防丢器等项目的原理。
实战二:最小 BLE 服务端
让 ESP32 对外广播一个服务,手机用 nRF Connect 等 BLE 调试 App 即可连接读取:
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHAR_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32-BLE"); // 广播名
BLEServer* pServer = BLEDevice::createServer();
BLEService* pService = pServer->createService(SERVICE_UUID);
BLECharacteristic* pChar = pService->createCharacteristic(
CHAR_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pChar->setValue("Hello BLE");
pService->start();
BLEAdvertising* pAdv = pServer->getAdvertising();
pAdv->start();
Serial.println("开始广播,可用手机 BLE App 连接");
}
void loop() {
// 实际项目中在此更新 pChar->setValue() 上报传感器读数
}
把 setValue() 换成温度、湿度等实时读数,就构成了一个 BLE 传感节点的雏形;把属性加上 PROPERTY_NOTIFY,客户端还能订阅推送。
内存与功耗提示
- 官方 BLE 库连同程序约占 1.2 MB Flash,选板时留意分区方案(默认方案够用);NimBLE-Arduino 可省下约一半
- BLE 与 WiFi 共用射频,同时开启时 WiFi 吞吐会明显下降,属正常现象
- 电池项目用
NimBLEDevice::setPower()控制发射功率,广播间隔拉长到 1 秒以上可显著省电
常见坑速查
- 扫不到任何设备:确认是 BLE 而非经典蓝牙设备(老式蓝牙耳机扫不到是正常的);手机蓝牙已关闭
- 编译提示 BLE 库找不到:用的是 ESP32-S3/C3 且例程名带 Classic/SerialBT,经典蓝牙例程在新芯片上不可用
- 连接很快断开:BLE 连接参数不匹配或服务端未处理 onDisconnect,参考官方 BLE_client 例程的回调写法
- 内存告警或崩溃:扫描结果没有
clearResults(),或改用 NimBLE-Arduino