ESP32 使用Iphone手机发送数据给BLE蓝牙服务端

实验效果

使用Iphone手机通过蓝牙发送数据给ESP32,ESP32显示接收到的数据。

ESP32 & BLE的关键概念

  • ESP32系列支持的低功耗蓝牙(BLE)协议
    ESP32蓝牙规格:BR/EDR + Bluetooth LE v4.2
    ESP32-S3蓝牙规格:Bluetooth LE 5.0

  • 什么是 Bluetooth 和 Bluetooth LE (Low Energy)

    Bluetooth 5.0 在早期的版本基础上,增加了一些新功能和改进,包括更高的数据传输速度,更大的范围,以及更好的低功耗性能等。

    Bluetooth LE 是这些特性中的一部分,主要针对需要低功耗和长电池寿命的设备,比如健康和健身设备,安全设备和家居自动化产品等。
    Bluetooth LE 和 Bluetooth 并不是完全相同。Bluetooth 包含 Bluetooth LE,但 Bluetooth 还有更多的特性和功能。

  • ESP32开发板 BLE的兼容性
    使用Arduino IDE ESP系列开发版中内置的BLE库和例程,经测试在ESP32-S3中不能使用,需要另外安装其他BLE的库,之后,再分享一篇ESP32-S3的的蓝牙系列例子

  • 什么是蓝牙低功耗服务应用(CHARACTERISTIC)
    每个蓝牙设备都有一个服务(SERVER UUID),
    在服务之下,可以有多个不同的服务应用(CHARACTERISTIC UUID),又或者叫特性。
    服务应用也有描述和值两个属性,并且可以配置不一样的功能或作用:

    1. Broadcast 广播
    2. Read 读取
    3. Write without response 无响应写入
    4. Write 写入
    5. Notify 通知
    6. Indicate 指示
    7. Authenticated Signed Writes 鉴权签名写入
    8. Extended Properties 扩展属性

    例如,一个带蓝牙的温湿度时钟,设备就是一个服务(Server UUID),其功能读取温度是一个服务应用(CHARACTERISTIC UUID),读取湿度是另一个服务应用(CHARACTERISTIC UUID),设置闹钟时写入时间信息又是另一个服务应用(CHARACTERISTIC UUID)。

  • UUID(通用唯一标识符)
    在蓝牙设备中每个服务、服务应用和描述符都有一个 UUID。 UUID 是一个唯一的 128 位(16 字节)数字。

  • GAP和GATT

    GAP和GATT在BLE(蓝牙低功耗)通信中扮演着至关重要的角色,它们共同定义了蓝牙设备如何进行发现、连接以及数据交换的基本框架。下面是关于两者的简要概述:

    • GAP(Generic Access Profile,通用访问规则):GAP提供了设备广播自身以供其他设备发现的机制,并且规定了如何建立和维护设备间的连接。GAP提供两种主要的设备角色,它们共同构成了蓝牙设备的通信网络。

    • 外围装置(Peripheral):这类设备通常是体积小、功能单一的设备,它们充当数据的提供者。外围装置通常也被称为“从模式-Slave”或“服务端(server)”。它们特征是低功耗和较小的处理能力。举例来说,手环,耳机等等外围装置。

    • 中心装置(Central):中心装置一般具有更强大的计算能力和资源,它们负责管理和连接一个或多个外围装置。中心装置也被称作“主模式-Master”或“客户端(client)”。一个常见的例子是智能手机,它可以同时连接多个外围装置,如智能手环、心率监测器等,并对这些设备提供的数据进行处理和记录。

    • GATT(Generic Attribute Profile,通用属性规则):在GAP定义的连接建立之后,GATT决定了设备之间如何进行数据交换。GATT运行在更低层次的属性协议(ATT)之上,并通过"服务"和"特性"的概念来组织和管理数据。简单地说,一个“服务”由许多“特性”组成,每个“特性”又包含了实际的数据值和一些相关的描述符(Descriptor),这些都是数据交互的基础。

    两者之间的关系可以类比为GAP是建立连接的“握手”过程,确保设备能够互相识别和连接;而GATT则是确立了一旦连接建立后,如何进行有效的数据沟通。这两个框架是BLE技术能够在如此低功耗下提供稳定、灵活的数据交换的基石。

BOM

ESP32 开发板 x1

Iphone 手机 x1

接线

只用使用USB线连接ESP32开发板,然后在电脑上传程序即可。

之后就是在手机操作连接了。

程序提点

ESP32 可以充当 BLE 服务器或 BLE 客户端。Arduino IDE 的 ESP32 BLE 库中有多个 ESP32 的 BLE 示例 。当您在 Arduino IDE 上安装 ESP32 时,默认安装该库。但正如之前说明的,这个原生的BLE库对ESP32-S3开发板不兼容。

本文主要关注 ESP32 做 BLE 服务器,使用Iphone手机通过蓝牙发送数据给ESP32。

1,加载需要的库

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

2,定义服务和特性的UUID:

接着定义了两个变量 SERVICE_UUIDCHARACTERISTIC_UUID。这两个变量是固定格式的字符串,分别表示我们要创建的BLE服务和特性的唯一标识符(UUID)。

// Service unique identification
// 服务唯一标识 
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b" 

// Characteristics unique identification
// 服务应用唯一标识
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

3, 定义回调类

MyCallbacks 类被定义为处理BLE服务应用写入时的回调,它会将写入的值输出到串行端口。

// 定义一个名为"MyCallbacks"的类,从"BLECharacteristicCallbacks"类公开继承
// 这意味着"MyCallbacks"类将继承"BLECharacteristicCallbacks"类的所有公开方法和属性。
class MyCallbacks: public BLECharacteristicCallbacks {

    // 定义一个名为"onWrite"的方法,
    // 用于处理BLECharacteristic类对象的写入操作。它接收一个指向BLECharacteristic类对象的指针作为参数。
    void onWrite(BLECharacteristic *pCharacteristic) {
      // 定义了一个字符串"value",并使用传入的服务应用获取特性的值。
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) { // 判断 value的长度是否大于0
        // 大于 0 表示 value 非空,则在串口中输出其值
        Serial.println("*********"); 
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

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

4,在Setup 中配置BLE的参数

 //  初始化BLE设备并设置其名称为"LingShunLAB"。这里可以设置成你喜欢的名称。
  BLEDevice::init("LingShunLAB");
  // 创建一个BLE服务器,并用指针pServer指向它。
  BLEServer *pServer = BLEDevice::createServer();
  // 使用SERVICE_UUID 在pServer指向的BLE服务器上创建一个服务,
  // 并用指针pService指向这个服务。
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // 在pService指向的BLE服务上开始创建一个服务应用
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,   // 定义此服务应用的CHARACTERISTIC_UUID
                                         BLECharacteristic::PROPERTY_READ | //设置这个特性的属性为可读和可写。
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
  // 为这个服务应用设置回调,当应用发生读取、写入等操作时,会调用MyCallbacks类的相应方法。
  pCharacteristic->setCallbacks(new MyCallbacks());
  // 将该服务应用的初始值设置为"Hello World"字符串。
  pCharacteristic->setValue("Hello World");
  pService->start(); // 启动pService指向的服务,使其开始工作。
  // 获取pServer指向的服务器的广告对象,并用pAdvertising指向它。
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start(); //启动广告,让BLE设备对外广播,这让其他BLE设备可以发现并连接到这个BLE服务器。

程序代码

本例程,是默认安装库里的例子,在此基础上进行一些注释,方便理解。

打开例子的路径:「File」-> 「Example」-> 「ESP32 BLE Arduino」-> 「BLE_write」

image-20240508232228477

以下完整代码是我修改了一下,并添加了一些注释:

// Welcome to Lingshunlab.com

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

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

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

// 服务唯一标识 
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b" 

// 服务应用唯一标识
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

// 定义一个名为"MyCallbacks"的类,从"BLECharacteristicCallbacks"类公开继承
// 这意味着"MyCallbacks"类将继承"BLECharacteristicCallbacks"类的所有公开方法和属性。
class MyCallbacks: public BLECharacteristicCallbacks {

    // 定义一个名为"onWrite"的方法,
    // 用于处理BLECharacteristic类对象的写入操作。它接收一个指向BLECharacteristic类对象的指针作为参数。
    void onWrite(BLECharacteristic *pCharacteristic) {
      // 定义了一个字符串"value",并使用传入的服务应用获取特性的值。
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) { // 判断 value的长度是否大于0
        // 大于 0 表示 value 非空,则在串口中输出其值
        Serial.println("*********"); 
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

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

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

  Serial.println("1- Download and install an BLE scanner app in your phone");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to MyESP32");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
  Serial.println("5- See the magic =)");

  //  初始化BLE设备并设置其名称为"LingShunLAB"。这里可以设置成你喜欢的名称。
  BLEDevice::init("LingShunLAB");
  // 创建一个BLE服务器,并用指针pServer指向它。
  BLEServer *pServer = BLEDevice::createServer();
  // 使用SERVICE_UUID 在pServer指向的BLE服务器上创建一个服务,
  // 并用指针pService指向这个服务。
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // 在pService指向的BLE服务上开始创建一个服务应用
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,   // 定义此服务应用的CHARACTERISTIC_UUID
                                         BLECharacteristic::PROPERTY_READ | //设置这个特性的属性为可读和可写。
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
  // 为这个服务应用设置回调,当应用发生读取、写入等操作时,会调用MyCallbacks类的相应方法。
  pCharacteristic->setCallbacks(new MyCallbacks());
  // 将该服务应用的初始值设置为"Hello World"字符串。
  pCharacteristic->setValue("Hello World");
  pService->start(); // 启动pService指向的服务,使其开始工作。
  // 获取pServer指向的服务器的广告对象,并用pAdvertising指向它。
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start(); //启动广告,让BLE设备对外广播,这让其他BLE设备可以发现并连接到这个BLE服务器。
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

Iphone连接ESP32 BLE发送数据

已经确保把程序上传成功后,就可以继续以下iphone手机连接蓝牙的操作:

1,下载可连接BLE的APP

在这个例子中使用Iphone系统的蓝牙搜索是无法找到ESP32蓝牙服务端的,需要下载一个「LightBlue」的App进系连接和操作。

打开「App Store」在搜索框中输入「lightblue」,找到如下App,进行安装

image-20240509145327473

2,打开App,查找ESP32的BLE,名称为「LingShunLAB」

image-20240510022654808

3,点击Write应用服务,发送数据到ESP32 BLE服务端

image-20240510022834084

这里发送的是HEX(16进制的数据),例如:
"LingShunLab" 通过ASCII 码表对照转换成如下:
"76 105 110 103 83 104 117 110 76 65 66",这段10进制再转换成16进制如下:
"4c 69 6e 67 53 68 75 6e 4c 41 42",但在App上输入的不能有空格,最后在APP输入为
"4c696e675368756e4c4142",按下「Done」发送即可。

4,ESP32 BLE服务端收到数据并在PC的串口显示

此时,ESP32 将会收到Iphone发送过来的数据,并且在串口中显示。

image-20240510022925785