(摘) Esp32 与 BLE

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

官方资料 适合先看。 这里还有PDF文档

蓝⽛牙系统分为两种不不同的技术:经典蓝⽛牙 (Classic Bluetooth) 和蓝⽛牙低功耗 (Bluetooth Low Energy)。ESP32 ⽀支持双模蓝⽛牙,即同时⽀支持经典蓝⽛牙和蓝⽛牙低功耗。BLE隶属于蓝牙4.0协议的一部分。

版本 时间 说明
3.0 + HS 2009.4.21 交替射频技术、802.11协议适配层、电源管理、取消了UMB的应用
4.0+BLE 2010.6.30 低功耗物理层和链路层、AES加密、Attribute Protocol(ATT)、Generic Attribute Profile(GATT)、Security Manager(SM)重要的特性是省电
4.1 2013.12 1)与4G不构成干扰;2)通过IPV6连接到网络。3)可同一时候发射和接收数据。
4.2 2014
5.0 在低功耗技术(BLE)可提供2倍的传输速率、8倍的广播能力、4倍的覆盖范围

ESP32 里的蓝牙协议栈是符合蓝牙4.2协议规范的

BLE 角色划分

在 BLE 协议栈,不同的层级里面有不同的角色划分:

LL:可以把设备分为主机和从机,从机广播,主机发起连接;

GAP:可以把设备分为中心设备和外围设备;

GATT:可以把设备分为服务端和客户端;

这些划分相互是不受影响的,在这里,我们只讨论 GATT 层的角色。

GATT 其实是一种属性传输协议,简单的讲可以认为是一种属性传输的应用层协议,这个属性的结构其实非常简单,其实也就是由一个个 services 组成,每个 service 又由数量不等的 characteristic 组成,每个characteristic 又由很多其它的元素组成

一个鼠标是一个BLEDevice,一个BLEDevice建立了一个BLE服务器 BLEServer

一个BLE服务器里有多个服务BLEService

一个服务里有多个特征值BLECharacteristic 每个特征值是一种数据.就是通过读写这些“Characteristic”实现读写数据

居然看到一个大型脑图,很厉害的样子,先贴再消化。

BLE的核心部份:

在链路层定义了蓝牙的五种状态:

就绪态(Standby) 不收发报文,默认状态

广播态(Advertising) 可以发送报文,也可以监听。

扫描态(Scanning) 能接收广播报文。分为主动扫描和被动扫描

初始态(Initiating) 可以发起连接请求

连接态(Connection) 数据传输状态

BLE ⾥⾯的数据以属性 (Attribute) ⽅式存在,每条属性由四个元素组成:

属性句柄 (Attribute Handle):

属性类型 (Attribute UUID):

属性值 (Attribute Value):

属性许可 (Attribute Permissions):可读/可写

UUID

服务和characteristic是通过UUID来进行识别的

这是一个UUID生成网站

notify通知

如果主机的一个特征值characteristic发生改变, 可以使用通知notify来告诉客户端. 这是服务器主动给客户端发的信息


示例

ESP32 BLE服务器

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"   // 服务ID
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"   // 功能ID

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("Long name works now");  // BLE设备名
  BLEServer *pServer = BLEDevice::createServer();  // 服务器
  BLEService *pService = pServer->createService(SERVICE_UUID);  // 创建服务
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(   // 服务中的一项特征,定义它的属性
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE  // 属性可读可写
                                       );

  pCharacteristic->setValue("Hello World says Neil");   // 创建特征后,可以写入一个值 
  pService->start();  // 开启服务
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID); // 加入功能
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // 有助于解决iPhone连接问题的功能
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("特性定义完成!可以手里连接!");
}

void loop() {
  delay(2000);
}

通过手机APP连接,会看到一些附加的服务。例如读取名字等,暂时不用管它。

在未知服务一栏中,列出了SERVICE_UUID。

例子实现了:写入什么,就可以通过服务读取出来。

步骤:

  1. 创建服务器 BLEDevice::init(ble_name);

  2. 创建服务 BLEServer *pServer = BLEDevice::createServer();

  3. 创建若干服务 BLEService *pService = pServer->createService(SERVICE_UUID);

  4. 在服务上创建特征

  5. 对特征进行描述

  6. 启动服务

  7. 开始广播

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <String.h>

BLECharacteristic *pCharacteristic; //创建一个BLE特性pCharacteristic
bool deviceConnected = false;       //连接否标志位
uint8_t txValue = 0;                //TX的值
long lastMsg = 0;                   //存放时间的变量
String rxload = "BlackWalnutLabs";  //RX的预置值

#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)
  {
    std::string rxValue = pCharacteristic->getValue();
    if (rxValue.length() > 0)
    {
      rxload = "";
      for (int i = 0; i < rxValue.length(); i++)
      {
        rxload += (char)rxValue[i];
        Serial.print(rxValue[i]);
      }
      Serial.println("");
    }
  }
};

void setupBLE(String BLEName)
{
  const char *ble_name = BLEName.c_str(); //将传入的BLE的名字转换为指针
  BLEDevice::init(ble_name);              //初始化一个蓝牙设备

  BLEServer *pServer = BLEDevice::createServer(); // 创建一个蓝牙服务器
  pServer->setCallbacks(new MyServerCallbacks()); //服务器回调函数设置为MyServerCallbacks

  BLEService *pService = pServer->createService(SERVICE_UUID); //创建一个BLE服务

  pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY); 
//创建一个(读)特征值 类型是通知
  pCharacteristic->addDescriptor(new BLE2902());
//为特征添加一个描述

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
 //创建一个(写)特征 类型是写入
  pCharacteristic->setCallbacks(new MyCallbacks());
//为特征添加一个回调

  pService->start();                  //开启服务
  pServer->getAdvertising()->start(); //服务器开始广播
  Serial.println("Waiting a client connection to notify...");
}
void setup()
{
  Serial.begin(115200);
  setupBLE("ESP32BLE"); //设置蓝牙名称
}

void loop()
{
  long now = millis(); //记录当前时间
  if (now - lastMsg > 1000)
  { //每隔1秒发一次信号
    if (deviceConnected && rxload.length() > 0)
    {
      String str = rxload;
      if (str=="10086")
      {
        const char *newValue = str.c_str();
        pCharacteristic->setValue(newValue);
        pCharacteristic->notify();
      }
    }
    lastMsg = now; //刷新上一次发送数据的时间
  }
}

实现了一个写入和获取,其它不重要。

BLE 扫描

#include <BLEDevice.h> // 蓝牙Ble设备库
#include <BLEUtils.h> 
#include <BLEScan.h> // 蓝牙ble设备的扫描功能库
#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(); // 创建一个新扫描入口
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());// 注册扫描结果回调
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster // 配置主动扫描
  pBLEScan->setInterval(100); // 配置扫描PDU间隔
  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();   // 删除结果,清除内存
  delay(2000);
}

不过在众多代码中,这个设备数量始终为0,不知为何。

BLE发通知

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};



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

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // 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
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
    // notify changed value
    if (deviceConnected) {
        pCharacteristic->setValue((uint8_t*)&value, 4);
        pCharacteristic->notify();
        value++;
        delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
    }
    // 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;
    }
}

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) {
      std::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("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(10); // 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;
    }
}

可能会用到的相关网文

菜鸟哥玩蓝牙Ble4.0系列

ESP32 Ble - 微信小程序蓝牙数据通信测试

ESP32 BLE蓝牙 微信小程序通信发送大于20字符数据

参考小程序代码:

 //要发送的字符串(要在起始位置添加起始字符,结束位置添加结束字符)
    let order = that.stringToBytes(recs);
    let byteLen = order.byteLength;//长度
    let pos = 0;       //字符位置
    let tempBuffer;   //一次发送的数据
    var i = 0;          //计数

//为了安全每次发送18个字节 (每次最多20个)
//发送之前
    while (byteLen > 0) {
      i++;
      if (byteLen > 18) {
        tempBuffer = order.slice(pos, pos + 18);
        pos += 18;
        byteLen -= 18;
        console.log("第", i, "次发送:", tempBuffer);
        that.writeBLECharacteristicValue(tempBuffer);
      } else {
        tempBuffer = order.slice(pos, pos + byteLen);
        pos += byteLen;
        byteLen -= byteLen;
        console.log("第", i, "次发送:", tempBuffer);
        that.writeBLECharacteristicValue(tempBuffer);
      }
    }
    console.log("发送结束");

ESP32接收端参考代码

   void onWrite(BLECharacteristic *pCharacteristic) {
        std::string rxValue = pCharacteristic->getValue();

        if (rxValue.length() > 0) {
            
            for (int i = 0; i < rxValue.length(); i++){  
                
                //防止 意外字符串过长
                if(bleReslen>1024){
                   bleReslen=0; 

                }

                //开始标志
                if((int)rxValue[i]==ASCII_STR){//ASCII  起始符
                    bleReslen=0;       

                //结束标志
                }else if((int)rxValue[i]==ASCII_END){//ASCII  结束符
                    extractData();//提取数据结束  
                    bleReslen=0;

                }else{
                   //拼接字符串
                    encBefor[bleReslen]=(char)rxValue[i];//赋值
                    bleReslen++;

                }
                           
            }
        }
      
    }        

相关文章