跳到主要内容

ArduinoBLE 库的使用

ArduinoBLE 是 Arduino 官方提供的用于低功耗蓝牙(BLE)通信的库。该库支持所有硬件支持 BLE 和 Bluetooth 4.0 及以上版本的 Arduino 开发板,包括:

  • Nano 33 BLE - 基于 Nordic nRF52840 的开发板
  • Arduino NANO 33 IoT - 集成 WiFi 和 BLE 的开发板
  • Uno WiFi Rev 2 - 带有 WiFi 和 BLE 功能的 Uno 开发板
  • MKR WiFi 1010 - MKR 系列开发板,支持 WiFi 和 BLE

使用方法

要使用 ArduinoBLE 库,首先需要在代码中包含库的头文件:

#include <ArduinoBLE.h>

BLE 快速入门

什么是 BLE?

Bluetooth 4.0 标准包含两种技术:传统的蓝牙(现在称为 "Bluetooth Classic")和低功耗蓝牙(Bluetooth Low Energy,简称 BLE)。BLE 针对低功耗和低数据速率进行了优化,设计用于从简单的纽扣电池供电运行。

BLE 通信模型

与基于异步串行连接(UART)的标准蓝牙通信不同,BLE 的通信模型更像是一个社区公告板。连接到它的设备就像社区成员阅读公告板一样。每个设备可以充当公告板(发布者)或读者(订阅者)。

  • 外设设备(Peripheral):充当公告板,发布数据供其他设备读取。在 BLE 术语中,外设设备类似于服务器,因为它们包含读者设备请求的信息。
  • 中心设备(Central):充当读者,从外设设备读取信息。在 BLE 术语中,中心设备类似于客户端,因为它们从外设读取可用信息。

img

可以将 BLE 外设设备想象成公告板,中心设备是查看者。中心设备查看服务、获取数据,然后离开。每次交互都很快(几毫秒),因此多个中心设备可以从一个外设获取数据。

服务和特征

外设提供的信息以 服务(Services) 的形式组织,每个服务又细分为 特征(Characteristics)。可以将服务想象成公告板上的通知,特征则是这些通知的各个段落。

  • 外设设备:只需在需要时更新每个服务特征,无需担心中心设备是否读取它们。
  • 中心设备:连接到外设后,读取所需的数据。
  • 读写权限:如果某个特征既可读又可写,那么外设和中心都可以修改它。

Notify 通知机制

BLE 规范包含一种称为 notify(通知) 的机制,可以在数据更改时通知接收方。当启用特征的通知功能并且发送方写入新值时,新值会自动发送给接收方,而无需接收方显式发出读取命令。这通常用于流式数据,例如加速度计或其他传感器读数。

indicate(指示) 是 notify 的一个变体,工作方式类似,但在 indicate 规范中,读取方会发送对推送数据的确认。

BLE 的客户端-服务器结构,结合 notify 特征,通常称为 发布-订阅模型(publish-and-subscribe model)

更新特征值

外设应该在特征值发生显著变化时更新它们。例如:

  • 当开关从关闭变为打开时,更新其特征值
  • 当模拟传感器变化达到显著程度时,更新其特征值

虽然可以按固定间隔更新特征值,但如果特征值没有变化,这会浪费处理能力和能量。因此,建议只在值发生有意义的变化时才更新特征。

中心设备和外设设备

  • 中心设备(Central):充当客户端。它们从外设设备读取和写入数据。
  • 外设设备(Peripheral):充当服务器。它们提供来自传感器的可读特征,并提供可读/可写特征来控制执行器(如电机、灯光等)。

在实际应用中:

  • 外设设备:通常是传感器节点、IoT 设备等,负责采集数据并提供给中心设备
  • 中心设备:通常是手机、平板电脑或主控制器,负责读取数据并控制外设

服务、特征和 UUID

BLE 外设提供 服务(Services),每个服务又提供 特征(Characteristics)。您可以定义自己的服务,或使用 标准服务

服务通过称为 UUID(通用唯一标识符) 的唯一数字进行标识。UUID 在其他上下文中也很常见。

  • 标准服务:使用 16 位 UUID(例如:0x180F 表示电池服务)
  • 自定义服务:使用 128 位 UUID(例如:19B10000-E8F2-537E-4F6C-D104768A1214

定义服务和特征的能力取决于您使用的无线模块及其固件。大多数现代 BLE 模块都支持自定义服务。

服务设计模式

特征值的最大长度为 512 字节。这是设计服务时的关键约束。考虑到这个限制,您应该考虑如何最有效地为应用程序存储传感器和执行器的数据。

模式 1:一个特征对应一个值(简单模式)

最简单的设计模式是为每个传感器或执行器值使用一个特征,使用 ASCII 编码值:

特征
Accelerometer X200
Accelerometer Y134
Accelerometer Z150

优点

  • 最简单,易于开发和调试
  • 每个值独立,易于理解

缺点

  • 内存占用最大
  • 读取时间最长(需要多次读取操作)

模式 2:组合多个值到一个特征(高效模式)

当某个传感器或执行器有多个关联值时,可以将读数组合到单个特征中:

特征
Motor Speed, Direction150,1
Accelerometer X, Y, Z200,133,150

优点

  • 更高效,减少读取次数
  • 节省内存和带宽

缺点

  • 需要解析数据(例如使用逗号分隔)
  • 需要注意不要超过 512 字节限制

例如,上面的加速度计特征作为 ASCII 编码字符串需要 11 字节("200,133,150")。

模式 3:二进制编码(最紧凑模式)

对于需要更高效率的应用,可以使用二进制编码而不是 ASCII:

特征值(二进制)大小
Accelerometer X, Y, Z[0xC8, 0x86, 0x96]3 字节

这种方式最紧凑,但需要中心设备和外设设备都支持相同的二进制格式。

读取/写入/通知/指示

中心设备可以对特征执行 4 种操作:

  • Read(读取):请求外设发送特征的当前值。通常用于不经常变化的特征,例如用于配置、版本号等的特征。
  • Write(写入):修改特征的值。通常用于类似命令的操作,例如告诉外设打开或关闭电机。
  • Notify(通知):请求外设持续发送特征的更新值,而无需中心设备不断请求。外设发送数据后不需要确认。
  • Indicate(指示):与 Notify 类似,但外设发送数据后需要中心设备确认。

使用场景对比

操作适用场景示例
Read不经常变化的数据设备版本号、配置参数
Write控制命令开关电机、设置参数
Notify频繁变化的流式数据加速度计、温度传感器
Indicate需要确认的重要数据警报、状态变化

广播和 GAP

BLE 设备通过使用 通用广播配置文件(General Advertising Profile,GAP) 进行广播,让其他设备知道它们的存在。广播数据包可以包含:

  • 设备名称
  • 其他信息(如发射功率、设备类型等)
  • 提供的服务列表

广播数据包限制

广播数据包的大小有限制(通常为 31 字节)。您只能在数据包中放入一个 128 位服务 UUID。确保设备名称不要太长,否则可能连设备名称都放不下。

优化建议

  • 设备名称控制在 8-12 个字符以内
  • 优先广播最重要的服务 UUID
  • 使用 16 位标准 UUID 可以节省空间

非广播服务

您可以提供未在广播中声明的附加服务。中心设备将通过连接/绑定过程了解这些服务。但是,非广播服务不能用于发现设备。

使用场景

  • 您可能有一个带有自定义服务的外设设备
  • 在中心设备应用程序中,您可能知道它还提供电池服务和其他标准服务
  • 这些标准服务不需要在广播中声明,因为它们可以通过连接后查询获得

GATT 协议

BLE 协议在多个层上运行。通用属性配置文件(General Attribute Profile,GATT) 是定义服务和特征并启用对它们的读取/写入/通知/指示操作的层。

GATT 服务器和客户端

在了解 GATT 时,您可能会遇到 GATT 的"服务器"和"客户端"概念。这些并不总是对应于中心设备和外设设备。但在大多数情况下:

  • 外设设备 = GATT 服务器(因为它提供服务和特征)
  • 中心设备 = GATT 客户端(因为它读取和写入数据)

GATT 层次结构

GATT Server (外设)
├── Service 1 (服务 1)
│ ├── Characteristic 1.1 (特征 1.1)
│ │ ├── Descriptor 1.1.1 (描述符 1.1.1)
│ │ └── Descriptor 1.1.2 (描述符 1.1.2)
│ └── Characteristic 1.2 (特征 1.2)
└── Service 2 (服务 2)
└── Characteristic 2.1 (特征 2.1)

说明

  • 服务(Service):相关功能的集合
  • 特征(Characteristic):实际的数据值
  • 描述符(Descriptor):特征的元数据(如单位、格式等)

库结构

ArduinoBLE 库提供了多个类来实现不同类型的 BLE 功能。以下是主要的类及其用途:

主要类

  • BLE:用于启用和初始化 BLE 模块。这是使用库的入口点,需要先调用 BLE.begin() 来启动 BLE 功能。

  • BLEDevice:用于获取扫描时发现或连接的设备信息。当作为中心设备扫描外设时,使用此类来访问设备信息(如设备名称、地址、RSSI 等)。

  • BLEService:用于启用开发板提供的服务,或与远程开发板提供的服务进行交互。每个服务包含一个或多个特征。

  • BLECharacteristic:用于启用开发板在服务中提供的特征,或与远程开发板提供的特征进行交互。这是实际存储和传输数据的地方。

  • BLEDescriptor:用于描述开发板提供的特征。描述符包含特征的元数据,如单位、格式、描述等。

使用流程示例

作为外设设备(Peripheral)

#include <ArduinoBLE.h>

BLEService myService("19B10000-E8F2-537E-4F6C-D104768A1214");
BLECharacteristic myCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214",
BLERead | BLEWrite, 20);

void setup() {
BLE.begin(); // 初始化 BLE
BLE.setLocalName("MyDevice"); // 设置设备名称
BLE.setAdvertisedService(myService); // 广播服务
myService.addCharacteristic(myCharacteristic); // 添加特征到服务
BLE.addService(myService); // 添加服务
BLE.advertise(); // 开始广播
}

作为中心设备(Central)

#include <ArduinoBLE.h>

void setup() {
BLE.begin(); // 初始化 BLE
BLE.scan(); // 开始扫描外设
}

void loop() {
BLEDevice peripheral = BLE.available(); // 检查是否有可用设备
if (peripheral) {
// 连接到设备并读取特征
}
}

应用场景

  • IoT 传感器节点:使用外设模式,定期广播传感器数据
  • 智能家居控制:使用中心模式,连接并控制多个智能设备
  • 可穿戴设备:使用外设模式,传输健康监测数据
  • 数据采集系统:使用中心模式,从多个传感器节点收集数据
阿基米东
📝 作者:阿基米东

GetIoT.tech 创始人,独立开发者,Linux 重度用户,开源软件作者,创业者,INTJ