ESP32用Arduino框架建GATT服务端需用BLEDevice、BLEServer、BLEService、BLECharacteristic四件套,关键在权限设置、回调绑定和数据类型;必须调用setEncryptionLevel、addDescriptor(new BLE2902())、start()服务,并注意MTU、write type及内存安全。

ESP32用Arduino框架建GATT服务端,核心是BLEDevice、BLEServer、BLEService、BLECharacteristic四件套
直接上手就能跑通,不用改SDK或切IDF——只要用的是Arduino-ESP32 2.0.12+(推荐2.0.16),BLEDevice::init()之后调用BLEDevice::createServer()就进入GATT服务端模式。关键不是“能不能”,而是特征值的读写权限、回调绑定、数据类型这三处容易卡住。
常见错误现象:BLECharacteristic::setValue()后手机App看不到更新;App写入数据但onWrite回调不触发;连接后立刻断连。
- 必须在
BLEDevice::init("MyDevice")之后立即调用BLEDevice::setEncryptionLevel(ESP_BLE_SEC_NONE)(除非你真要配对),否则某些Android手机会因安全策略拒绝连接 -
BLECharacteristic构造时第三个参数是属性(BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE),漏掉PROPERTY_NOTIFY就无法让App收到变化通知 - 所有
BLECharacteristic必须通过myService->addCharacteristic()挂到服务下,且服务必须用myServer->createService()创建——顺序错或漏掉service->start(),GATT表根本不会广播
手机App读不到特征值?检查notify/indicate是否启用且客户端已配置CCCD
ESP32本身不自动响应“开启通知”的请求,得靠客户端写CCCD(Client Characteristic Configuration Descriptor)才能触发通知。很多App(比如nRF Connect)点“Enable notification”时,其实是往0x2902这个描述符写了0x0100(notify)或0x0200(indicate)。
所以你得手动添加CCCD并监听写入:
立即学习“C++免费学习笔记(深入)”;
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
"11111111-2222-3333-4444-555555555555",
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->addDescriptor(new BLE2902()); // 必须加这句!否则CCCD不存在
如果你需要在CCCD被写入时做动作(比如启动定时上报),得自己捕获描述符写事件——BLE2902不提供回调,得换成自定义描述符类,重写onWrite()。
App写入数据但onWrite没触发?确认write type和MTU是否匹配
默认ESP32 BLE的write type是“write without response”,这种写法不会触发onWrite回调——它只走底层中断,不进GATT栈。手机App必须用“write with response”才能让onWrite执行。
常见场景差异:
- nRF Connect里点“Write”按钮默认是with response;长按“Write”选“Write without response”则回调静默失败
- iOS CoreBluetooth的
writeValue(_:for: type:)中type传.withResponse才触发 - 如果App发的数据超长(比如一次写512字节),而ESP32 MTU还是默认23字节,会被截断或丢弃——调用
pServer->updateConnParams(12, 12, 0, 60)前先用BLEDevice::setMTU(517)(最大支持517)
特征值存取要注意内存生命周期和线程安全
BLECharacteristic::getValue()返回的是内部缓冲区指针,setValue()传入的指针若指向局部变量(比如函数内char buf[32]),回调返回时内存已释放,App读到的是乱码。
正确做法:
- 用
static uint8_t s_value[32]或全局std::string存值(注意std::string在Arduino-ESP32中需确保启用了C++11) - 在
onWrite里用pCharacteristic->setValue(pWrite->getData(), pWrite->getLength()),别自己memcpy到临时buffer再set - 多连接时
onWrite在BLE任务上下文中执行,避免调用delay()、Serial.print()等阻塞操作;要用esp_task_wdt_reset()防看门狗复位
真正麻烦的不是建服务,是手机App每次连接行为不一致:有的缓存GATT结构,有的反复discover,有的连上就狂写CCCD。建议第一次调试时用nRF Connect,关掉“Use cached services”,强制每次都discover——这样看到的问题才是真实GATT结构问题。


















