蓝牙通信¶
ESP32 同时支持经典蓝牙(Classic Bluetooth)和低功耗蓝牙(BLE 4.2),可以与手机、电脑和其他蓝牙设备进行数据交换。
一、蓝牙类型对比¶
| 特性 | 经典蓝牙(Classic BT) | 低功耗蓝牙(BLE) |
|---|---|---|
| 传输速率 | 最高 3 Mbps | 最高 1 Mbps |
| 功耗 | 较高 | 极低 |
| 连接方式 | 点对点,持续连接 | 可广播、扫描、连接 |
| 适用场景 | 音频传输、大量数据传输 | 传感器数据、可穿戴、IoT |
| 配对 | 需要配对 | 可选配对 |
| 手机兼容性 | iOS 不支持 SPP | iOS + Android 都支持 |
| ESP32 API | BluetoothSerial |
BLEDevice |
graph TB
BT[ESP32 蓝牙] --> C[经典蓝牙<br>Classic BT]
BT --> B[低功耗蓝牙<br>BLE]
C --> C1[SPP 串口透传]
C --> C2[A2DP 音频]
B --> B1[GATT Server<br>ESP32 作为外围设备]
B --> B2[GATT Client<br>ESP32 连接其他 BLE 设备]
B --> B3[广播 Beacon]
选择建议
- 与 Android 手机做串口通信 → 经典蓝牙 SPP(最简单)
- 与 iOS 手机通信 → 必须用 BLE(iOS 不支持 SPP)
- 低功耗传感器 → BLE
- 音频传输 → 经典蓝牙 A2DP
二、经典蓝牙 SPP(串口透传)¶
SPP(Serial Port Profile)让蓝牙就像一个无线串口一样使用,非常适合调试和简单数据传输。
基本使用¶
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32-Robot"); // 蓝牙设备名称
Serial.println("蓝牙已启动,等待连接...");
}
void loop() {
// USB 串口 → 蓝牙
if (Serial.available()) {
SerialBT.write(Serial.read());
}
// 蓝牙 → USB 串口
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
delay(10);
}
Android APP
推荐使用 Serial Bluetooth Terminal(Google Play 搜索),连接 ESP32 后即可收发数据。
蓝牙遥控小车¶
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
const int MOTOR_L1 = 16, MOTOR_L2 = 17; // 左电机
const int MOTOR_R1 = 18, MOTOR_R2 = 19; // 右电机
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32-Car");
pinMode(MOTOR_L1, OUTPUT); pinMode(MOTOR_L2, OUTPUT);
pinMode(MOTOR_R1, OUTPUT); pinMode(MOTOR_R2, OUTPUT);
stopMotors();
}
void loop() {
if (SerialBT.available()) {
char cmd = SerialBT.read();
switch (cmd) {
case 'F': forward(); break;
case 'B': backward(); break;
case 'L': turnLeft(); break;
case 'R': turnRight();break;
case 'S': stopMotors(); break;
}
SerialBT.printf("执行命令: %c\n", cmd);
}
}
void forward() {
digitalWrite(MOTOR_L1, HIGH); digitalWrite(MOTOR_L2, LOW);
digitalWrite(MOTOR_R1, HIGH); digitalWrite(MOTOR_R2, LOW);
}
void backward() {
digitalWrite(MOTOR_L1, LOW); digitalWrite(MOTOR_L2, HIGH);
digitalWrite(MOTOR_R1, LOW); digitalWrite(MOTOR_R2, HIGH);
}
void turnLeft() {
digitalWrite(MOTOR_L1, LOW); digitalWrite(MOTOR_L2, HIGH);
digitalWrite(MOTOR_R1, HIGH); digitalWrite(MOTOR_R2, LOW);
}
void turnRight() {
digitalWrite(MOTOR_L1, HIGH); digitalWrite(MOTOR_L2, LOW);
digitalWrite(MOTOR_R1, LOW); digitalWrite(MOTOR_R2, HIGH);
}
void stopMotors() {
digitalWrite(MOTOR_L1, LOW); digitalWrite(MOTOR_L2, LOW);
digitalWrite(MOTOR_R1, LOW); digitalWrite(MOTOR_R2, LOW);
}
蓝牙事件回调¶
void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
if (event == ESP_SPP_SRV_OPEN_EVT) {
Serial.println("蓝牙客户端已连接");
}
if (event == ESP_SPP_CLOSE_EVT) {
Serial.println("蓝牙客户端已断开");
}
}
void setup() {
SerialBT.register_callback(btCallback);
SerialBT.begin("ESP32-Device");
}
三、BLE 基础概念¶
GATT 架构¶
BLE 通信基于 GATT(Generic Attribute Profile) 协议:
graph TB
S[BLE Server<br>ESP32] --> SV1[Service 1<br>环境传感器<br>UUID: 0x181A]
S --> SV2[Service 2<br>LED 控制<br>UUID: 自定义]
SV1 --> C1[Characteristic<br>温度<br>UUID: 0x2A6E<br>Read + Notify]
SV1 --> C2[Characteristic<br>湿度<br>UUID: 0x2A6F<br>Read + Notify]
SV2 --> C3[Characteristic<br>LED 状态<br>UUID: 自定义<br>Read + Write]
| 概念 | 说明 | 类比 |
|---|---|---|
| Server | 提供数据的设备 | 服务器 |
| Client | 请求数据的设备 | 客户端(手机) |
| Service | 一组相关功能的集合 | 数据库中的表 |
| Characteristic | 具体的数据点 | 表中的字段 |
| UUID | 唯一标识符 | ID |
| Descriptor | 特征值的元信息 | 字段说明 |
Characteristic 属性¶
| 属性 | 说明 |
|---|---|
| Read | 客户端可以读取数据 |
| Write | 客户端可以写入数据 |
| Notify | 服务端主动推送数据(不需要确认) |
| Indicate | 服务端主动推送数据(需要确认) |
四、BLE Server(ESP32 作为服务端)¶
创建 BLE 服务端¶
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// 自定义 UUID(使用在线生成器生成)
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHAR_TEMP_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHAR_LED_UUID "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
BLEServer* pServer = NULL;
BLECharacteristic* pTempChar = NULL;
BLECharacteristic* pLedChar = NULL;
bool deviceConnected = false;
// 连接回调
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("BLE 客户端已连接");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("BLE 客户端已断开");
pServer->startAdvertising(); // 重新开始广播
}
};
// 写入回调
class LedCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pCharacteristic) {
String value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.printf("收到 LED 指令: %s\n", value.c_str());
if (value == "1" || value == "on") {
digitalWrite(2, HIGH);
} else {
digitalWrite(2, LOW);
}
}
}
};
void setup() {
Serial.begin(115200);
pinMode(2, OUTPUT);
// 初始化 BLE
BLEDevice::init("ESP32-BLE-Sensor");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// 创建 Service
BLEService* pService = pServer->createService(SERVICE_UUID);
// 创建温度 Characteristic(Read + Notify)
pTempChar = pService->createCharacteristic(
CHAR_TEMP_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
);
pTempChar->addDescriptor(new BLE2902()); // 启用 Notify
// 创建 LED Characteristic(Read + Write)
pLedChar = pService->createCharacteristic(
CHAR_LED_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
);
pLedChar->setCallbacks(new LedCallbacks());
// 启动服务和广播
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
Serial.println("BLE 服务端已启动,等待连接...");
}
void loop() {
if (deviceConnected) {
// 定期发送温度数据
float temp = 25.0 + random(0, 100) / 10.0;
char tempStr[8];
snprintf(tempStr, sizeof(tempStr), "%.1f", temp);
pTempChar->setValue(tempStr);
pTempChar->notify(); // 推送通知
Serial.printf("发送温度: %s°C\n", tempStr);
}
delay(2000);
}
手机端连接¶
推荐 APP
- nRF Connect(Nordic 官方,功能最全)
- LightBlue(iOS 推荐)
- BLE Scanner
连接后:
- 找到 ESP32-BLE-Sensor → 连接
- 展开 Service → 找到 Characteristic
- 读取温度值(按 Read 按钮)
- 订阅通知(按 Notify 按钮,自动接收推送)
- 写入 LED 控制(按 Write,输入 "1" 或 "0")
五、BLE Client(ESP32 作为客户端)¶
ESP32 也可以主动连接其他 BLE 设备(如小米手环、BLE 传感器节点):
#include <BLEDevice.h>
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static BLEAdvertisedDevice* targetDevice;
static bool doConnect = false;
static bool connected = false;
static BLERemoteCharacteristic* pRemoteChar;
// 扫描回调
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (advertisedDevice.haveServiceUUID() &&
advertisedDevice.isAdvertisingService(serviceUUID)) {
Serial.printf("找到目标设备: %s\n",
advertisedDevice.toString().c_str());
targetDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
BLEDevice::getScan()->stop();
}
}
};
// Notify 回调
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteChar,
uint8_t* pData, size_t length, bool isNotify) {
Serial.printf("收到通知: %.*s\n", length, (char*)pData);
}
bool connectToServer() {
BLEClient* pClient = BLEDevice::createClient();
pClient->connect(targetDevice);
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) return false;
pRemoteChar = pRemoteService->getCharacteristic(charUUID);
if (pRemoteChar == nullptr) return false;
if (pRemoteChar->canNotify()) {
pRemoteChar->registerForNotify(notifyCallback);
}
connected = true;
return true;
}
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32-Client");
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(10); // 扫描 10 秒
}
void loop() {
if (doConnect) {
if (connectToServer()) {
Serial.println("连接成功!");
}
doConnect = false;
}
if (connected && pRemoteChar->canRead()) {
String value = pRemoteChar->readValue();
Serial.printf("读取值: %s\n", value.c_str());
}
delay(2000);
}
六、BLE Beacon(广播信标)¶
BLE Beacon 不需要建立连接,只是周期性广播数据,适合室内定位、信息推送等场景:
#include <BLEDevice.h>
#include <BLEBeacon.h>
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32-Beacon");
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
BLEBeacon beacon;
beacon.setManufacturerId(0x4C00); // Apple iBeacon
beacon.setProximityUUID(BLEUUID("12345678-1234-1234-1234-123456789ABC"));
beacon.setMajor(1);
beacon.setMinor(100);
beacon.setSignalPower(-59);
BLEAdvertisementData advData;
advData.setFlags(0x06);
std::string strServiceData = "";
strServiceData += (char)26; // 长度
strServiceData += (char)0xFF; // 厂商自定义
strServiceData += beacon.getData();
advData.addData(strServiceData);
pAdvertising->setAdvertisementData(advData);
pAdvertising->start();
Serial.println("iBeacon 广播已启动");
}
void loop() {
delay(1000);
}
七、蓝牙与 WiFi 共存¶
ESP32 的 WiFi 和蓝牙共用同一个 2.4GHz 射频模块,需要注意:
共存注意事项
- WiFi + BLE 可以共存,但性能会互相影响
- WiFi + 经典蓝牙共存时,吞吐量明显下降
- 蓝牙和 WiFi 通过==时分复用==共享天线
- 高负载 WiFi 通信时,BLE 的延迟会增加
- 内存占用:WiFi ≈ 80KB,BLE ≈ 60KB,同时开启需要更多内存
// WiFi + BLE 同时使用
#include <WiFi.h>
#include <BLEDevice.h>
void setup() {
// 先初始化 WiFi
WiFi.begin("SSID", "password");
while (WiFi.status() != WL_CONNECTED) delay(500);
// 再初始化 BLE
BLEDevice::init("ESP32-Dual");
// ... 创建 BLE 服务 ...
}
八、常用函数速查¶
BluetoothSerial(经典蓝牙)¶
| 函数 | 功能 |
|---|---|
SerialBT.begin(name) |
启动蓝牙,设置设备名 |
SerialBT.available() |
可读字节数 |
SerialBT.read() |
读取一字节 |
SerialBT.write(data) |
写入数据 |
SerialBT.print(data) |
格式化输出 |
SerialBT.connected() |
是否已连接 |
SerialBT.end() |
关闭蓝牙 |
BLE¶
| 函数 | 功能 |
|---|---|
BLEDevice::init(name) |
初始化 BLE |
createServer() |
创建 Server |
createService(uuid) |
创建 Service |
createCharacteristic(uuid, props) |
创建 Characteristic |
setValue(value) |
设置特征值 |
getValue() |
获取特征值 |
notify() |
发送通知 |
startAdvertising() |
开始广播 |
九、常见问题¶
经典蓝牙和 BLE 怎么选?
| 场景 | 选择 |
|---|---|
| 与 Android 手机简单通信 | 经典蓝牙 SPP |
| 与 iOS 手机通信 | BLE(唯一选择) |
| 低功耗传感器节点 | BLE |
| 大量数据持续传输 | 经典蓝牙 |
| 室内定位信标 | BLE Beacon |
| 同时兼容 Android + iOS | BLE |
BLE 最多能传多少数据?
- 单次通知/读写最大 MTU 默认 23 字节(20 字节有效载荷)
- 可以协商更大 MTU(最大 512 字节):
pServer->updatePeerMTU(pServer->getConnId(), 512) - 大量数据需要分包传输
手机搜不到 ESP32 蓝牙怎么办?
- 经典蓝牙:在手机蓝牙设置中搜索,确认 ESP32 代码中
begin()已调用 - BLE:普通蓝牙搜索看不到 BLE 设备,需要使用 nRF Connect 等 BLE 扫描 APP
- 确认 ESP32 已开始广播(
startAdvertising()) - 某些 Android 版本需要打开==位置权限==才能扫描 BLE
ESP32-C3 / ESP32-S2 支持蓝牙吗?
- ESP32-S2:不支持任何蓝牙
- ESP32-C3:仅支持 BLE 5.0
- ESP32-S3:仅支持 BLE 5.0
- 只有原版 ESP32 同时支持经典蓝牙和 BLE