本帖最后由 HonestQiao 于 2024-5-21 18:50 编辑
一、BLE功能了解
ESP32-C6搭载160MHz的高性能RISC-V 32位处理器,支持Wi-Fi 6、Bluetooth 5、Zigbee 3.0、Thread 1.3通讯协议,可接入多种通讯协议的物联网网络。
其中的Bluetooth 5,是蓝牙标准第5代,和Bluetooth 4对比如下:
从上面的对比可以看到,Bluetooth 5比Bluetooth 4提升了一大截。
乐鑫的esp-idf,也为ESP32-C6提供了完善的低功耗蓝牙(BLE)支持能力。
二、BLE扫描功能使用
为了方便分享,本篇分享的实例,使用了友好的Arduino开发平台。
在Arduino的ESP32支持库Arduino-ESP32中,有BLE能力的支持库:https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE
BLE的扫描功能,主要用于设备发现,通过周边BLE设备的广播信息,获取BLE设备的基础信息。
要使用设备发现功能,首先需要调用几个关键的库文件:
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
上面的库文件,提供了BLE基本功能支持,扫描功能支持,和设备广播信息支持。
然后实例化一个BLE设备对象:
BLEScan *pBLEScan;
在开启扫描功能:
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
同时,还要设置一个广播信息获取后的回调,以便把扫描到的BLE谁被信息输出,具体如下:
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
上面代码中,定一个基类BLEAdvertisedDeviceCallbacks的类MyAdvertisedDeviceCallbacks,其中的onResult用于接收扫描到的设备信息。
然后,启动扫描功能即可进行扫描和输出信息:
BLEScanResults *foundDevices = pBLEScan->start(5, false);
Serial.println(foundDevices->getCount()); // 获取扫描到的BLE设备数量
pBLEScan->clearResults(); // 清理扫描结果
完整的代码如下:
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
int scanTime = 5; //In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
在Arduino中,按照下面的设置,进行编译烧录:
烧录后,打开串口监控,可以看到扫描输出信息:
上面输出中的L4260,就是我家打印机了。
三、BLE UART功能使用
BLE设备可以通过GATT的GAP定义自身的角色,例如:外围设备(Peripheral)和中心设备(Central)。
GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范。
GAP定义了设备如何彼此发现、建立连接以及如何实现绑定,同时描述了设备如何成为广播者和观察者,并且实现无需连接的传输。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。
当做为外围设备(Peripheral),可以对外发布自身能够提供的服务。其中,最常见的一项服务是UART服务,可以通过低功耗蓝牙,实现乐思串口通讯的能力。UART服务最早是Nordic Semiconductor 的 BLE 模组提供的自定义服务Nordic UART Service,现在只要遵循其标准定义,那么都可以提供该服务。
通过BLE对外提供服务,主要需要定义服务(Service) 和 标签(Characteristic),以便告知其他设备,自己能够提供的服务和具体的功能:
而在UART服务中,Service包含两个Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。
通常情况下,Service和Characteristic都是一个UUID,UART对应的如下:
- UART Service:6E400001-B5A3-F393-E0A9-E50E24DCCA9E
- RX标签:6E400002-B5A3-F393-E0A9-E50E24DCCA9E,表示可以从该标签接收到信息
- TX标签:6E400003-B5A3-F393-E0A9-E50E24DCCA9E ,表示可以向改标签写入信息
要在ESP32-C6上提供UART 服务,需要按照以下的步骤进行:
- 创建BLE服务端
- 创建BLE服务
- 创建BLE服务对应的标签
- 设置标签对应的描述信息
- 启动服务
- 启动广播
参考官方的实例,测试代码如下:
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32-C6 UART Service");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
txValue++;
delay(1000); // bluetooth stack will go into congestion, if too many packets are sent
pTxCharacteristic->setValue("\nESP32-C6\n");
pTxCharacteristic->notify();
delay(1000); // bluetooth stack will go into congestion, if too many packets are sent
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
在上述代码中的setup()部分,按照以上步骤,进行了对应的服务的创建,标签的创建,以及服务和广播的启动。
另外,还定义了两个回调类,分别是MyServerCallbacks用于处理蓝牙连接状态,MyCallbacks用于处理收到信息时的处理。
在loop()循环调用中,则进行了信息发送的处理,以及根据连接情况决定是否启动广播的处理。
在Arduino中编译烧录后,串口监听输出如下:
表示ESP32-C6启动了广播,可以用手机或者电脑连接获取服务了。
此时,用前面的扫描程序,可以扫描到:
从上图中可以看到,找到了 ESP32-C6 UART Service 了。
此时,用BLE工具,可以进行连接,就能够收到对应的信息了:
在上面代码的loop()中,有两个设置发送信息的处理:
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
pTxCharacteristic->setValue("\nESP32-C6\n");
pTxCharacteristic->notify();
第一个表示发送数值,大小为1字节;第二个表示发送一个字符串。
把BLE工具的接收HEX打开,就可以看到发送的数值信息了:
然后,在BLE工具中,给ESP32-C6发送信息:
串口监控就会收到发送的信息了:
四、总结
刚开始用BLE功能时,觉得基于GATT的处理过程很麻烦,但是一旦用上了后,会非常的好用,因为已经有很多标准化的处理了,符合标准定义的设备,可以轻松的进行互联和通信。
如果你的设备,按照标准定义来广播Service和通过Characteristic提供对应的实际功能,那么其他符合这个标准的设备,就能够快速连接获取数据或者发送数据。
现在有很多BLE低功耗设备,如心率传感器、手环、温度传感器、光纤传感器等采用,你不用了解它具体的工作细节,仅需要通过BLE扫描发现对应的BLE设备和服务,然后就可以通过标准定义的Characteristic来获取数据了,非常的方便。