(4)蓝牙使用(Classic BT+BLE)

已写文章链接

本专栏主要内容是记录基于ESP32的开发工作,包括介绍 ESP32 基础知识、开发环境搭建、基础外设使用、蓝牙、WiFi 、与微信小程序联动等知识,达到自己动手做一些智能硬件的目的。
开发过程中主要参考官方资料,包括官网、规格书、参考手册、编程指南、驱动包等。
本专栏适合对ESP32感兴趣,想要找一个简单入手教程的同学。

ESP32 基础知识(已完成)

(1)ESP32基础知识

开发环境搭建(已完成)

(2-1)开发环境搭建(基于Arduino)
(2-2)开发环境搭建(基于VS Code+PlatformIO)
(2-3)开发环境搭建(基于ESP-IDF软件)

基础外设使用(已完成)

(3-1)基础外设使用(GPIO)
(3-2)基础外设使用(USART)

蓝牙(已完成)

(4)蓝牙使用(Classic BT+BLE)

WiFi(已完成)

(5)WiFi使用(STA模式)

微信小程序(已完成)

(6)微信小程序(配网和开关灯)


本章会介绍一下蓝牙的基础知识(参考自文心一言、乐鑫蓝牙参考手册),我们想使用微信小程序通过蓝牙与 ESP32 通信,而目前微信小程序不支持经典蓝牙,只支持 BLE,所以本章分别使用经典蓝牙和 BLE。

硬件和软件版本

本文章使用的软硬件版本如下。
硬件

  • 开发板:ESP32-DevKitC-32E
  • 模组:ESP32-WROOM-32E
  • 芯片:ESP32-DOWD-V3

软件

  • 开发环境:Arduino IDE V2.2.1
  • ESP32 软件包:V2.0.11

蓝牙基础知识

蓝牙是一种短距通信系统,其关键特性包括鲁棒性、低功耗、低成本等。蓝牙系统分为两种不同的技术:经典蓝牙(Classic Bluetooth)和蓝牙低功耗(Bluetooth Low Energy)。
BLE是蓝牙低能耗(Bluetooth Low Energy)的简称,是蓝牙技术的一个低功耗分支,诞生于2010年。它是一种无线通信技术,专为解决移动设备和周边设备间的低功耗、高效、长时间的无线通信需求而设计的。BLE协议与传统蓝牙协议不同,它在保持通信的同时,消耗的功率也非常低,因此BLE协议适用于那些需要长时间运行的低功耗设备,例如智能手表、智能手环、健身设备、家庭自动化设备等。
ESP32 支持双模蓝牙,即同时支持经典蓝牙和蓝牙低功耗。支持的蓝牙版本为 V4.2。

蓝牙 4.0 和 BLE 的关系

BLE是蓝牙4.0标准中的一个子集,也就是说,蓝牙4.0标准包含了BLE和经典蓝牙(BR/EDR)两种模式。BLE是低功耗蓝牙,而经典蓝牙则是一种传统的蓝牙技术,适用于传输音频和文件等大数据量数据。
蓝牙4.0是一个综合性协议规范,它将低功耗蓝牙和经典蓝牙两种技术融合在一起,从而提供了更广泛的应用场景和更高效的数据传输能力。在蓝牙4.0中,低功耗蓝牙和经典蓝牙可以同时运行,并且可以互相通信,从而实现了更加灵活和高效的应用。
因此,BLE和蓝牙4.0之间的关系是子集和总体的关系,BLE是蓝牙4.0标准中的一个特定技术分支。

GATT

GATT(Generic Attribute Profile)是蓝牙低功耗(Bluetooth Low Energy,BLE)协议栈中的一部分,它定义了 BLE 设备之间交换数据的格式和规范。
GATT基于属性和服务的概念,通过将数据封装在属性中,从而实现设备之间的通信。在GATT中,一个服务表示一个特定的功能,一个服务可以包含多个属性。每个属性都有一个唯一的标识符(UUID),可以用来识别它们。属性可以是只读的(Read),也可以是可写的(Write)。属性还可以包含一个描述符(Descriptor),用于描述属性的特性和值。
此外,GATT使用基于请求-响应模型的通信方式。当一个设备想要读取或写入属性时,它会发送一个请求给另一个设备,请求的格式包含要访问的属性的UUID和操作类型(读或写)。接收方设备会根据请求返回响应消息,其中包含请求的数据,或者在写入操作时返回确认消息。
BLE 里面的数据以属性(Attribute)方式存在,每条属性由四个元素组成:

  1. 属性句柄(Attribute Handle):正如我们可以使用内存地址查找内存中的内容一样,ATT 属性的句柄也可以协助我们找到相应的属性,例如第一个属性的句柄是0x0001,第二个属性的句柄是 0x0002,以此类推,最大可以到 0xFFFF。
  2. 属性类型(Attribute UUID):每个数据有自己需要代表的意思,例如表示温度、发射功率、电池等等各种各样的信息。蓝牙组织(Bluetooth SIG)对常用的一些数据类型进行了归类,赋予不同的数据类型不同的标识码(UUID)。例如0x2A09表示电池信息,0x2A6E表示温度信息。UUID可以是 16 比特的(16-bit UUID),也可以是 128 比特的(128-bit UUID)。
  3. 属性值(Attribute Value):属性值是每个属性真正要承载的信息,其他 3个元素都是为了让对方能够更好地获取属性值。有些属性的长度是固定的,例如电池属性(Battery Level)的长度只有 1个字节,因为需要表示的数据仅有 0~100%,而 1 个字节足以表示1~100的范围;而有些属性的长度是可变的,例如基于 BLE实现的透传模块。
  4. 属性许可(Attribute Permissions):每个属性对各自的属性值有相应的访问限制,比如有些属性是可读的、有些是可写的、有些是可读又可写的等等。拥有数据的一方可以通过属性许可,控制本地数据的可读写属性。

服务器和客户端的交互

我们把存有数据(即属性)的设备叫做服务器(Server),而将获取别人设备数据的设备叫做客户端(Client)。下面是服务器和客户端间的常用操作:
客户端给服务端发数据,通过对服务器的数据进行写操作(Write),来完成数据发送工作。写操作分两种,一种是写入请求(Write Request),一种是写入命令(Write Command),两者的主要区别是前者需要对方回复响应(Write Response),而后者不需要对方回复响应。
服务端给客户端发数据,主要通过服务端指示(Indication)或者通知(Notification)的形式,实现将服务端更新的数据发给客户端。与写操作类似,指示和通知的主要区别是前者需要对方设备在收到数据指示后,进行回复(Confirmation)。
客户端也可以主动通过读操作读取服务端的数据。
image.png
例如 PC 要获取传感器的数据时,PC 为 GATT Client,传感器为 GATT Server。

ESP32 蓝牙主机与控制器

从整体结构上,蓝牙可分为控制器(Controller)和主机(Host)两大部分:控制器包括了PHY、Baseband、Link Controller、Link Manager、Device Manager、HCI 等模块,用于硬件接口管理、链路管理等等;主机则包括了L2CAP、SMP、SDP、ATT、GATT、GAP以及各种规范,构建了向应用层提供接口的基础,方便应用层对蓝牙系统的访问。主机可以与控制器运行在同一个宿主上,也可以分布在不同的宿主上。
image.png

硬件连接

经典蓝牙

本章硬件无连线,使用蓝牙连接开发板和电脑,使用开发板端的蓝牙转串口,虚拟出一个串口设备。电脑端连接开发板蓝牙后,也会虚拟出一个串口,使用两个串口来通信。

软件设计

经典蓝牙

打开例程

本章使用Arduino的例程,选择示例—BluetoothSerial—SerialToSerial
image.png
打开后修改一下蓝牙名称和配对密码即可,不修改也行。
image.png
编译运行信息如下。image.png

连接蓝牙

使用电脑端的蓝牙搜索。能看到已经有了。点击连接。正常会要求输入配对密码,输入后连接。其会安装一些文件。连接成功后会虚拟出一个串口。(此处为 COM11)
image.png
image.png
image.png

打开虚拟串口

设定如下的参数后,打开 COM11。
image.png

验证功能

此时也打开Arduino的串口监视器。设定波特率 115200。
image.pngimage.png
点击串口助手中的发送按钮,Arduino的串口监视器中就出现了发送的字符了。也可以从串口监视器发送字符到串口助手,双向通信没问题。
image.png
经过以上步骤,板子就可以和电脑实现无线通信了。

完整代码

//This example code is in the Public Domain (or CC0 licensed, at your option.)
//By Evandro Copercini - 2018
//
//This example creates a bridge between Serial and Classical Bluetooth (SPP)
//and also demonstrate that SerialBT have the same functionalities of a normal Serial

#include "BluetoothSerial.h"

//#define USE_PIN // Uncomment this to use PIN during pairing. The pin is specified on the line below
const char *pin = "3625"; // Change this to more secure PIN.

String device_name = "CC-BT";

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name); //Bluetooth device name
  Serial.printf("The device with name \"%s\" is started.\nNow you can pair it with Bluetooth!\n", device_name.c_str());
  //Serial.printf("The device with name \"%s\" and MAC address %s is started.\nNow you can pair it with Bluetooth!\n", device_name.c_str(), SerialBT.getMacString()); // Use this after the MAC method is implemented
  #ifdef USE_PIN
    SerialBT.setPin(pin);
    Serial.println("Using PIN");
  #endif
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

BLE

由于微信官方文档-小程序指南中写了小程序目前不支持经典蓝牙,只支持蓝牙低功耗(BLE)。所以就有了下面 BLE 的内容。
image.png

打开例程

本章使用Arduino的例程,选择示例—ESP32 BLE Arduino—BLE_uart
image.png
看例程前面的注释,含义如下。
例程创建了一个 BLE 服务器,有三个 UUID,第一个为设备 UID,第二个用于发送带有“Notify”的数据,第三个用于接收带有“Write”的数据。
创建 BLE 服务器有以下几步:

  1. 创建 BLE Server。
  2. 创建 BLE Service。
  3. 在 BLE Service 上创建 BLE Characteristic。
  4. 在 BLE Characteristic 上创建 BLE Descriptor。
  5. 启动服务。
  6. 开始广播。

修改例程

在下图中的网址中生成 3 组 UUID。
image.png
image.png
再将 3 组 UUID 分别填入SERVICE_UUIDCHARACTERISTIC_UUID_RXCHARACTERISTIC_UUID_TX的位置,代码如下。

#define SERVICE_UUID           "2c3eaaaa-6762-4039-8902-6fd7e51b81af" // UART service UUID
#define CHARACTERISTIC_UUID_RX "4de93100-e96b-436b-a07b-21de47c15af6"
#define CHARACTERISTIC_UUID_TX "5fb4a2c3-d9cb-4f0e-9c6b-7ed3a60800be"

编译后下载。
image.png

连接蓝牙

打开手机蓝牙,安装蓝牙调试助手,我用的是下面这款。
image.png
搜索蓝牙设备。名称是代码中默认的UART Service

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

image.pngimage.png
点击即可配对。能看到SERVICE_UUIDCHARACTERISTIC_UUID_RXCHARACTERISTIC_UUID_TX是之前我们设置好的值。如果SERVICE_UUID不是设置的值,则可能是下面的原因。
image.png

验证功能

在手机端连接UART Service,再断开。会出现下面开始广播的提示。
image.png
image.png
在连接的页面选择最下面的 UUID 为4de93100-e96b-436b-a07b-21de47c15af6的特征值。然后输入要发送的字符串,点击写入。就能在 ESP32 的串口看到发送的数据了。
image.pngimage.png
image.png
也可以选择 16 进制发送。
image.pngimage.png
16 进制发送的 0x23,接收到的数值变成了“#”,这是因为此处是将收到的数据按照数据的Ascii码打印的,16 进制的 0x23 其Ascii码为“#”。
image.png
image.png
要想将收到的数据按照 16 进制打印,修改一下给打印函数的传参,编译下载,发送 16 进制 0x23 即可。

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], HEX);

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

image.png

完整代码

以下代码是在 VS Code 环境下,采用platformio插件创建的工程。

#include <Arduino.h>

/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   In this example rxValue is the data received (only accessible inside that function).
   And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#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 "2c3eaaaa-6762-4039-8902-6fd7e51b81af" // UART service UUID
#define CHARACTERISTIC_UUID_RX "4de93100-e96b-436b-a07b-21de47c15af6"
#define CHARACTERISTIC_UUID_TX "5fb4a2c3-d9cb-4f0e-9c6b-7ed3a60800be"

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], HEX);

      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;
  }
}

参考资料

玩转 ESP32 + Arduino (九) ESP32 低功耗蓝牙BEL

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尽欢_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值