- 1. BLE 服务器和客户端
- 2. GATT 协议
- 3. UUID
- 4. 项目概述
- 4.1 材料清单
- 4.2 ESP32 BLE 服务器
- 4.3 ESP32 BLE 客户端
- 参考资料
1. BLE 服务器和客户端
使用蓝牙低功耗,有两种类型的设备:服务器和客户端。ESP32 既可以作为客户端,也可以作为服务器。
服务器 宣传它的存在,因此它可以被其他设备发现并包含客户端 可以读取的数据。客户端扫描附近的设备,当它找到它正在寻找的服务器时,它会建立连接并监听传入的数据。这称为点对点通信(还有其他可能的通信模式,如广播模式和网状网络)。
待确定:谁是服务器,谁是客户端?(滑板) (手柄)
2. GATT 协议GATT (Generic Attribute Profile) 代表通用属性,它定义了向连接的 BLE 设备公开的分层数据结构。这意味着 GATT 定义了两个 BLE 设备发送和接收标准消息的方式。理解这个层次结构很重要,因为它可以更容易地理解如何将 BLE 与 ESP32 一起使用。
- 服务集合 Profile: 针对特定用例的标准服务集合;
- 服务 Service: 收集相关信息,如传感器读数、电池电量、心率等;
- 特征 Characteristic: 它是实际数据保存在层次结构(值)上的位置;
- 描述 Descriptor: 关于数据的元数据;
- 属性 Properties: 描述如何与特征值交互。例如:读、写、通知、广播、指示等。
在示例中,我们将创建一个具有两个特征的服务:
- 一个用于温度
- 一个用于湿度。
实际温度和湿度读数保存在其特性值上。每个特征都有notify属性,以便在值更改时通知客户端。
3. UUID每个 服务 、特征 和 描述符 都有一个 UUID(通用唯一标识符)。UUID 是唯一的 128 位(16 字节)数字。例如:
55072829-bc9e-4c53-938a-74a6d4c78776
如果您的应用程序需要自己的 UUID,您可以使用此UUID 生成器网站生成它。
总之,UUID 用于唯一标识信息。例如,它可以识别蓝牙设备提供的特定服务。
4. 项目概述
在本教程中,您将学习如何在两个 ESP32 板之间建立 BLE 连接。一个 ESP32 将作为 BLE 服务器,另一个 ESP32 将作为 BLE 客户端。
本项目分为两部分:
- 第 1 部分 – ESP32 BLE 服务器 (手柄)
- 第 2 部分 – ESP32 BLE 客户端(滑板)
Parts Required
ESP32 BLE 服务器:
- ESP32 WROOM 32开发板
- 微型指拨摇杆开关 C2685355
- 0.96 OLED液晶屏
- ESP32 扩展板
ESP32 BLE 客户端:
-
ESP32 WROOM 32开发板
-
BLDC三相直流无刷直流电机控制器
-
ESP32 扩展板
4.2 ESP32 BLE 服务器
在这一部分中,我们将设置 BLE 服务器来宣传包含两个特征的服务:一个是PWM信号值,另一个是DIR方向信号。这些特征具有 Notify通知属性 以将新值通知给客户端。
遇到一个问题,当 ESP32 服务器端加入OLED屏幕显示部分程序,ESP32服务器端和ESP32客户端不能蓝牙配对,连接不上。注释掉
//Server V.05.10 #include#include #include #include #include // 使用I2C库 ESP32 GPIO22(SCL) GPIO21(SDA) #include #include #define SCREEN_WIDTH 128 // 使用的是 128×64 OLED 显示屏 #define SCREEN_HEIGHT 64 // BLE 服务器名称 服务器客户端的名称必须匹配 #define bleServerName "ESP32-S-test" const int ADC_PIN = 34; // ADC GPIO 34 (Analog ADC1_CH6) 电源 const int ADC_RS_Y_PIN = 35; // Rocker switch forward&back GPIO 35 (ADC1_CH7) (摇杆-Y-前后) const int DIR_PIN = 26; // Direction GPIO 26 const int BUT_D_PIN = 18; // Button down GPIO 18 (按键-下) // 定义电源ADC相关变量 GPIO34 int adcValue = 0; // 存放读取的ADC数值0~4095 // 定义摇杆Y轴ADC相关变量 GPIO35 int rsyadcValue = 0; // 存放读取的ADC数值0~4095 // 定义PWM相关变量 int dutyCycle = 0; // PWM 占空比 // 定义DIR方向相关变量 unsigned int dirState = HIGH; //BUT_D_PIN按下控制方向变化 boolean buttonState = false; // 计时器变量 unsigned long lastTime = 0; unsigned long timerDelay = 100; bool deviceConnected = false; // 设备已连接布尔变量 // 服务、以摄氏度为单位的温度特性,湿度定义UUID #define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59" // 服务 UUID // Characteristic特征 Descriptor描述 BLECharacteristic dutyCycleCharacteristic( "cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); BLEDescriptor dutyCycleDescriptor(BLEUUID((uint16_t)0x2902)); // BLECharacteristic dirStateCharacteristic( "ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); BLEDescriptor dirStateDescriptor(BLEUUID((uint16_t)0x2903)); // OLED I2C 通信协议 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);// (-1) 参数表示您的 OLED 显示器没有 RESET 引脚 //回调函数 class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true;// 客户端连接到服务器,状态为true }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; //oled 屏幕显示 void printReadings(){ display.clearDisplay();// 清除显示 display.setTextSize(1);// 设置文本大小 display.setTextColor(WHITE);// 设置文本颜色 display.setCursor(0, 5);//设置显示坐标 display.print(F("PWM: ")); display.println(dutyCycle); display.setCursor(0, 20);//设置显示坐标 display.print(F("ADC: ")); display.print(adcValue); display.print(" ");display.print(map(adcValue,0,4095,0,100)); display.println(F("%"));// 显示剩余电量百分比% display.setCursor(0, 35);//设置显示坐标 display.print(F("rsyAdc: ")); display.println(rsyadcValue); display.display(); // 屏幕上实际显示文本 } void setup() { Serial.begin(115200); pinMode(DIR_PIN, OUTPUT); digitalWrite(DIR_PIN,dirState); //GPIO26 电机方向 设置为输出模式 初始化为HIGH pinMode(BUT_D_PIN, INPUT_PULLDOWN); //GPIO18 按键按-下 // OLED 屏幕初始化 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); } // OLED 屏幕显示"BLE Server" display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE,0); display.setCursor(0,25); display.print("BLE Server"); display.display(); //使用BLE服务器名称创建一个新的BLE设备 BLEDevice::init(bleServerName); // 将 BLE 设备设置为服务器并分配回调函数 BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service bmeService BLEService *bmeService = pServer->createService(SERVICE_UUID); // 使用之前定义的服务 UUID 启动 BLE 服务 // Temperature bmeService->addCharacteristic(&dutyCycleCharacteristic); dutyCycleDescriptor.setValue("dutyCycle"); dutyCycleCharacteristic.addDescriptor(&dutyCycleDescriptor); // Humidity bmeService->addCharacteristic(&dirStateCharacteristic); dirStateDescriptor.setValue("BME humidity"); dirStateCharacteristic.addDescriptor(new BLE2902()); // 启动服务,服务器启动广告 bmeService->start(); // Start advertising BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pServer->getAdvertising()->start(); Serial.println("Waiting a client connection to notify..."); } void loop() { // ADC 电源 adcValue = analogRead(ADC_PIN); Serial.print("- ADC:");Serial.println(adcValue); // ADC 摇杆-Y-前后 rsyadcValue = analogRead(ADC_RS_Y_PIN); Serial.print("-RSY ADC:");Serial.println(rsyadcValue); // PWM 控制电机速度 dutyCycle = (map(analogRead(ADC_RS_Y_PIN),2047,4095,0,255)); Serial.print("- PWM = ");Serial.println(dutyCycle); if(dutyCycle <= 0 || dutyCycle > 255){dutyCycle = 0;} // Dir 控制电机方向 if(digitalRead(BUT_D_PIN) == HIGH && buttonState == false) { dirState = !digitalRead(DIR_PIN); //GPIO26电机方向控制 digitalWrite(DIR_PIN,dirState); // GPIO26 方向控制 输出HIGH/LOW buttonState = true; } else if (digitalRead(BUT_D_PIN) == LOW && buttonState == true) { buttonState = false; } // 蓝牙通信 if (deviceConnected) { // Serial.println("client connected."); if ((millis() - lastTime) > timerDelay) { static char dutyCycleTemp[6]; dtostrf(dutyCycle, 6, 0, dutyCycleTemp);// dtostrf()函数:将float数据转换成char型字符串 6是输出字符串的总位数;2是输出字符串小数点后的位数 dutyCycleCharacteristic.setValue(dutyCycleTemp); dutyCycleCharacteristic.notify(); static char dirStateTemp[6]; dtostrf(dirState, 6, 0, dirStateTemp); dirStateCharacteristic.setValue(dirStateTemp); dirStateCharacteristic.notify(); Serial.print(" - PWM: "); Serial.println(dutyCycleTemp); Serial.print(" - DIR: "); Serial.println(dirStateTemp); printReadings();// 屏幕显示 lastTime = millis(); } } else{Serial.println("client connection failed.");} delay(50); }
4.3 ESP32 BLE 客户端
在这一部分中,我们将设置 BLE 客户端来接收服务器来宣传的两个特征的服务:一个是PWM信号值,另一个是DIR方向信号(其实还需要有一个BK刹车信号)。并显示在OLED屏幕上。
值得注意的是,dirStateChar = (char*)pData; 接收到的是字符指针,需要将char* 型数据转换成 int 型数据。
//Client #include "BLEDevice.h" #include// 使用I2C库 ESP32 GPIO22(SCL) GPIO21(SDA) #include #include #define SCREEN_WIDTH 128 // 使用的是 128×64 OLED 显示屏 #define SCREEN_HEIGHT 64 // BLE 服务器名称和 UUID #define bleServerName "ESP32-S-test" // BLE 服务名称 // BLE 服务器名称 服务器客户端的名称必须匹配 static BLEUUID pwmServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); // bmeServiceUUID TO pwmServiceUUID static BLEUUID dutyCycleCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518"); // temperatureCharacteristicUUID TO dutyCycleCharacteristicUUID static BLEUUID dirStateCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99"); // humidityCharacteristicUUID TO dirStateCharacteristicUUID static boolean doConnect = false;// 检查是否连接到服务器的变量 static boolean connected = false; static BLEAddress *pServerAddress;// 要连接的服务器的地址,该地址将在扫描期间找到 static BLERemoteCharacteristic* dutyCycleCharacteristic;// 要读取的特性 temperatureCharacteristic TO dutyCycleCharacteristic static BLERemoteCharacteristic* dirStateCharacteristic; // humidityCharacteristic TO dirStateCharacteristic //Activate notify const uint8_t notificationOn[] = {0x1, 0x0}; const uint8_t notificationOff[] = {0x0, 0x0}; // I2C 通信协议 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);// (-1) 参数表示您的 OLED 显示器没有 RESET 引脚 char* dutyCycleChar; //服务器接收到的数据 temperatureChar TO dutyCycleChar char* dirStateChar; // humidityChar TO dirStateChar boolean newdutyCycle = false; //是否有新的数据 newTemperature TO newdutyCycle boolean newdirState = false; // newHumidity TO newdirState //连接服务器 bool connectToServer(BLEAddress pAddress) { BLEClient* pClient = BLEDevice::createClient(); pClient->connect(pAddress);//连接服务器 Serial.println(" - Connected to server"); // 获取服务器中的参数UUID BLERemoteService* pRemoteService = pClient->getService(pwmServiceUUID); if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(pwmServiceUUID.toString().c_str()); return (false); } // 获取服务器中的特征 dutyCycleCharacteristic = pRemoteService->getCharacteristic(dutyCycleCharacteristicUUID); dirStateCharacteristic = pRemoteService->getCharacteristic(dirStateCharacteristicUUID); if (dutyCycleCharacteristic == nullptr || dirStateCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID"); return false; } Serial.println(" - Found our characteristics"); //分配回调函数,处理接收到的新数据 dutyCycleCharacteristic->registerForNotify(dutyCycleNotifyCallback); dirStateCharacteristic->registerForNotify(dirStateNotifyCallback); return true; } // 检查服务器 回调函数 class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { if (advertisedDevice.getName() == bleServerName) { //检查找到的设备是否具有正确的 BLE 服务器名称 advertisedDevice.getScan()->stop(); //停止扫描 pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //获取服务器地址 doConnect = true; //标识true后开始建立连接 Serial.println("Device found. Connecting!"); } } }; //数据更新通知 回调函数 static void dutyCycleNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store dutyCycle value dutyCycleChar = (char*)pData; newdutyCycle = true; } static void dirStateNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store dirState value dirStateChar = (char*)pData; newdirState = true; // Serial.print(newdirState); } //oled 屏幕显示 void printReadings(){ display.clearDisplay(); display.setTextSize(1); display.setCursor(0,0); display.print("PWM: "); display.setTextSize(2); display.setCursor(0,10); display.print(dutyCycleChar); //display dirState display.setTextSize(1); display.setCursor(0, 35); display.print("DIR: "); display.setTextSize(2); display.setCursor(0, 45); display.print(dirStateChar); display.display(); Serial.print("- PWM:"); Serial.println(dutyCycleChar); Serial.print("- DIR:"); Serial.println(dirStateChar); } void setup() { Serial.begin(115200);// 115200 的波特率启动串行通信 // OLED 屏幕初始化 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } // OLED 屏幕显示"BLE Client" display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE,0); display.setCursor(0,25); display.print("BLE Client"); display.display(); Serial.println("Starting Arduino BLE Client application..."); BLEDevice::init(""); //初始化BLE设备 //扫描附近的设备 BLEScan* pBLEScan = BLEDevice::getScan();//pBLEScan扫描仪 pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());//设置回调函数 pBLEScan->setActiveScan(true);//启动扫描活动 pBLEScan->start(30);// 扫描30秒 } void loop() { if (doConnect == true) { if (connectToServer(*pServerAddress)) { Serial.println("We are now connected to the BLE Server."); dutyCycleCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); dirStateCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); connected = true; } else { Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again."); } doConnect = false; } //数据更新,刷新屏幕显示 if (newdutyCycle || newdirState){ newdutyCycle = false; newdirState = false; printReadings();// OLED } delay(100); }
参考资料
- [1] 【RNT】在 Arduino IDE 上开始使用 ESP32 蓝牙低功耗 (BLE)
- [2]【RNT】ESP32 BLE 服务器和客户端(蓝牙低功耗)



