基于 ESP32-WROOM-32 实践 BLE 串口服务器(Write 和 Notify 综合) – PlatformIO

最近更新于 2026-07-26 19:44

测试环境

  • ESP32-WROOM-32
  • PlatformIO IDE 3.3.4
  • PlatformIO Core 6.1.19
  • pioarduino platform-espressif32 55.03.311(Arduino Release v3.3.11 based on ESP-IDF v5.5.5)

platformio.ini

[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
board = esp32dev
framework = arduino
monitor_speed = 115200

实践

源码参考来源:https://github.com/espressif/arduino-esp32/blob/3.3.11/libraries/BLE/examples/UART/UART.ino

/*
    【程序功能总结】:
    创建一个标准的 Nordic UART Service (NUS) BLE 服务器:
    1. 广播服务 UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
    2. RX 特征值 (接收): 6E400002-..., 模式为 WRITE,用于接收手机/电脑发送的数据
    3. TX 特征值 (发送): 6E400003-..., 模式为 NOTIFY,每秒向手机/电脑递增推送一个字节数据
*/

#include <Arduino.h>
#include <BLEDevice.h> // BLE 核心库:设备初始化、创建 Server/Service
#include <BLEServer.h> // BLE 服务端库:管理连接与回调
#include <BLEUtils.h>  // BLE 工具库:UUID 解析等
#include <BLE2902.h>   // CCCD 描述符库:0x2902,开启 Notify/Indicate 必需

// 全局变量定义
BLEServer *pServer = NULL;            // BLE 服务端指针
BLECharacteristic *pTxCharacteristic; // TX(发送)特征值指针,loop 中需要通过它推送数据
bool deviceConnected = false;         // 当前是否处于连接状态标志位
bool oldDeviceConnected = false;      // 上一次循环时的连接状态(用于边缘触发判定)
uint8_t txValue = 0;                  // 要发送的数据(0~255 循环递增)

// 预定义 UUID(使用经典的 Nordic UART Service 官方标准 UUID)
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"           // 服务 UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // 接收特征值 UUID (WRITE)
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // 发送特征值 UUID (NOTIFY)

/**
 * @brief BLE 服务器状态改变的回调类
 * 继承自 BLEServerCallbacks,用于监听客户端(手机/电脑)的“连接”与“断开”事件
 */
class MyServerCallbacks : public BLEServerCallbacks
{
    // 当有设备成功连接到 ESP32 时自动触发
    void onConnect(BLEServer *pServer)
    {
        deviceConnected = true;
        Serial.println("Device connected"); // 串口打印:设备已连接
    };

    // 当设备与 ESP32 断开连接时自动触发
    void onDisconnect(BLEServer *pServer)
    {
        deviceConnected = false;
        Serial.println("Device disconnected"); // 串口打印:设备已断开
    }
};

/**
 * @brief RX 特征值数据接收的回调类
 * 继承自 BLECharacteristicCallbacks,用于监听客户端通过 WRITE 属性发来的数据
 */
class MyCallbacks : public BLECharacteristicCallbacks
{
    // 当客户端向 RX 特征值写入数据(Write)时自动触发
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        String rxValue = pCharacteristic->getValue(); // 读取客户端发送进来的数据字符串

        // 如果接收到的数据长度大于 0,进行打印输出
        if (rxValue.length() > 0)
        {
            Serial.println("*********");
            Serial.print("Received Value: ");
            // 逐字节打印接收到的数据(ASCII 字符形式)
            for (int i = 0; i < rxValue.length(); i++)
            {
                Serial.print(rxValue[i]);
            }

            Serial.println();
            Serial.println("*********");
        }
    }
};

void setup()
{
    Serial.begin(115200);

    // 1. 初始化 BLE 设备,设置广播名称为 "UART Service"
    BLEDevice::init("UART Service");

    // 2. 创建 BLE 服务端 (Server) 并注册连接/断开状态回调
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // 3. 在 Server 上创建具体的 BLE 服务 (Service)
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // 4.1 创建 TX(发送)特征值,设置属性为仅允许 NOTIFY(通知)
    pTxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);

    // 4.2 为 TX 特征值挂载 0x2902 描述符(CCCD),允许客户端在 App 里点击开关订阅 Notify
    // 注:如果改用 NimBLE 库,该描述符会被自动添加,不需要手动 addDescriptor
    pTxCharacteristic->addDescriptor(new BLE2902());

    // 4.3 创建 RX(接收)特征值,设置属性为仅允许 WRITE(写入)
    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);

    // 4.4 为 RX 特征值注册写入监听回调
    pRxCharacteristic->setCallbacks(new MyCallbacks());

    // 5. 启动 BLE 服务
    pService->start();

    // 6. 获取广播对象并开启广播,等待手机/电脑连接
    pServer->getAdvertising()->start();
    Serial.println("Waiting a client connection to notify...");
}

void loop()
{
    // ==================== 状态 1:客户端在线,持续推送数据 ====================
    if (deviceConnected)
    {
        Serial.print("Notifying Value: ");
        Serial.println(txValue);

        // 将 txValue (1个字节) 写入特征值底层缓冲区
        pTxCharacteristic->setValue(&txValue, 1);

        // 向已订阅的客户端发起 Notify 推送(非阻塞,不等待 ACK 确认包)
        pTxCharacteristic->notify();

        txValue++;   // 递增数据
        delay(1000); // 每秒推送一次
    }

    // ==================== 状态 2:检测到断开连接事件(下降沿) ====================
    // 条件:当前未连接 (!deviceConnected),但上一次是连接的 (oldDeviceConnected)
    if (!deviceConnected && oldDeviceConnected)
    {
        delay(500);                  // 留出 500ms 缓冲,给底层的蓝牙协议栈清理资源和句柄
        pServer->startAdvertising(); // 重新开启广播,以便其他设备或重新打开的 App 能搜到 ESP32
        Serial.println("Started advertising again...");
        oldDeviceConnected = false; // 复位标志位,防止重复触发重新广播
    }

    // ==================== 状态 3:检测到新连接建立事件(上升沿) ====================
    // 条件:当前已连接 (deviceConnected),但上一次记录的是未连接 (!oldDeviceConnected)
    if (deviceConnected && !oldDeviceConnected)
    {
        // 可以在这里做一些刚建立连接时的初始化工作(比如清空计数器、发送欢迎信息等)
        oldDeviceConnected = true; // 标记为已连接状态,防止重复触发
    }
}

现在服务器主动通知,同时接收来自客户端的消息,模拟串口通信
file

file

基于 ESP32-WROOM-32 实践 BLE 串口服务器(Write 和 Notify 综合) – PlatformIO
Scroll to top
打开目录