3、BLE蓝牙技术基础:BLE与经典蓝牙的区别、BLE协议栈(GAP/GATT)、广播与连接、服务与特征值概念

在Arduino教育原型项目中,当推理结果需要从开发板发送到手机App或上位机时,BLE(Bluetooth Low Energy,低功耗蓝牙)往往是比经典蓝牙更合适的选择。BLE并非经典蓝牙的简单升级,而是一套面向低功耗、小数据量、间歇性传输场景重新设计的无线通信体系。理解BLE与经典蓝牙的本质差异、协议栈分层、广播与连接机制以及服务与特征值模型,是正确设计Arduino BLE应用的前提。本章将从这四个维度展开,帮助你在实际项目中做出合理的技术选型与代码设计。

经典蓝牙 (BR/EDR) BLE (低功耗蓝牙) • 功耗:~1W,持续连接 • 速率:1-3 Mbps • 拓扑:点对点/微微网 • 延迟:~100ms • 应用:音频、文件传输 • 功耗:~0.01-0.5W,休眠唤醒 • 速率:125kbps-2Mbps • 拓扑:广播/连接/mesh • 延迟:~3-10ms • 应用:传感器、控制、信标 BLE协议栈分层 应用层 (App / Arduino Sketch) GAP (Generic Access Profile) GATT (Generic Attribute Profile) L2CAP (逻辑链路控制与适配协议) HCI (主机控制器接口) + 链路层 (LL) + 物理层 (PHY)

3.1 BLE与经典蓝牙的核心差异

核心原理:两种不同的设计哲学

经典蓝牙(BR/EDR,Basic Rate/Enhanced Data Rate)和BLE虽然共享"蓝牙"这一名称,但它们在设计目标上截然不同。经典蓝牙面向持续、高速率的数据流,例如无线耳机音频、文件传输;而BLE面向低功耗、小数据包、非连续传输的物联网场景。BLE通过三种关键机制实现低功耗:极短的射频开启时间(一次传输仅需数毫秒)、深度休眠模式(连接间隙可进入睡眠)、非对称设计(从设备可以比主设备更简单、更省电)。

在Arduino项目中,这种差异直接体现在硬件选型上。例如,HC-05/HC-06模块基于经典蓝牙,适合与手机配对后做串口透传;而HM-10、CC2541、nRF52832等模块基于BLE,适合传感器数据上报、控制指令下发等场景。如果项目需要持续传输音频或大量日志,经典蓝牙更合适;如果项目由电池供电、每次只发送几十字节的推理结果(如"检测到障碍物"或"温度25.3°C"),BLE是更优选择。

对比表:经典蓝牙 vs BLE

对比维度经典蓝牙 (BR/EDR)BLE (低功耗蓝牙)
功耗约1W,持续连接0.01~0.5W,休眠唤醒
峰值速率1~3 Mbps125 kbps ~ 2 Mbps
连接建立延迟约100ms约3~10ms
数据包大小最大约1MB(可拆包)ATT层默认23字节(可协商至244字节)
拓扑结构点对点、微微网(最多7个从设备)广播、连接、mesh网络
典型应用音频耳机、文件传输、车载免提传感器、信标、智能家居、穿戴设备
Arduino常用模块HC-05、HC-06HM-10、CC2541、nRF52832

判断方法:如何选择BLE还是经典蓝牙

  1. 看数据量:单次传输超过几百字节且持续不断,选经典蓝牙;每次几十字节且间隔数秒以上,选BLE。
  2. 看供电方式:USB供电或插座供电,两者皆可;纽扣电池或锂电池供电,优先BLE。
  3. 看连接延迟要求:需要秒级甚至毫秒级快速连接,BLE明显优于经典蓝牙。
  4. 看手机兼容性:iOS和Android对BLE支持非常完善,而经典蓝牙在iOS上受限较多。
经验提醒:在Arduino教育项目中,如果只是把推理结果(如"正常"、"异常")发送到手机,BLE是默认选择。即使BLE的速率看起来不如经典蓝牙,但实际项目中BLE的2Mbps模式已经足够传输文本、JSON、甚至小型图片缩略图。

3.2 BLE协议栈:GAP与GATT的分工

核心原理:协议栈的分层协作

BLE协议栈从下到上分为物理层(PHY)、链路层(LL)、主机控制器接口(HCI)、L2CAP、属性协议(ATT)、通用访问规范(GAP)和通用属性规范(GATT)。对于Arduino开发者而言,最需要关注的是GAP和GATT这两层,因为它们直接决定了设备如何被发现、如何连接以及数据如何组织。

GAP(Generic Access Profile)负责设备的广播、扫描和连接建立。它定义了设备角色:广播者(Broadcaster)、观察者(Observer)、外围设备(Peripheral)和中心设备(Central)。在Arduino场景中,Arduino通常作为外围设备(Peripheral)广播自己的存在,手机作为中心设备(Central)扫描并连接。

GATT(Generic Attribute Profile)负责连接建立后的数据交互。它基于ATT协议,将数据组织为"服务(Service)"和"特征值(Characteristic)"的层级结构。GATT定义了两种角色:GATT服务器(提供数据)和GATT客户端(读取/写入数据)。Arduino通常作为GATT服务器,手机作为GATT客户端。

GAP与GATT的分工对比

层次职责关键操作Arduino中的体现
GAP设备发现、连接建立广播、扫描、发起连接BLEPeripheral.begin()、setLocalName()
GATT连接后的数据交互服务发现、读写特征值、通知addService()、addCharacteristic()、setValue()

案例拆解:Arduino BLE外设的GAP/GATT配置

假设我们要用Arduino Nano 33 BLE Sense采集环境温度,并通过BLE发送到手机。在Arduino代码中,GAP层的配置包括设置设备名称和广播参数,GATT层的配置包括创建服务和特征值:

// 引入BLE库
#include <ArduinoBLE.h>

// 创建自定义服务(UUID需唯一)
BLEService envService("181A"); // 环境监测服务
// 创建特征值:温度,支持读取和通知
BLEFloatCharacteristic tempChar("2A6E", BLERead | BLENotify);

void setup() {
  // GAP层配置
  BLE.begin();
  BLE.setLocalName("ArduinoTemp"); // 广播名称
  BLE.setAdvertisedService(envService); // 广播服务UUID
  
  // GATT层配置
  BLE.setAdvertisedService(envService);
  envService.addCharacteristic(tempChar);
  BLE.addService(envService);
  
  // 开始广播
  BLE.advertise();
}

void loop() {
  // 等待手机连接
  BLEDevice central = BLE.central();
  if (central) {
    while (central.connected()) {
      float temp = readTemperature(); // 读取传感器
      tempChar.writeValue(temp); // 更新特征值
      delay(1000);
    }
  }
}

这段代码中,BLE.setLocalName()和BLE.advertise()属于GAP层操作,而addService()、addCharacteristic()、writeValue()属于GATT层操作。理解这两层的分工,有助于排查连接问题和数据交互问题。

3.3 广播与连接:两种工作模式

核心原理:广播是"喊话",连接是"对话"

BLE设备有两种基本工作模式:广播模式和连接模式。广播模式下,外围设备周期性发送包含设备名称、服务UUID、厂商自定义数据等信息的广播包,任何扫描设备都能收到,但广播包最大仅31字节(扩展广播可达255字节)。连接模式下,中心设备与外围设备建立一对一的逻辑链路,双方可以双向收发数据,数据量更大、更可靠。

广播模式适合单向、无需确认的数据传输,例如信标(Beacon)推送、设备发现。连接模式适合双向交互,例如手机向Arduino发送控制指令,Arduino返回传感器数据。在Arduino项目中,广播模式常用于设备配对前的发现阶段,连接模式用于实际的数据交换。

广播与连接的工作流程

  1. 广播阶段:外围设备以固定间隔(如100ms)发送广播包,包含设备地址、设备名称、服务UUID等。
  2. 扫描阶段:中心设备持续扫描,收到广播包后解析设备信息。
  3. 连接发起:中心设备向目标外围设备发送连接请求,包含连接间隔、从设备延迟等参数。
  4. 连接建立:双方切换到连接状态,按照协商的连接间隔定期唤醒交换数据。
  5. 数据交互:通过GATT进行读写、通知等操作。
  6. 断开连接:任一方主动断开或超时断开,设备回到广播或休眠状态。
关键参数说明:连接间隔(Connection Interval)决定了两个设备之间的数据交换频率,范围7.5ms~4s。间隔越短,数据实时性越高,但功耗越大。在Arduino中,可以通过BLE.setConnectionInterval()调整。对于推理结果上报,建议设置100ms~500ms的间隔,兼顾实时性和功耗。

案例拆解:广播与连接的排障场景

某学生在项目中遇到"手机扫描不到Arduino设备"的问题。排查过程如下:

  • 初始现象:手机App使用nRF Connect扫描,列表为空。
  • 检查广播参数:确认BLE.advertise()已调用,但发现广播间隔设置过短(10ms),导致广播包冲突。
  • 检查设备名称:设备名称超过20字节,部分手机无法正确解析。
  • 检查电源:Arduino使用USB供电,但BLE模块需要3.3V,电压不稳导致射频异常。
  • 解决措施:将广播间隔调整为100ms,缩短设备名称为"ArduinoTemp",改用稳压电源供电。
  • 结果对比:调整后手机立即扫描到设备,连接成功率从30%提升至95%。
风险提示:广播间隔过短会占用大量射频资源,导致周围其他BLE设备无法正常工作;广播间隔过长则会导致手机扫描响应慢。建议广播间隔设置在50ms~200ms之间。另外,Arduino的BLE库默认使用3.3V逻辑电平,如果使用5V供电的Arduino板,需要确认模块是否兼容。

3.4 服务与特征值:GATT的数据组织模型

核心原理:UUID、服务、特征值的层级关系

GATT将数据组织为三级结构:服务(Service)、特征值(Characteristic)和描述符(Descriptor)。每个服务包含一个或多个特征值,每个特征值包含一个值(Value)和若干属性(Properties),如读、写、通知、指示等。每个服务和特征值都有一个唯一的UUID(Universally Unique Identifier)来标识。

UUID分为两种:16位标准UUID(由蓝牙SIG定义,如温度服务0x181A、电池服务0x180F)和128位自定义UUID(用于厂商自定义服务)。在Arduino项目中,如果使用标准服务,可以直接使用16位UUID;如果自定义数据格式,需要生成128位UUID。

服务与特征值的结构示例

层级示例说明
服务环境监测服务 (0x181A)包含温度、湿度、气压等特征值
特征值温度特征值 (0x2A6E)数据类型为Float,支持读取和通知
描述符客户端特征配置描述符 (0x2902)用于启用/禁用通知功能

特征值的属性与操作

  • 读(Read):客户端主动读取特征值,适合低频数据获取。
  • 写(Write):客户端向特征值写入数据,适合下发控制指令。
  • 通知(Notify):服务器主动推送数据,客户端无需请求,适合实时数据上报。
  • 指示(Indicate):类似通知,但客户端需要确认,可靠性更高。

案例拆解:推理结果通过BLE通知发送

假设Arduino运行一个简单的图像分类模型,推理结果为"cat"或"dog"。我们可以设计一个自定义服务,包含一个字符串特征值用于发送结果:

// 自定义128位UUID(示例)
const char* serviceUUID = "12345678-1234-5678-1234-56789abcdef0";
const char* resultCharUUID = "12345678-1234-5678-1234-56789abcdef1";

BLEService inferenceService(serviceUUID);
BLECharacteristic resultChar(resultCharUUID, BLERead | BLENotify, 20);

void setup() {
  BLE.begin();
  BLE.setLocalName("AIInference");
  BLE.setAdvertisedService(inferenceService);
  inferenceService.addCharacteristic(resultChar);
  BLE.addService(inferenceService);
  BLE.advertise();
}

void loop() {
  BLEDevice central = BLE.central();
  if (central) {
    while (central.connected()) {
      String result = runInference(); // 执行推理
      resultChar.writeValue(result.c_str(), result.length());
      delay(500); // 每500ms发送一次
    }
  }
}

在这个案例中,手机App作为GATT客户端,订阅resultChar的通知。当Arduino写入新值时,手机会自动收到推送,无需轮询。这种模式非常适合推理结果上报,既省电又实时。

常见误区与总结复盘

常见误区1:认为BLE连接建立后数据会自动传输。实际上,连接建立只是建立了通信链路,数据交互必须通过GATT的读写或通知操作完成。
常见误区2:忽略特征值的最大长度限制。BLE 4.0/4.1的ATT默认MTU为23字节,扣除协议头后有效载荷仅20字节。如果需要发送更长的数据,必须协商MTU或分包发送。
常见误区3:混淆GAP广播和GATT通知。广播是连接前所有设备都能收到的,通知是连接后只有配对设备能收到的。
总结复盘:BLE的核心设计思想是"按需通信"——不需要传输时进入休眠,需要传输时快速唤醒。在Arduino项目中,合理利用广播做设备发现、利用GATT服务做数据组织、利用通知做实时推送,可以构建出低功耗、高响应速度的无线传感与推理系统。建议初学者先使用nRF Connect或LightBlue等手机App调试BLE服务,确认UUID、特征值属性、数据格式正确后,再编写Arduino代码,这样可以大幅减少调试时间。