2、BLE基础概念:蓝牙低功耗技术原理、GAP与GATT协议、广播与连接模式

在ESP32上开发BLE家居设备,首先需要理解BLE(Bluetooth Low Energy,蓝牙低功耗)的底层逻辑。BLE并非传统蓝牙的简单升级,而是为物联网场景重新设计的一套协议栈。它牺牲了连续数据传输速率,换取了极低的峰值功耗、毫秒级的连接建立时间和灵活的角色切换能力。本章将围绕BLE的物理层与协议栈分层、GAP(Generic Access Profile,通用访问规范)与GATT(Generic Attribute Profile,通用属性规范)两大核心协议,以及广播与连接两种工作模式展开,帮助你在ESP32上建立正确的BLE开发心智模型。

BLE协议栈分层与工作模式关系图 应用层(ESP32 用户代码 / 智能家居App) 开关控制、温度上报、电量同步、设备配网 GAP(通用访问规范) 定义设备角色:Broadcaster / Observer Peripheral / Central 广播数据格式、连接参数、配对绑定 设备发现与连接管理 GATT(通用属性规范) 定义数据交互:Service / Characteristic UUID 标识、属性读写、通知/指示 Profile 分层结构 面向连接的数据传输 广播模式(无连接) 单向数据传播,Observer 被动接收 适合信标、状态广播、低功耗传感器 广播间隔 20ms ~ 10.24s 连接模式(面向连接) 双向可靠通信,Central 发起连接 连接间隔 7.5ms ~ 4s 支持数据重传、流控、加密 物理层(PHY)与链路层(LL) 2.4GHz ISM 频段 · 40个信道(37个数据 + 3个广播) · GFSK调制 · 1Mbps / 2Mbps / 125kbps(LE Coded) 协作

上图展示了BLE协议栈的完整分层关系。物理层负责2.4GHz频段的射频收发,链路层管理信道选择、重传和加密。在链路层之上,GAP和GATT是两个平行的协议,分别解决“设备如何被发现和连接”与“连接后如何组织数据”的问题。对于ESP32开发者而言,理解这两个协议的区别至关重要:GAP处理的是设备间的“外交关系”,而GATT处理的是“商务合同”。

2.1 蓝牙低功耗技术原理:从物理层到协议栈

BLE之所以能实现低功耗,核心在于其“短突发”工作方式。传统蓝牙BR/EDR在连接后持续占用射频资源,而BLE在空闲时让射频完全关闭,仅在需要发送或接收数据时快速唤醒。这种机制使得BLE的峰值电流虽然可达15mA左右,但平均电流可以低至微安级别。

BLE在2.4GHz频段划分了40个信道,每个信道带宽2MHz。其中37个信道用于数据传输,3个专用广播信道(37、38、39)用于设备发现和连接建立。这种设计避免了与Wi-Fi的2.4GHz信道重叠干扰,同时广播信道分布在频段的不同位置,提高了抗干扰能力。

ESP32 BLE 关键参数:ESP32支持BLE 4.2规范,后续版本(如ESP32-C3、ESP32-S3)支持BLE 5.0。BLE 5.0引入了2Mbps物理层速率和LE Coded模式(125kbps/500kbps),后者通过冗余编码将通信距离提升至原来的4倍。在ESP32上,你可以通过 esp_ble_gap_set_prefer_conn_params() 调整连接参数,但物理层速率由芯片硬件决定。

BLE协议栈从下到上依次为:物理层(PHY)、链路层(LL)、主机控制接口(HCI)、逻辑链路控制与适配协议(L2CAP)、属性协议(ATT)、安全管理(SM)、GAP和GATT。在ESP32中,这些层大部分由芯片固件实现,开发者通过ESP-IDF提供的API操作GAP和GATT层即可,无需直接操作链路层。

2.2 GAP协议:设备发现与连接管理

GAP定义了BLE设备如何广播自身信息、如何被其他设备发现、以及如何建立连接。它规定了四种角色:Broadcaster(仅广播)、Observer(仅扫描)、Peripheral(从设备,可被连接)、Central(主设备,发起连接)。在智能家居场景中,ESP32通常作为Peripheral,手机App作为Central。

GAP的核心机制是广播数据包。广播包由两部分组成:广播数据(Advertising Data)和扫描响应数据(Scan Response Data)。广播数据是必须的,最多31字节;扫描响应数据是可选的,也最多31字节。当Observer收到广播包后,可以主动发送扫描请求,Peripheral则回复扫描响应数据。

广播数据类型 AD Type 值 用途 示例
Flags 0x01 声明设备支持的能力(LE、BR/EDR等) 0x06 表示支持LE且不支持BR/EDR
Complete Local Name 0x09 设备完整名称 "ESP32_Light_01"
Service UUIDs 0x03/0x05 声明设备提供的服务UUID 0xFFF0(自定义服务)
Manufacturer Specific Data 0xFF 厂商自定义数据 设备状态、电量等
Tx Power Level 0x0A 广播时的发射功率 0dBm、-4dBm等

在ESP32上配置广播数据时,需要将多个AD结构(Type + Length + Data)拼接成一个完整的广播包。例如,要广播设备名称和自定义服务UUID,可以这样构造:

// ESP32 广播数据配置示例
uint8_t adv_data[] = {
    0x02, 0x01, 0x06,             // Flags: LE General Discoverable
    0x03, 0x03, 0xF0, 0xFF,       // Complete List of 16-bit Service UUIDs: 0xFFF0
    0x0F, 0x09, 'E', 'S', 'P', '3', '2', '_', 'L', 'i', 'g', 'h', 't', '_', '0', '1'  // Name
};
esp_ble_gap_config_adv_data(sizeof(adv_data), adv_data);

GAP还定义了连接参数,包括连接间隔(Connection Interval)、从设备延迟(Slave Latency)和超时时间(Supervision Timeout)。连接间隔决定了Central和Peripheral之间的数据交换频率,间隔越短,实时性越高,但功耗也越大。从设备延迟允许Peripheral跳过若干次连接事件而不必监听,进一步降低功耗。

经验建议:对于智能家居设备,如果对实时性要求不高(如温湿度传感器),建议设置连接间隔为100ms~500ms,从设备延迟为2~4个事件。这样可以在保持连接的同时,将平均电流控制在几十微安级别。ESP32上通过 esp_ble_gap_update_conn_params() 动态调整。

2.3 GATT协议:数据组织与交互

GATT建立在ATT协议之上,定义了连接建立后数据的结构化组织方式。GATT的核心概念是Service(服务)和Characteristic(特征)。一个Service包含多个Characteristic,每个Characteristic包含一个Value和若干Descriptor(描述符)。所有数据通过UUID(Universally Unique Identifier)进行标识。

GATT定义了两种角色:GATT Server(提供数据)和GATT Client(消费数据)。在ESP32智能家居设备中,ESP32通常作为GATT Server,手机App作为GATT Client。Server维护一个属性表,Client通过读写属性来获取或设置数据。

Characteristic支持四种操作:Read(读)、Write(写)、Notify(通知)和Indicate(指示)。Notify和Indicate的区别在于:Notify不要求Client确认,数据可能丢失;Indicate要求Client发送确认,可靠性更高但多一次交互。对于状态同步场景,Notify通常足够;对于关键控制指令,建议使用Indicate或Write with Response。

// ESP32 GATT 服务定义示例(简化)
static esp_gatt_srvc_id_t service_id = {
    .id.uuid = {.len = ESP_UUID_LEN_16, .uuid = {.uuid16 = 0xFFF0}},
    .is_primary = true
};
// 特征:开关状态(可读、可写、可通知)
static esp_gatt_char_prop_t char_prop = ESP_GATT_CHAR_PROP_BIT_READ | 
                                        ESP_GATT_CHAR_PROP_BIT_WRITE | 
                                        ESP_GATT_CHAR_PROP_BIT_NOTIFY;
esp_ble_gatts_create_attr_tab(gatts_attr_tab, gatts_if, 3, 0);

GATT的Profile结构可以类比为文件系统:Service是文件夹,Characteristic是文件,Descriptor是文件的属性标签。这种分层设计使得不同厂商的设备可以互相理解数据含义,只要它们遵循相同的UUID定义。

2.4 广播与连接模式:两种工作方式的选择

广播模式(无连接)和连接模式(面向连接)各有适用场景。广播模式适合单向、低频、低功耗的数据传输,例如智能门锁的电量广播、温湿度传感器的周期性上报。连接模式适合双向、高频、需要可靠传输的场景,例如智能灯的控制与状态反馈。

广播模式的优势在于:无需配对、功耗极低、支持一对多接收。但缺点是:数据长度受限(31字节)、无确认机制、无法保证数据到达。连接模式则提供可靠传输、支持大数据量、支持加密和配对,但需要额外的连接维护开销。

常见误区:很多开发者认为广播模式一定比连接模式省电。实际上,如果广播间隔设置过短(如20ms),广播模式的功耗可能高于连接模式。广播间隔越长,数据更新越慢,但功耗越低。对于需要频繁上报数据的场景,连接模式可能更省电,因为连接事件可以高效地批量传输数据。

在ESP32上,你可以通过 esp_ble_gap_start_advertising() 启动广播,通过 esp_ble_gap_connect() 发起连接。设备可以同时处于广播和连接状态,但需要注意广播和连接会共享射频资源,可能影响性能。

2.5 实战场景:智能灯泡的BLE控制与状态同步

假设我们要设计一款ESP32智能灯泡,要求支持手机App控制开关、调节亮度,并实时同步灯泡状态。我们选择ESP32作为Peripheral和GATT Server,手机App作为Central和GATT Client。

初始目标与约束:灯泡需要支持开关和亮度调节,状态变化需要在100ms内同步到手机。同时,灯泡在待机时功耗要尽可能低,目标平均电流小于1mA。

关键设计决策:

  1. GAP角色:ESP32作为Peripheral,广播间隔设为100ms,广播数据包含设备名称和自定义服务UUID(0xFFF0)。
  2. GATT服务设计:定义两个Characteristic:开关状态(0xFFF1,可读可写可通知)和亮度值(0xFFF2,可读可写可通知)。
  3. 连接参数:连接间隔设为30ms,从设备延迟设为0,超时时间设为2s。这样保证控制指令的实时性。
  4. 功耗优化:当灯泡处于关闭状态且无操作时,通过 esp_ble_gap_update_conn_params() 将连接间隔调整为200ms,降低监听功耗。

实现过程:手机App扫描到广播后发起连接。连接建立后,App读取当前开关状态和亮度值。当用户在App上滑动亮度条时,App通过Write操作发送亮度值,ESP32收到后控制PWM输出,并通过Notify将新亮度值广播给App(实际上是通过连接发送通知)。当用户关闭灯泡时,ESP32更新状态并发送Notify,App收到后更新UI。

结果与复盘:实测控制延迟约50ms,满足100ms要求。待机时平均电流约0.8mA(连接间隔200ms),符合低功耗目标。但发现一个问题:当手机App在后台运行时,系统可能挂起BLE连接,导致状态不同步。解决方案是App在进入后台前主动断开连接,ESP32检测到连接断开后恢复快速广播,等待下次连接。

复盘经验:BLE开发中,连接参数的动态调整是功耗和实时性的关键平衡点。不要一开始就追求最低功耗,先保证功能正确,再逐步优化。同时,要处理连接断开后的重连逻辑,这是智能家居设备稳定性的重要保障。

2.6 常见误区与总结

在BLE开发中,以下误区需要特别注意:

  • 误区一:认为广播数据越多越好。广播数据过长会降低广播成功率,且Observer需要更多时间解析。建议广播数据控制在20字节以内,剩余空间留给扫描响应。
  • 误区二:忽略MTU(Maximum Transmission Unit)大小。默认MTU为23字节,其中ATT头占3字节,实际数据仅20字节。如果传输大块数据,需要协商更大的MTU(ESP32支持最大517字节)。
  • 误区三:混淆Notify和Indicate。Notify不保证送达,适合状态同步;Indicate保证送达,适合控制指令。在信号不稳定的环境中,Indicate更可靠。
  • 误区四:不处理配对和绑定。对于智能家居设备,建议启用配对和加密,防止未授权设备连接。ESP32支持LE Secure Connections,使用ECDH密钥交换。