ESP32——蓝牙 BLE_编程教程:Arduino 与 ESP-IDF 双环境指南

ESP32——蓝牙 BLE_编程教程:Arduino 与 ESP-IDF 双环境指南

蓝牙低功耗(BLE / Bluetooth Low Energy)是 IoT 设备与手机互联的首选协议。ESP32 内置 蓝牙 4.2 + 低功耗蓝牙,支持 BLE 外设(Peripheral)、BLE 中央(Central) 和 Beacon 三种角色。无论是制作智能手表、心率监测器、BLE 网关,还是扫码配网,BLE 都是核心技能。本文从 GATT 原理到实战,覆盖 Arduino 和 ESP-IDF 双环境。


目录

  1. BLE 协议基础

  2. ESP32 BLE 硬件

  3. 环境准备与验证

  4. BLE 外设模式(被手机连接)

  5. BLE 中央模式(连接传感器)

  6. BLE Beacon 广播

  7. BLE 数据收发实战

  8. BLE 通知与订阅

  9. BLE + 微信小程序

  10. BLE 低功耗优化

  11. BLE 安全问题

  12. ESP-IDF NimBLE 框架

  13. 常见问题与调试

  14. 总结与对比


1. BLE 协议基础

1.1 BLE vs 经典蓝牙

特性

经典蓝牙 (BR/EDR)

BLE (低功耗蓝牙)

功耗

较高 (几十 mA)

极低

 (µA 级)

速率

1–3 Mbps

125 kbps – 2 Mbps

应用场景

音频、文件传输

传感器、控制、通知

连接延迟

100ms 级

可到 ~7.5ms

数据量

大

小 (20–244 字节/包)

拓扑

点对点

点对点 / 广播

协议栈

复杂

简单 (GATT)

1.2 BLE 协议栈分层

┌─────────────────────────────────────┐│         应用层 (APP)                 ││   GATT / GAP (服务与角色定义)        │├─────────────────────────────────────┤│        L2CAP (逻辑链路)              ││        分片、多路复用                │├─────────────────────────────────────┤│        HCI (主机控制接口)            │├─────────────────────────────────────┤│       Link Layer (链路层)            ││      LL Control、加密、跳频          │├─────────────────────────────────────┤│      Physical Layer (物理层)         ││      2.4GHz GFSK 调制                │└─────────────────────────────────────┘

1.3 角色概念

BLE 有两种角色:  广播者/外设 (Peripheral / Advertiser):    向外广播数据,等待被连接    例: 心率传感器、ESP32 蓝牙服务  扫描者/中央 (Central / Scanner):    扫描广播,发起连接    例: 手机、ESP32 连接传感器  ⚠️ ESP32 可以同时支持两种角色!  Beacon 是特殊的广播者:    只广播不连接(如 iBeacon)

1.4 GATT 概念详解

GATT (通用属性协议) 是 BLE 数据交换的核心——用"服务/特征"层级组织数据:

┌─────────────────────────────────┐│         服务 (Service)          ││  ┌───────────────────────────┐  ││  │  特征 (Characteristic)     │  ││  │  ┌─────────────────────┐  │  ││  │  │ 属性 (Value)         │  │  ││  │  │ + 属性句柄 (UUID)     │  │  ││  │  │ + 属性权限             │  │  ││  │  └─────────────────────┘  │  ││  │  + 特征描述符 (Descriptor)  │  ││  └───────────────────────────┘  ││  + 特征 (多个)                  │└─────────────────────────────────┘一个服务可以包含多个特征一个特征有 16 位或 128 位 UUID 标识

常用 UUID:

UUID

服务/特征

说明

0x1800

Generic Access

通用访问服务

0x1801

Generic Attribute

通用属性服务

0x180A

Device Information

设备信息

0x180D

Heart Rate

心率服务

0x180F

Battery

电池服务

0x2A00

Device Name

设备名称

0x2A19

Battery Level

电池电量

0x2A37

Heart Rate Measurement

心率测量

自定义

自定义服务

用户 128 位 UUID

1.5 BLE 通信操作

操作

方向

说明

Read

中央→外设

读取特征值

Write

中央→外设

写入特征值

Write No Response

中央→外设

写但不等待确认(更快)

Notify

外设→中央

外设主动通知(需要订阅)

Indicate

外设→中央

通知并要求确认

Subscribe

中央→外设

订阅通知(CCCD 描述符)


2. ESP32 BLE 硬件

2.1 BLE 能力

参数

ESP32 (原版)

蓝牙版本

BLE 4.2 (经典蓝牙 + BLE)

角色

中央 + 外设 + 广播者

最多连接

3 个并发连接 (BLE)

广播间隔

20ms – 10.24s

最大传输单元 (MTU)

517 字节

发射功率

+12 dBm 到 -6 dBm

接收灵敏度

-97 dBm (BLE)

数据速率

最高 ~1 Mbps (BLE 4.2)

天线

PCB 天线 (2.4GHz)

⚠️ ESP32 蓝牙 + Wi-Fi 共存

⚠️ 重要:ESP32 蓝牙和 Wi-Fi 共用 2.4GHz 射频!  同频段干扰问题:    - BLE 和 Wi-Fi 不能同时全速工作    - 同一时刻只能有一个使用射频  ESP32 的解决方案:    - 硬件分时复用(共存机制自动处理)    - Wi-Fi 优先于 BLE(默认)    - 共存模式下 BLE 性能略有下降  实际影响:    - 连接 Wi-Fi 时 BLE 广播间隔可能变长    - BLE 吞吐量可能降低    - 建议错开传输时间(如间隔发送)  使用注意:    - 如果只要 BLE,不要初始化 Wi-Fi    - 如果同时用,接受轻微性能损失

2.2 天线设计(PCB 开发)

ESP32 模块的天线:  1. PCB 天线(板载)— 默认使用,注意周围不要有金属  2. IPEX 外置天线 — 需要焊接连接器,可外接胶棒天线  布局建议:    - 天线周围 5mm 内不要走线    - 天线不要靠近电池/金属外壳    - 远离高频数字信号线

3. 环境准备与验证

3.1 硬件准备

测试 BLE 需要的硬件:  ✅ ESP32 开发板 × 1  ✅ 手机 × 1(iOS / Android)  ✅ 手机 BLE 调试 App:     iOS:       - LightBlue® (推荐)       - nRF Connect       - BLE 调试助手     Android:       - nRF Connect (推荐)       - BLE 调试助手       - LightBlue

3.2 Arduino 环境

// 安装 BLE 库(两种选择):// 方法 1: 使用 ESP32 官方 BLE 库(推荐)//   文件 → 首选项 → 库管理器//   搜索 "ESP32 BLE Arduino" → 安装//   作者: Neil Kolban//   包含: BLEUtils.h, BLEServer.h, BLEDevice.h ...// 方法 2: ESP32 自带 BLEDevice//   #include <BLEDevice.h>  // 部分核心版本自带// 验证安装:#include <BLEDevice.h>#include <BLEServer.h>void setup() {    Serial.begin(115200);    BLEDevice::init("ESP32_BLE_Test");  // 初始化蓝牙    Serial.println("BLE initialized");}void loop() {}

3.3 ESP-IDF 环境

# ESP-IDF 提供两套 BLE 栈:## 1. Bluedroid(完整蓝牙栈,功能全面)#    menuconfig:#      Component config → Bluetooth → Bluetooth → Enabled#      → Bluetooth controller → [Enabled]#      → Bluedroid → [Enabled]## 2. NimBLE(轻量级 BLE 栈,推荐用于纯 BLE)#    menuconfig:#      Component config → Bluetooth → Bluetooth → Enabled#      → Bluetooth controller → [Enabled]#      → NimBLE → [Enabled]  (同时关闭 Bluedroid)## NimBLE 优势:#   - 内存占用小 (~50KB vs Bluedroid ~130KB)#   - 代码更简单#   - 更活跃的社区维护# 示例代码位置:#   examples/bluetooth/bluedroid/ble/gatt_server#   examples/bluetooth/bluedroid/ble/gatt_client#   examples/bluetooth/nimble/bleprph

3.4 验证 BLE 是否工作

/** * 功能: 验证 BLE 广播是否正常 * 测试: 用手机 nRF Connect 扫描,应看到设备 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLEAdvertising.h>void setup() {    Serial.begin(115200);    delay(1000);    // 初始化 BLE    BLEDevice::init("ESP32_BLE_Demo");    Serial.println("BLE initialized");    // 创建 BLE 服务器    BLEServer *server = BLEDevice::createServer();    // 创建广播    BLEAdvertising *advertising = BLEDevice::getAdvertising();    advertising->addServiceUUID(BLEUUID((uint16_t)0x1800));    advertising->setScanResponse(true);    advertising->setMinPreferred(0x06);   // iPhone 连接优化    advertising->setMinPreferred(0x12);    // 启动广播    BLEDevice::startAdvertising();    Serial.println("Advertising started!");    Serial.println("Use nRF Connect app to scan for 'ESP32_BLE_Demo'");}void loop() {    delay(1000);}

4. BLE 外设模式(被手机连接)

4.1 基本外设(GATT 服务器)

/** * 功能: BLE 外设 — 创建 GATT 服务供手机连接 * 测试: 手机 nRF Connect 连接后,可以看到服务和特征 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>// 自定义服务/特征 UUID#define SERVICE_UUID           "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define CHARACTERISTIC_UUID    "beb5483e-36e1-4688-b7f5-ea07361b26a8"// 连接回调class MyServerCallbacks : public BLEServerCallbacks {    void onConnect(BLEServer *server) {        Serial.println("📱 Client connected!");        digitalWrite(2, HIGH);    }    void onDisconnect(BLEServer *server) {        Serial.println("📵 Client disconnected");        digitalWrite(2, LOW);        // 重新开始广播(断开后需要重新广播)        BLEDevice::startAdvertising();    }};void setup() {    Serial.begin(115200);    pinMode(2, OUTPUT);    // 1. 初始化 BLE    BLEDevice::init("ESP32_Server");    Serial.println("BLE Server initializing...");    // 2. 创建服务器    BLEServer *server = BLEDevice::createServer();    server->setCallbacks(new MyServerCallbacks());    // 3. 创建服务    BLEService *service = server->createService(SERVICE_UUID);    // 4. 创建特征(可读可写)    BLECharacteristic *characteristic = service->createCharacteristic(        CHARACTERISTIC_UUID,        BLECharacteristic::PROPERTY_READ |       // 可读        BLECharacteristic::PROPERTY_WRITE |      // 可写        BLECharacteristic::PROPERTY_NOTIFY       // 可通知    );    // 5. 添加 CCCD 描述符(Notify 必需)    characteristic->addDescriptor(new BLE2902());    // 6. 设置初始值    characteristic->setValue("Hello from ESP32!");    // 7. 启动服务    service->start();    // 8. 开始广播    BLEAdvertising *advertising = BLEDevice::getAdvertising();    advertising->addServiceUUID(SERVICE_UUID);    advertising->setScanResponse(true);    advertising->setMinPreferred(0x06);    BLEDevice::startAdvertising();    Serial.println("✅ BLE Server ready");    Serial.println("Service UUID: " + String(SERVICE_UUID));}void loop() {    delay(1000);}

4.2 特征值读写回调

/** * 功能: 处理手机写入的数据 * 场景: 手机发送命令控制 ESP32 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>#define SERVICE_UUID           "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define CHARACTERISTIC_UUID    "beb5483e-36e1-4688-b7f5-ea07361b26a8"// ── 特征值回调:处理手机写入 ──class MyCharacteristicCallbacks : public BLECharacteristicCallbacks {    void onWrite(BLECharacteristic *characteristic) {        std::string value = characteristic->getValue();        Serial.printf("📥 Received %d bytes: ", value.length());        for (size_t i = 0; i < value.length(); i++) {            Serial.printf("0x%02X ", (uint8_t)value[i]);        }        Serial.println();        // 根据接收内容执行操作        if (value == "ON") {            digitalWrite(2, HIGH);            characteristic->setValue("LED ON");            Serial.println("LED turned ON");        } else if (value == "OFF") {            digitalWrite(2, LOW);            characteristic->setValue("LED OFF");            Serial.println("LED turned OFF");        } else {            characteristic->setValue("Unknown command");        }    }    // 手机读取时的回调(可选)    void onRead(BLECharacteristic *characteristic) {        Serial.println("📖 Characteristic read by phone");    }};// ── 连接回调 ──class MyServerCallbacks : public BLEServerCallbacks {    void onConnect(BLEServer *server) {        Serial.println("📱 Connected");    }    void onDisconnect(BLEServer *server) {        Serial.println("📵 Disconnected");        BLEDevice::startAdvertising();  // 重新广播    }};void setup() {    Serial.begin(115200);    pinMode(2, OUTPUT);    BLEDevice::init("ESP32_LED");    BLEServer *server = BLEDevice::createServer();    server->setCallbacks(new MyServerCallbacks());    BLEService *service = server->createService(SERVICE_UUID);    BLECharacteristic *characteristic = service->createCharacteristic(        CHARACTERISTIC_UUID,        BLECharacteristic::PROPERTY_READ |        BLECharacteristic::PROPERTY_WRITE |        BLECharacteristic::PROPERTY_NOTIFY    );    characteristic->setCallbacks(new MyCharacteristicCallbacks());    characteristic->addDescriptor(new BLE2902());    characteristic->setValue("READY");    service->start();    BLEAdvertising *advertising = BLEDevice::getAdvertising();    advertising->addServiceUUID(SERVICE_UUID);    BLEDevice::startAdvertising();    Serial.println("✅ BLE LED Control ready");    Serial.println("Phone app: write 'ON' or 'OFF'");}void loop() {    delay(1000);}

4.3 多服务多特征

/** * 功能: 创建多个服务(常见于专业 BLE 设备) * 场景: 传感器设备 — 电池服务 + 传感器服务 + 设备信息服务 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>// 服务 UUID#define SERVICE_BATTERY    "180f"    // 标准电池服务#define SERVICE_SENSOR     "181a"    // 标准环境传感服务#define SERVICE_CUSTOM     "4fafc201-1fb5-459e-8fcc-c5c9c331914b"// 特征 UUID#define CHAR_BATTERY_LEVEL "2a19"    // 电量 0-100#define CHAR_TEMP          "2a6e"    // 温度#define CHAR_HUMIDITY      "2a6f"    // 湿度#define CHAR_CUSTOM_CTRL   "beb5483e-36e1-4688-b7f5-ea07361b26a8"BLEService *battery_service;BLECharacteristic *battery_char;BLECharacteristic *temp_char;BLECharacteristic *humid_char;void setup() {    Serial.begin(115200);    BLEDevice::init("ESP32_MultiService");    BLEServer *server = BLEDevice::createServer();    // ── 服务 1: 电池 ──    battery_service = server->createService(SERVICE_BATTERY);    battery_char = battery_service->createCharacteristic(        CHAR_BATTERY_LEVEL,        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY    );    battery_char->addDescriptor(new BLE2902());    battery_char->setValue((uint8_t)85);  // 85% 电量    // ── 服务 2: 环境传感 ──    BLEService *sensor_service = server->createService(SERVICE_SENSOR);    temp_char = sensor_service->createCharacteristic(        CHAR_TEMP,        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY    );    temp_char->addDescriptor(new BLE2902());    humid_char = sensor_service->createCharacteristic(        CHAR_HUMIDITY,        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY    );    humid_char->addDescriptor(new BLE2902());    // ── 启动所有服务 ──    battery_service->start();    sensor_service->start();    // 广播所有服务    BLEAdvertising *advertising = BLEDevice::getAdvertising();    advertising->addServiceUUID(SERVICE_BATTERY);    advertising->addServiceUUID(SERVICE_SENSOR);    BLEDevice::startAdvertising();    Serial.println("✅ Multi-service BLE ready");}void loop() {    // 每 5 秒更新数据    static unsigned long last = 0;    if (millis() - last > 5000) {        last = millis();        // 模拟传感器读数        float temp = 25.0 + (esp_random() % 100) / 100.0;        float hum  = 60.0 + (esp_random() % 100) / 100.0;        // 更新特征值        char buf[16];        snprintf(buf, sizeof(buf), "%.1f", temp);        temp_char->setValue(buf);        snprintf(buf, sizeof(buf), "%.1f", hum);        humid_char->setValue(buf);        // 通知已订阅的手机        temp_char->notify();        humid_char->notify();        Serial.printf("Updated: temp=%.1f hum=%.1f\n", temp, hum);    }    delay(100);}

5. BLE 中央模式(连接传感器)

5.1 扫描设备

/** * 功能: BLE 中央 — 扫描附近的 BLE 设备 * 场景: ESP32 作为网关,扫描并连接传感器 */#include <BLEDevice.h>#include <BLEUtils.h>#include <BLEScan.h>int scanTime = 5;  // 扫描时长(秒)// ── 扫描结果处理 ──class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {    void onResult(BLEAdvertisedDevice advertisedDevice) {        Serial.printf("📡 Device: %s | ",                      advertisedDevice.toString().c_str());        // 打印 RSSI(信号强度)        Serial.printf("RSSI: %d dBm | ", advertisedDevice.getRSSI());        // 有名称则打印        if (advertisedDevice.haveName()) {            Serial.printf("Name: %s | ", advertisedDevice.getName().c_str());        }        Serial.println();    }};void setup() {    Serial.begin(115200);    Serial.println("BLE Scanner");    // 初始化 BLE    BLEDevice::init("");    // 创建扫描器    BLEScan *scan = BLEDevice::getScan();    scan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());    scan->setActiveScan(true);      // 主动扫描(响应更快)    scan->setInterval(100);         // 扫描间隔 (ms)    scan->setWindow(99);            // 扫描窗口 (ms)    // 执行扫描(阻塞)    Serial.printf("Scanning for %d seconds...\n", scanTime);    BLEScanResults results = scan->start(scanTime);    Serial.printf("\n✅ Found %d devices\n", results.getCount());    for (int i = 0; i < results.getCount(); i++) {        BLEAdvertisedDevice device = results.getDevice(i);        Serial.printf("  %d. %s | RSSI: %d\n",                      i + 1,                      device.toString().c_str(),                      device.getRSSI());    }}void loop() {    delay(10000);  // 10 秒后重新扫描}

5.2 连接并从特征读取数据

/** * 功能: 连接 BLE 设备并读取特征值 * 场景: ESP32 连接心率传感器并读取心率 */#include <BLEDevice.h>#include <BLEUtils.h>#include <BLEClient.h>// 目标设备信息#define TARGET_NAME  "ESP32_Server"      // 或使用地址#define TARGET_SERVICE_UUID    "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define TARGET_CHAR_UUID       "beb5483e-36e1-4688-b7f5-ea07361b26a8"BLEUUID serviceUUID(TARGET_SERVICE_UUID);BLEUUID charUUID(TARGET_CHAR_UUID);// 连接回调static BLEAddress *pServerAddress = NULL;// ── 连接并读取数据 ──bool connectToServer(BLEAddress pAddress) {    Serial.printf("Connecting to %s...\n", pAddress.toString().c_str());    BLEClient *client = BLEDevice::createClient();    client->connect(pAddress);    Serial.println("✅ Connected");    // 获取服务    BLERemoteService *remoteService =        client->getService(serviceUUID);    if (remoteService == nullptr) {        Serial.println("❌ Service not found!");        return false;    }    Serial.println("Service found");    // 获取特征    BLERemoteCharacteristic *remoteCharacteristic =        remoteService->getCharacteristic(charUUID);    if (remoteCharacteristic == nullptr) {        Serial.println("❌ Characteristic not found!");        return false;    }    Serial.println("Characteristic found");    // 读取特征值    std::string value = remoteCharacteristic->readValue();    Serial.printf("📖 Read value: %s\n", value.c_str());    return true;}void setup() {    Serial.begin(115200);    BLEDevice::init("");    // 扫描目标设备    BLEScan *scan = BLEDevice::getScan();    scan->setActiveScan(true);    Serial.printf("Scanning for %s...\n", TARGET_NAME);    BLEScanResults results = scan->start(10);    bool found = false;    for (int i = 0; i < results.getCount(); i++) {        BLEAdvertisedDevice device = results.getDevice(i);        if (device.haveName() && device.getName() == TARGET_NAME) {            Serial.printf("🎯 Found target: %s\n", device.toString().c_str());            found = true;            // 保存地址并连接            pServerAddress = new BLEAddress(device.getAddress());            connectToServer(*pServerAddress);            break;        }    }    if (!found) {        Serial.println("❌ Target device not found");    }}void loop() {    delay(10000);}

5.3 客户端回调(接收通知)

/** * 功能: 订阅 BLE 特征通知(客户端) * 场景: 接收外设主动推送的数据 */#include <BLEDevice.h>#include <BLEUtils.h>#include <BLEClient.h>#define SERVICE_UUID    "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define CHAR_UUID       "beb5483e-36e1-4688-b7f5-ea07361b26a8"BLEUUID serviceUUID(SERVICE_UUID);BLEUUID charUUID(CHAR_UUID);static boolean doConnect = false;static boolean connected = false;static BLEAddress *serverAddress;// ── 通知回调:外设推送数据时触发 ──class MyClientCallback : public BLEClientCallbacks {    void onConnect(BLEClient *client) {        connected = true;        Serial.println("✅ Connected to server");    }    void onDisconnect(BLEClient *client) {        connected = false;        Serial.println("📵 Disconnected");        // 重新扫描连接        doConnect = true;    }};// ── 特征通知回调 ──class MyNotifyCallback : public BLECharacteristicCallbacks {    void onNotify(BLEClient *client, BLERemoteCharacteristic *characteristic,                  uint8_t *data, size_t len) {        Serial.printf("🔔 Notification (%d bytes): ", len);        for (size_t i = 0; i < len; i++) {            Serial.printf("0x%02X ", data[i]);        }        Serial.println();    }};// ── 连接并订阅 ──bool connectAndSubscribe(BLEAddress address) {    BLEClient *client = BLEDevice::createClient();    client->setClientCallbacks(new MyClientCallback());    client->connect(address);    BLERemoteService *service = client->getService(serviceUUID);    BLERemoteCharacteristic *characteristic =        service->getCharacteristic(charUUID);    // 注册通知回调    characteristic->registerForNotify(        [](BLERemoteCharacteristic *c, uint8_t *data, size_t len, bool isNotify) {            Serial.printf("🔔 Got %d bytes: ", len);            for (int i = 0; i < len; i++) {                Serial.printf("%02X ", data[i]);            }            Serial.println();        }, true);    return true;}void setup() {    Serial.begin(115200);    BLEDevice::init("");    BLEDevice::setClientCallbacks(new MyClientCallback());    // 扫描并连接    BLEScan *scan = BLEDevice::getScan();    scan->setActiveScan(true);    BLEScanResults results = scan->start(5);    for (int i = 0; i < results.getCount(); i++) {        BLEAdvertisedDevice device = results.getDevice(i);        if (device.haveServiceUUID() && device.isAdvertisingService(serviceUUID)) {            serverAddress = new BLEAddress(device.getAddress());            doConnect = true;            break;        }    }}void loop() {    if (doConnect) {        doConnect = false;        if (connectAndSubscribe(*serverAddress)) {            Serial.println("Subscribed to notifications");        }    }    delay(100);}

6. BLE Beacon 广播

6.1 Beacon 概念

Beacon 是一种特殊的 BLE 广播方式:  特点:    - 只广播,不连接    - 周期性发送一小段数据    - 低功耗(广播间隔可配置)  常见协议:    - iBeacon (苹果)    - Eddystone (谷歌)    - 自定义广播  应用:    - 室内定位    - 广告推送    - 近场触发(到店检测)    - 资产追踪

6.2 iBeacon 实现

/** * 功能: 制作 iBeacon 广播 * 测试: 用手机 nRF Connect 或 Locate Beacon 查看 * * iBeacon 广播数据格式: *   02 01 06            — Flags *   1A FF               — Manufacturer Specific Data *   4C 00               — Apple Company ID *   02 15               — iBeacon Type *   <UUID 16字节> *   <Major 2字节> <Minor 2字节> *   <Tx Power 1字节> */#include <BLEDevice.h>#include <BLEAdvertising.h>// iBeacon 参数const char *BEACON_UUID = "74278BDA-B644-4520-8F0C-720EAF059935";uint16_t major = 100;uint16_t minor = 1;int8_t txPower = -59;  // 1m 处的信号强度 (dBm)// 构建 iBeacon 广播数据BLEAdvertisementData buildIBeaconData() {    BLEAdvertisementData advData;    // 自定义广播数据    std::string data;    // Flags    data += (char)0x02;  // 长度    data += (char)0x01;  // 类型    data += (char)0x06;  // LE General Discoverable + BR/EDR Not Supported    // iBeacon 厂商数据    data += (char)0x1A;  // 长度 (26)    data += (char)0xFF;  // Manufacturer Specific    data += (char)0x4C;  // Apple    data += (char)0x00;    data += (char)0x02;  // iBeacon    data += (char)0x15;    // UUID (16 字节)    std::string uuidStr = BEACON_UUID;    uuidStr.erase(std::remove(uuidStr.begin(), uuidStr.end(), '-'), uuidStr.end());    for (int i = 0; i < 32; i += 2) {        std::string byteStr = uuidStr.substr(i, 2);        data += (char)strtol(byteStr.c_str(), NULL, 16);    }    // Major (2 字节, 大端)    data += (char)(major >> 8);    data += (char)(major & 0xFF);    // Minor (2 字节, 大端)    data += (char)(minor >> 8);    data += (char)(minor & 0xFF);    // Tx Power    data += (char)txPower;    advData.setData(data);    return advData;}void setup() {    Serial.begin(115200);    BLEDevice::init("");    // 创建广播    BLEAdvertising *advertising = BLEDevice::getAdvertising();    // 设置 iBeacon 数据    advertising->setAdvertisementData(buildIBeaconData());    // 广播参数    advertising->setScanResponse(false);    advertising->setMinInterval(100);   // 100ms    advertising->setMaxInterval(200);   // 200ms    // 开始广播    advertising->start();    Serial.println("✅ iBeacon started");    Serial.printf("UUID: %s\n", BEACON_UUID);    Serial.printf("Major: %d, Minor: %d\n", major, minor);}void loop() {    delay(1000);}

6.3 自定义 Beacon(传感器数据广播)

/** * 功能: 自定义 Beacon — 广播传感器数据 * 场景: 不需要连接,让手机直接接收传感器读数 */#include <BLEDevice.h>#include <BLEAdvertising.h>void setup() {    Serial.begin(115200);    BLEDevice::init("");}void updateBeaconData() {    BLEAdvertising *advertising = BLEDevice::getAdvertising();    // 读取传感器    float temp = 25.3;    float humid = 60.5;    // 构建自定义广播数据    BLEAdvertisementData data;    std::string customData;    customData += (char)0x03;   // 长度    customData += (char)0xFF;   // 厂商数据    customData += (char)0x01;   // 厂商 ID (自定义)    customData += (char)0x02;   // 数据版本    // 温度 (2 字节,整数*10)    int temp_i = (int)(temp * 10);    customData += (char)(temp_i >> 8);    customData += (char)(temp_i & 0xFF);    // 湿度 (2 字节,整数*10)    int hum_i = (int)(humid * 10);    customData += (char)(hum_i >> 8);    customData += (char)(hum_i & 0xFF);    data.setData(customData);    advertising->setAdvertisementData(data);}void loop() {    updateBeaconData();    // 重新广播    BLEAdvertising *advertising = BLEDevice::getAdvertising();    advertising->stop();    advertising->start();    delay(1000);  // 每秒更新一次}

7. BLE 数据收发实战

7.1 发送大数据(分包)

/** * 功能: 通过 BLE 发送大数据(如日志、配置) * 原理: BLE 单包最大 20 字节(默认),需要分包发送 *       建议通过多特征或分片协议实现 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>#define SERVICE_UUID    "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define CHAR_DATA_UUID  "beb5483e-36e1-4688-b7f5-ea07361b26a8"BLECharacteristic *data_char;// ── 分包发送大数据 ──void sendDataInChunks(BLECharacteristic *characteristic,                      uint8_t *data, size_t len) {    const size_t CHUNK_SIZE = 20;  // BLE 单包最大 20 字节    size_t offset = 0;    uint32_t seq = 0;    while (offset < len) {        size_t chunkLen = min(CHUNK_SIZE - 2, len - offset);  // 2 字节帧头        // 构建分片帧: [序号高] [序号低] [数据...]        uint8_t packet[22];        packet[0] = (seq >> 8) & 0xFF;        packet[1] = seq & 0xFF;        memcpy(packet + 2, data + offset, chunkLen);        characteristic->setValue(packet, chunkLen + 2);        characteristic->notify();        offset += chunkLen;        seq++;        delay(10);  // 给手机处理时间    }    // 发送结束标志    uint8_t end[] = {0xFF, 0xFF};    characteristic->setValue(end, 2);    characteristic->notify();    Serial.printf("Sent %d bytes in %u chunks\n", len, seq);}// 测试数据void sendTestData() {    // 发送 500 字节的随机数据    uint8_t testData[500];    for (int i = 0; i < 500; i++) {        testData[i] = i % 256;    }    sendDataInChunks(data_char, testData, sizeof(testData));}void setup() {    Serial.begin(115200);    BLEDevice::init("ESP32_DataSender");    BLEServer *server = BLEDevice::createServer();    BLEService *service = server->createService(SERVICE_UUID);    data_char = service->createCharacteristic(        CHAR_DATA_UUID,        BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ    );    data_char->addDescriptor(new BLE2902());    data_char->setValue("ready");    service->start();    BLEDevice::startAdvertising();    Serial.println("Ready. Connect and send data.");}void loop() {    // 每 5 秒发送一次测试数据    static unsigned long last = 0;    if (millis() - last > 5000) {        last = millis();        sendTestData();    }    delay(10);}

7.2 提高吞吐量

/** * 功能: 优化 BLE 吞吐量 */#include <BLEDevice.h>#include <BLEServer.h>// ════════════════════════════════════════════════// BLE 吞吐量优化技巧://// 1. 增大 MTU(传输单元)//    默认 23 字节(含 3 字节开销)→ 请求 517 字节//    iOS: 请求 185 字节 (iPhone 支持)//    Android: 请求 517 字节//// 2. 使用 Write Without Response (更快)//    比 Write with Response 快 ~2×//// 3. 增大连接间隔//    连接间隔越短,延迟越低,但功耗更高//// 4. 使用多个并行特征//    可以在多个特征间交替发送// ════════════════════════════════════════════════// ── 请求更大 MTU(客户端)──class MyClientCallback : public BLEClientCallbacks {    void onConnect(BLEClient *client) {        // 请求更大的 MTU (185 字节)        // 注意: 部分 BLE 核心版本需要在连接后请求        Serial.println("Connected. Requesting MTU 185...");    }};// 或使用以下方法:void requestLargerMTU(BLEClient *client) {    // ESP32 Arduino 核心自动请求 MTU 517    // 也可以通过配置设置:    // BLEDevice::setMTU(185);}

7.3 BLE 传输速率实测

ESP32 BLE 实际传输速率参考:  配置                    速率  ──────────────────      ──────  Notify, 20字节, 默认      ~1 KB/s  Notify, 20字节, 优化      ~3 KB/s  Notify, 185字节 MTU       ~10 KB/s  Notify, 512字节 MTU       ~20 KB/s  高速模式 (1M PHY)         最高 ~40 KB/s  影响速率因素:    - MTU 大小 (最关键)    - 连接间隔    - 是否使用 Write No Response    - 手机系统的 BLE 性能限制

8. BLE 通知与订阅

8.1 外设主动通知

/** * 功能: 外设向已订阅的手机推送数据 * 场景: 传感器周期性上报(无需手机轮询) */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h>#define SERVICE_UUID    "4fafc201-1fb5-459e-8fcc-c5c9c331914b"#define CHAR_NOTIFY_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"BLECharacteristic *notify_char;// ── 订阅状态跟踪 ──class MyCharCallbacks : public BLECharacteristicCallbacks {    // 手机订阅/取消订阅时触发(通过 CCCD)    void onSubscribe(BLECharacteristic *characteristic,                     BLE2902 *descriptor) {        uint16_t cccd = descriptor->getValue();        if (cccd & 0x0001) {  // Notifications 使能            Serial.println("🔔 Phone subscribed to notifications");        } else {            Serial.println("🔕 Phone unsubscribed");        }    }};void setup() {    Serial.begin(115200);    BLEDevice::init("ESP32_Notifier");    BLEServer *server = BLEDevice::createServer();    BLEService *service = server->createService(SERVICE_UUID);    notify_char = service->createCharacteristic(        CHAR_NOTIFY_UUID,        BLECharacteristic::PROPERTY_NOTIFY |   // 通知        BLECharacteristic::PROPERTY_READ    );    notify_char->addDescriptor(new BLE2902());  // CCCD 必须!    notify_char->setCallbacks(new MyCharCallbacks());    service->start();    BLEDevice::startAdvertising();    Serial.println("✅ Notification service ready");    Serial.println("Connect phone app and tap subscribe (CCCD)");}void loop() {    // 每 2 秒通知一次(即使没人订阅也无妨)    static unsigned long last = 0;    if (millis() - last > 2000) {        last = millis();        // 生成心跳数据        uint16_t heart_rate = 60 + esp_random() % 40;  // 60-100        uint8_t data[2] = {0, (uint8_t)heart_rate};        // 通知已订阅客户端        notify_char->setValue(data, 2);        notify_char->notify();        Serial.printf("💓 Notified: HR=%d bpm\n", heart_rate);    }    delay(10);}

8.2 Notify vs Indicate

/* * Notify vs Indicate 区别: * * Notify (通知): *   - 不要求接收方确认 *   - 更快的传输速率 *   - 可能丢失数据(极少) *   - 用 PROPERTY_NOTIFY 声明 *   - CCCD 值 0x0001 * * Indicate (指示): *   - 接收方必须确认 (ACK) *   - 确保数据送达 *   - 速度略慢 *   - 用 PROPERTY_INDICATE 声明 *   - CCCD 值 0x0002 * * 选择建议: *   - 周期上报 → Notify *   - 关键指令 → Indicate *   - 大数据量 → Notify */// 创建一个同时支持 Notify 和 Indicate 的特征BLECharacteristic *char_data = service->createCharacteristic(    CHAR_UUID,    BLECharacteristic::PROPERTY_NOTIFY |    BLECharacteristic::PROPERTY_INDICATE |    BLECharacteristic::PROPERTY_READ);// 使用 Notify 发送char_data->notify();// 使用 Indicate 发送char_data->indicate();  // 等待手机 ACK

9. BLE + 微信小程序

9.1 小程序 BLE API

微信小程序通过 wx.openBluetoothAdapter / wx.createBLEConnection 系列 API 连接 BLE 设备。

// ════════════════════════════════════════════════// 微信小程序 BLE 连接 ESP32 示例// ════════════════════════════════════════════════// 1. 初始化蓝牙wx.openBluetoothAdapter({  success: function(res) {    console.log('蓝牙适配器打开成功');    startScan();  },  fail: function(err) {    console.log('打开蓝牙失败', err);  }});// 2. 开始扫描function startScan() {  wx.startBluetoothDevicesDiscovery({    allowDuplicatesKey: true,    interval: 100,    success: function(res) {      console.log('开始扫描');      setTimeout(stopScan, 5000);    }  });}// 3. 监听发现设备wx.onBluetoothDeviceFound(function(res) {  var devices = res.devices;  for (var i = 0; i < devices.length; i++) {    var device = devices[i];    // 查找 ESP32 设备(根据广播数据或名称)    if (device.name && device.name.indexOf('ESP32') >= 0) {      console.log('找到 ESP32 设备', device);      connectDevice(device.deviceId);      break;    }  }});// 4. 连接设备function connectDevice(deviceId) {  wx.createBLEConnection({    deviceId: deviceId,    success: function(res) {      console.log('连接成功');      getServices(deviceId);    },    fail: function(err) {      console.log('连接失败', err);    }  });}// 5. 获取服务var SERVICE_UUID = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';var CHAR_UUID = 'beb5483e-36e1-4688-b7f5-ea07361b26a8';var deviceIdGlobal = '';function getServices(deviceId) {  deviceIdGlobal = deviceId;  wx.getBLEDeviceServices({    deviceId: deviceId,    success: function(res) {      console.log('服务列表:', res.services);      enableNotify();    }  });}// 6. 获取特征值function getCharacteristics() {  wx.getBLEDeviceCharacteristics({    deviceId: deviceIdGlobal,    serviceId: SERVICE_UUID,    success: function(res) {      console.log('特征列表:', res.characteristics);    }  });}// 7. 启用通知(接收 ESP32 推送)function enableNotify() {  wx.notifyBLECharacteristicValueChange({    deviceId: deviceIdGlobal,    serviceId: SERVICE_UUID,    characteristicId: CHAR_UUID,    state: true,    success: function(res) {      console.log('已启用通知');      listenForData();    }  });}// 8. 监听通知数据function listenForData() {  wx.onBLECharacteristicValueChange(function(res) {    // res.value 是 ArrayBuffer    var data = new Uint8Array(res.value);    console.log('收到数据:', data);  });}// 9. 发送数据到 ESP32function sendToESP32(message) {  // 字符串转 ArrayBuffer  var buffer = new ArrayBuffer(message.length);  var dataView = new DataView(buffer);  for (var i = 0; i < message.length; i++) {    dataView.setUint8(i, message.charCodeAt(i));  }  wx.writeBLECharacteristicValue({    deviceId: deviceIdGlobal,    serviceId: SERVICE_UUID,    characteristicId: CHAR_UUID,    value: buffer,    success: function(res) {      console.log('发送成功');    },    fail: function(err) {      console.log('发送失败', err);    }  });}// 10. 断开连接function disconnect() {  wx.closeBLEConnection({    deviceId: deviceIdGlobal,    success: function() {      console.log('已断开');    }  });}

9.2 蓝牙调试工具对比

工具

平台

特点

nRF Connect

iOS/Android

⭐ 功能最全,专业

LightBlue

iOS

简洁易用

BLE 调试助手

Android

国产,常用

微信小程序调试

微信

实际项目调试

esp32 Ble Mesh

Android

Espressif 官方


10. BLE 低功耗优化

10.1 广播功耗

BLE 广播功耗优化:  广播参数                    功耗 (mA)  ──────────────────────      ────────  广播间隔 20ms               ~15 mA  广播间隔 100ms              ~5 mA  广播间隔 1s                 ~1 mA  广播间隔 10s                ~0.3 mA  广播间隔设置:    advertising->setMinInterval(1000);  // 最小间隔 (0.625ms 单位)    advertising->setMaxInterval(2000);  // 最大间隔  注意: 广播间隔越长,手机发现设备越慢        建议 ≤ 100ms 用于快速配对

10.2 连接功耗

连接期间的功耗优化:  关键参数:    - 连接间隔 (Connection Interval)    - 从机延迟 (Slave Latency)    - 超时时间 (Supervision Timeout)  默认值:    - 连接间隔: 30ms    - 从机延迟: 0 (每个间隔都唤醒)    - 超时: 5s  优化设置 (ESP-IDF):    esp_ble_gap_update_conn_params(...)    // conn_interval_min = 100 (100 × 1.25ms = 125ms)    // conn_interval_max = 200    // slave_latency = 4 (每 5 个间隔醒来一次)    // supervision_timeout = 500 (5s)

10.3 深度睡眠 + BLE 唤醒

/** * 功能: 深度睡眠 + BLE 定时唤醒(低功耗 IoT 节点) * 注意: BLE 不能从深度睡眠唤醒(这是 Wi-Fi 才有的功能) *       但可以定时唤醒后短暂广播 */#include <BLEDevice.h>#include <esp_sleep.h>RTC_DATA_ATTR int boot_count = 0;void setup() {    Serial.begin(115200);    delay(100);    boot_count++;    Serial.printf("Boot #%d\n", boot_count);    // ── 1. 初始化 BLE 并广播 3 秒 ──    BLEDevice::init("ESP32_Beacon");    BLEDevice::getAdvertising()->start();    Serial.println("Broadcasting for 3 seconds...");    // 广播 3 秒让手机有机会扫描到    delay(3000);    BLEDevice::deinit(false);  // 关闭 BLE    // ── 2. 深度睡眠 30 秒 ──    Serial.println("Entering deep sleep for 30s...");    Serial.flush();    esp_sleep_enable_timer_wakeup(30 * 1000000ULL);    esp_deep_sleep_start();}void loop() {}

10.4 BLE 低功耗技巧汇总

🔋 BLE 低功耗优化清单:1.  ⭐ 广播间隔适当拉长    需要快速连接 → 100ms    省电优先     → 1s+2.  ⭐ 不连接时关闭 BLE    只在需要时才初始化广播3.  使用从机延迟 (Slave Latency)    允许跳过多个连接间隔再唤醒4.  缩短连接时间    数据传完就断开,不要保持长连接5.  减少数据量    能用 1 字节绝不用 4 字节    传感器数据压缩6.  降低发射功率    esp_ble_tx_power_set(ESP_BLE_PWR_LVL_P3);    // P0=-12dBm(最省电) P3=0dBm(默认) P9=+12dBm(最强)7.  使用深度睡眠周期广播    每 30s 唤醒广播 1s → 平均功耗极低8.  广播时设置 TX 功率    近距离设备用低功率即可

11. BLE 安全问题

11.1 认证与加密

/** * 功能: BLE 配对认证(PIN 码) * 场景: 防止未授权设备连接 */#include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>// ── 安全回调 ──class MySecurityCallbacks : public BLESecurityCallbacks {    uint32_t onPassKeyRequest() {        Serial.println("📋 Passkey requested");        return 123456;  // 固定 PIN 码    }    void onPassKeyNotify(uint32_t pass_key) {        Serial.printf("🔑 Passkey: %u\n", pass_key);    }    bool onConfirmPIN(uint32_t pass_key) {        Serial.printf("🔑 Confirm PIN: %u\n", pass_key);        return true;  // 自动确认    }    bool onSecurityRequest() {        Serial.println("🔒 Security requested");        return true;    }    void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl) {        if (auth_cmpl.success) {            Serial.println("🔓 Authentication success!");        } else {            Serial.println("🔒 Authentication failed!");        }    }};void setup() {    Serial.begin(115200);    BLEDevice::init("ESP32_Secure");    // ── 启用安全功能 ──    BLESecurity *security = new BLESecurity();    security->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);    security->setCapability(ESP_IO_CAP_OUT);  // 有输出(显示 PIN)    security->setPin(123456);    security->setSecurityCallbacks(new MySecurityCallbacks());    BLEServer *server = BLEDevice::createServer();    // ... 创建服务和特征 ...    Serial.println("✅ Secure BLE ready (PIN: 123456)");}void loop() {    delay(1000);}

11.2 安全建议

🔐 BLE 安全建议:1.  ⭐ 始终启用配对认证    默认情况下 BLE 可被任何人连接!2.  使用加密连接    传输敏感数据时强制加密3.  PIN 码不要用固定值(生产环境)    开发阶段可以用固定 PIN    生产建议动态生成 + 屏幕显示4.  校验数据完整性    命令帧加 CRC 或校验和    防止中间人篡改5.  BLE 广播数据不包含敏感信息    广播是公开的,任何人可嗅探!6.  限制连接数量    每个 ESP32 默认允许 3 个连接    生产环境可限制为 17.  定期更新固件    BLE 漏洞主要通过 OTA 修复

12. ESP-IDF NimBLE 框架

12.1 NimBLE 简介

NimBLE (Apache 2.0) 是轻量级 BLE 协议栈:  优势:    - 内存占用小 (~50KB,Bluedroid 需 ~130KB)    - 代码简单易读    - 活跃社区    - 纯 BLE 场景推荐  配置 (menuconfig):    Component config → Bluetooth → Bluetooth → Enabled      → Bluetooth controller → Enabled      → NimBLE → Enabled  (关闭 Bluedroid)  示例:    examples/bluetooth/nimble/bleprph  (外设)    examples/bluetooth/nimble/blecent  (中央)    examples/bluetooth/nimble/blemesh  (Mesh)

12.2 NimBLE 外设示例

/** * 功能: NimBLE GATT 服务器(外设) */#include <stdio.h>#include <string.h>#include "esp_log.h"#include "nimble/nimble_port.h"#include "nimble/nimble_port_freertos.h"#include "host/ble_hs.h"#include "host/util/util.h"#include "services/gap/ble_svc_gap.h"#include "services/gatt/ble_svc_gatt.h"static const char *TAG = "NimBLE";// ── GATT 服务/特征定义 ──static const ble_uuid128_t gatt_svr_svc_uuid = BLE_UUID128_INIT(    0x14, 0x9f, 0xc2, 0x01, 0xb5, 0x1f, 0x59, 0x4e,    0x8f, 0xcc, 0xc5, 0xc9, 0x33, 0x19, 0x4f, 0xaf);static const ble_uuid128_t gatt_svr_chr_uuid = BLE_UUID128_INIT(    0xa8, 0x26, 0x61, 0xea, 0x07, 0xf5, 0x88, 0x46,    0xb7, 0xf5, 0xe1, 0x36, 0x3e, 0x54, 0xb5, 0xbe);// ── 特征写入回调 ──static int gatt_svr_chr_write(struct ble_hs_conn *conn,                              uint16_t attr_handle,                              struct os_mbuf *om, uint16_t min_len,                              uint16_t max_len, void *arg) {    uint8_t data[256];    uint16_t len = OS_MBUF_PKTLEN(om);    if (len > sizeof(data)) {        return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;    }    os_mbuf_copydata(om, 0, len, data);    data[len] = '\0';    ESP_LOGI(TAG, "Received (%d): %s", len, data);    return 0;}// ── GATT 服务定义 ──static const struct ble_gatt_svc_def gatt_svr_svcs[] = {    // 自定义服务    {        .type = BLE_GATT_SVC_TYPE_PRIMARY,        .uuid = &gatt_svr_svc_uuid.u,        .characteristics = (struct ble_gatt_chr_def[]) {            {                .uuid = &gatt_svr_chr_uuid.u,                .access_cb = gatt_svr_chr_write,                .flags = BLE_GATT_CHR_F_READ |                         BLE_GATT_CHR_F_WRITE |                         BLE_GATT_CHR_F_NOTIFY,            },        },    },    {        .type = BLE_GATT_SVC_TYPE_END,  // 结束标记    },};// ── GAP 事件处理 ──static int gap_event_handler(struct ble_gap_event *event, void *arg) {    switch (event->type) {        case BLE_GAP_EVENT_CONNECT:            ESP_LOGI(TAG, "Connection %s",                     event->connect.status == 0 ? "established" : "failed");            break;        case BLE_GAP_EVENT_DISCONNECT:            ESP_LOGI(TAG, "Disconnected");            // 重新广播            ble_gap_adv_start(ble_adv_params, NULL);            break;        case BLE_GAP_EVENT_ADV_COMPLETE:            ESP_LOGI(TAG, "Advertising complete");            break;        default:            break;    }    return 0;}// ── 广播参数 ──static struct ble_gap_adv_params adv_params = {    .conn_mode = BLE_GAP_CONN_MODE_UND,   // 可连接    .disc_mode = BLE_GAP_DISC_MODE_GEN,   // 可发现};// ── 主机同步回调 ──static void on_sync(void) {    // 设置设备名称    ble_svc_gap_device_name_set("NimBLE_ESP32");    // 开始广播    ble_gap_adv_start(&adv_params, NULL);    ESP_LOGI(TAG, "Advertising started");}// ── 主机复位回调 ──static void on_reset(int reason) {    ESP_LOGW(TAG, "BLE reset: %d", reason);}void app_main(void) {    // 1. 初始化 NimBLE 主机    ESP_ERROR_CHECK(nimble_port_init());    // 2. 注册服务    ble_svc_gap_init();    ble_svc_gatt_init();    ble_gatts_count_cfg(gatt_svr_svcs);    ble_gatts_add_svcs(gatt_svr_svcs);    // 3. 注册同步/复位回调    ble_hs_cfg.sync_cb = on_sync;    ble_hs_cfg.reset_cb = on_reset;    // 4. 启动主机    nimble_port_freertos_init(gap_event_handler);    ESP_LOGI(TAG, "NimBLE initialized");}

12.3 Bluedroid vs NimBLE 选择

对比项

Bluedroid

NimBLE

内存占用

~130KB

~50KB

经典蓝牙

✅ 支持

❌ 仅 BLE

API 复杂度

复杂

简单

社区活跃

官方维护

Apache 活跃

Arduino 支持

使用 Kolban 库

可用但少

推荐场景

需要经典蓝牙

纯 BLE 项目


13. 常见问题与调试

13.1 故障排查树

❌ BLE 问题排查│├─ 手机扫不到设备?│   ├─ BLE 初始化了吗?→ BLEDevice::init()│   ├─ 广播开始了吗?→ startAdvertising()│   ├─ 广播间隔太长?→ 调到 100ms 测试│   └─ 手机蓝牙开了吗?→ 常见低级错误│├─ 能扫到但连接不上?│   ├─ 连接数满了?→ 最多 3 个连接│   ├─ 其他设备占着连接?→ 断开重试│   ├─ 配对认证失败?→ 检查安全设置│   └─ 距离太远?→ BLE 有效距离 ~10m│├─ 能连接但读不到特征?│   ├─ 服务 UUID 匹配吗?→ 大小写/格式│   ├─ 特征属性对吗?→ READ/WRITE 权限│   └─ 服务启动了吗?→ service->start()│├─ 收不到通知?│   ├─ CCCD 订阅了吗?→ 手机要订阅│   ├─ 加了 BLE2902 吗?→ Notify 必须│   └─ 调用了 notify() 吗?→ 记得调用│└─ 数据乱码/丢失?    ├─ 分包了吗?→ 单包限 20 字节    ├─ MTU 太小的手机 → 用 20 字节分包    └─ 传输太快?→ 加延迟

13.2 调试技巧

// ── 开启详细日志 (Arduino) ──BLEDevice::setVerbose(true);  // 打开 BLE 详细日志// 或:// BLEDevice::init("");// BLEDevice::setVerbose(true);// ── 查看 BLE 状态 ──void printBLEStatus() {    // 检查 BLE 是否在广播    // 打印本地地址    BLEAddress addr = BLEDevice::getAddress();    Serial.printf("BLE MAC: %s\n", addr.toString().c_str());}// ── ESP-IDF 日志等级 ──// menuconfig → Component config → Bluetooth → Debug log level// 设为 Verbose 可以看到完整的协议栈日志// ── 硬件检查 ──// 1. 板载天线附近不要有金属遮挡// 2. 供电要稳定(BLE 广播瞬间电流 ~40mA)// 3. 远离大功率干扰源

13.3 常见错误与解决

错误现象

原因

解决方案

GATT error 133

特征写失败

检查属性权限

Connection timeout

连接超时

距离太远/干扰

Pin mismatch

配对失败

检查 PIN

服务发现失败

外设未启动服务

检查 service->start()

内存不足

BLE 栈内存占用大

用 NimBLE 或增大分区

广播丢失

射频繁忙

检查 Wi-Fi 共存


14. 总结与对比

14.1 功能对照表

操作

Arduino (ESP32 BLE)

ESP-IDF (Bluedroid/NimBLE)

初始化 BLEDevice::init() nimble_port_init()

 / esp_ble_gap_start_advertising()

创建服务 createService() ble_gatts_add_svcs()
创建特征 createCharacteristic() ble_gatt_svc_def

 定义

广播 startAdvertising() ble_gap_adv_start()
扫描 BLEScan::start() ble_gap_disc()
连接 BLEClient::connect() ble_gap_connect()
读取特征 readValue() ble_gattc_read()
写入特征 setValue() ble_gattc_write_flat()
通知 characteristic->notify() ble_gatts_chr_updated()
回调

类继承回调

事件回调函数

安全

BLESecurity

ble_hs_cfg 配置

14.2 Arduino vs ESP-IDF 选择

Arduino (ESP32 BLE Arduino 库):✅ API 面向对象,清晰直观✅ 开发速度快✅ 大量示例可参考✅ 适合快速原型和教学⚠️ 基于 Bluedroid,内存占用大⚠️ 底层控制有限ESP-IDF (NimBLE):✅ 内存占用小 (~50KB)✅ 更灵活的控制✅ 更适合产品级开发✅ Mesh 等高级功能⚠️ API 更底层⚠️ 学习曲线陡⚠️ 代码量更大

14.3 BLE 开发黄金法则

🔧 BLE 开发黄金法则1.  ⭐ 先用手机 App 验证    nRF Connect 是调试 BLE 最强大的工具。    先让它能扫到、能连接,再写代码。2.  UUID 要规范    标准服务用标准 UUID(0x1800 等)。    自定义服务用 128 位 UUID(不要截断)。3.  Notify 必须配 BLE2902 (CCCD)    没有 CCCD 描述符,手机无法订阅通知。4.  单包数据 ≤ 20 字节    默认 MTU 23 字节,减去 3 字节头 = 20 字节数据。    发大数据必须分包。5.  断开后记得重新广播    外设被断开后,需要手动重新调用 startAdvertising()。6.  Wi-Fi + BLE 共存要测试    两者共用射频,同时用会有性能损失。7.  广播数据不加密    广播是公开的,别把敏感数据放广播里。8.  连接间隔 vs 功耗权衡    连接间隔短 = 低延迟但费电。    根据应用需求平衡。

14.4 参考资源

资源

链接

BLE 协议规范 4.2

https://www.bluetooth.com/specifications/specs/

ESP32 BLE Arduino 库

https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE

ESP-IDF BLE 示例

https://github.com/espressif/esp-idf/tree/master/examples/bluetooth

NimBLE 官方文档

https://mynewt.apache.org/latest/network/docs/

nRF Connect App

https://www.nordicsemi.com/Products/Development-tools/nrf-connect-for-mobile

微信小程序蓝牙 API

https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/


🌟 欢迎关注与交流

如果这份教程对你有帮助,欢迎点赞、收藏、转发!

📌

链接 / 说明

📖 更多 ESP32 教程

本系列持续更新中

💬 问题反馈

欢迎在项目中提 Issue 或 Discussions

⭐ 收藏关注

点个 Star / 收藏,第一时间获取更新

📚 系列教程(已发布):

#

教程

状态

1

ESP32 GPIO 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

2

ESP32 FreeRTOS 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

3

ESP32——深度睡眠与低功耗 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

4

ESP32——Wi-Fi 网络编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

5

ESP32——I2C 总线通信 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

6

ESP32——SPI 总线通信 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

7

ESP32——ADC / 模拟信号 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

8

ESP32——UART 串口通信 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 已完成

9

ESP32——蓝牙 BLE 编程教程:Arduino 与 ESP-IDF 双环境指南

✅ 最新

🔜 更多主题规划中:LVGL 图形界面、摄像头与视觉、NVS 存储……

文档版本:v1.0 | 最后更新:2026-07-27

发表评论