4. 蓝牙LE协议栈基础与GAP/GATT

在nRF多协议设备中,蓝牙低功耗(Bluetooth LE)是最常用的协议之一。理解BLE协议栈的分层结构,以及GAP与GATT在其中的位置和职责,是进行广播、扫描、连接建立与数据交互的前提。本节从协议栈分层入手,逐步展开GAP角色、广播与扫描机制、连接建立过程,以及GATT服务、特征与属性表的配置方法。

BLE协议栈分层结构

BLE协议栈自下而上可分为三层:控制器(Controller)、主机(Host)和应用层(Application)。控制器包含物理层(PHY)和链路层(Link Layer),负责射频收发、调制解调、广播与连接事件的时序控制。主机包含L2CAP、ATT、GATT、GAP、SM等协议,运行在nRF SoC的CPU上。应用层则是开发者编写的业务逻辑。

在nRF Connect SDK中,协议栈以SoftDevice Controller或Zephyr Bluetooth Host的形式提供。控制器与主机之间通过HCI(Host Controller Interface)交互,可以运行在同一芯片上(单芯片方案),也可以分离到不同芯片(双芯片方案)。对于nRF52840、nRF5340等SoC,通常采用单芯片方案,控制器与主机共享同一CPU资源。

BLE协议栈分层与GAP/GATT位置 应用层 Application 业务逻辑 · 服务定义 · 特征读写回调 · 连接参数管理 nRF Connect SDK bt_gatt_service_register() 主机 Host GAP 广播/扫描/连接 角色管理 GATT 服务/特征/描述符 属性表管理 ATT 属性协议 请求/响应 L2CAP / SM 逻辑信道/安全管理 配对与加密 HCI — 主机控制器接口(命令 / 事件 / ACL数据) 控制器 Controller 链路层 Link Layer 物理层 PHY (1M/2M/Coded) 射频 Radio nRF SoC 硬件(天线 · 射频前端 · 时钟)

GAP角色与广播机制

GAP(Generic Access Profile)定义了设备如何被发现、如何建立连接以及连接后的角色分工。BLE中有四种GAP角色:广播者(Broadcaster)、观察者(Observer)、外围设备(Peripheral)和中心设备(Central)。广播者只发送广播数据,不建立连接;观察者只扫描广播,不发起连接;外围设备作为从机被连接;中心设备作为主机发起连接。

广播是BLE设备被发现的第一步。广播包在37、38、39三个广播信道上轮流发送,每个信道发送一次称为一个广播事件。广播间隔(Advertising Interval)决定了两个广播事件之间的时间,范围从20ms到10.24s。较短的间隔提高被发现速度但增加功耗,较长的间隔降低功耗但延长发现时间。

广播数据(Advertising Data)和扫描响应数据(Scan Response Data)各最多31字节。广播数据中必须包含Flags字段,用于指示设备是否支持LE General Discoverable Mode和BR/EDR。常见的广播数据类型包括:

  • Flags (0x01):发现模式与能力标志
  • Complete Local Name (0x09):设备完整名称
  • 16-bit Service UUIDs (0x03):完整16位服务UUID列表
  • Manufacturer Specific Data (0xFF):厂商自定义数据
  • TX Power Level (0x0A):发射功率

在nRF Connect SDK中,使用bt_le_adv_start()启动广播,通过bt_data数组组织广播内容。例如,一个典型的广播配置如下:

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(0x180F)),
};

static const struct bt_data sd[] = {
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};

int err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
                          sd, ARRAY_SIZE(sd));

其中BT_LE_ADV_CONN表示可连接广播,ad为广播数据,sd为扫描响应数据。若启动成功,设备将周期性发送广播包,等待中心设备发起连接。

扫描与连接建立

中心设备通过扫描(Scanning)发现外围设备。扫描分为被动扫描和主动扫描:被动扫描只接收广播包;主动扫描在收到广播包后,还会发送扫描请求(Scan Request),外围设备回复扫描响应(Scan Response),从而获取更多数据。

扫描参数包括扫描间隔(Scan Interval)和扫描窗口(Scan Window)。扫描窗口必须小于等于扫描间隔。例如,扫描间隔100ms、扫描窗口50ms,表示每100ms中有一半时间在监听广播信道。占空比越高,发现越快,功耗也越高。

当中心设备决定连接某个外围设备时,发送连接请求(CONNECT_IND),其中包含连接参数:连接间隔(Connection Interval)、从机延迟(Slave Latency)和监督超时(Supervision Timeout)。连接间隔决定主从设备每次通信的时间间隔,范围7.5ms到4s。从机延迟允许从机跳过若干连接事件以节省功耗。监督超时是连接丢失的判断时间,必须大于(1+从机延迟)×连接间隔×2。

连接建立后,中心设备成为主机,外围设备成为从机。双方在约定的连接事件中交替收发数据。主机在每个连接事件开始时发送数据包,从机在收到后回复。若从机没有数据发送,可以回复空包。

GATT服务、特征与属性表

GATT(Generic Attribute Profile)定义了BLE设备之间如何组织和交换数据。GATT使用属性(Attribute)作为基本数据单元,每个属性包含句柄(Handle)、类型(UUID)、值(Value)和权限(Permissions)。属性表(Attribute Table)是设备上所有属性的有序集合,由GATT服务器维护。

GATT层次结构为:服务(Service)→ 特征(Characteristic)→ 描述符(Descriptor)。服务是相关特征的集合,通过服务声明(Service Declaration)定义。特征是服务中实际承载数据的单元,包含特征声明(Characteristic Declaration)、特征值(Characteristic Value)和可选的描述符。描述符用于描述特征的额外信息,如客户端特征配置描述符(CCCD)用于使能通知或指示。

每个属性都有一个16位句柄,由GATT服务器自动分配。句柄按属性表顺序递增,客户端通过句柄访问属性。服务UUID可以是16位(标准服务)或128位(自定义服务)。标准服务如电池服务(0x180F)、设备信息服务(0x180A)、心率服务(0x180D)等。

在nRF Connect SDK中,使用BT_GATT_SERVICE_DEFINE宏定义服务,或使用bt_gatt_service_register()动态注册。以下是一个自定义服务的示例:

static ssize_t read_temp(struct bt_conn *conn,
                         const struct bt_gatt_attr *attr,
                         void *buf, uint16_t len, uint16_t offset)
{
    uint16_t temp = 2350; /* 23.50°C */
    return bt_gatt_attr_read(conn, attr, buf, len, offset,
                             &temp, sizeof(temp));
}

BT_GATT_SERVICE_DEFINE(temp_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x181A)),
    BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_16(0x2A6E),
                           BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ, read_temp, NULL, NULL),
    BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

上述代码定义了一个环境感知服务(0x181A),包含一个温度特征(0x2A6E),支持读取和通知。CCCD描述符用于客户端使能通知。当温度变化时,服务器可调用bt_gatt_notify()向已订阅的客户端推送数据。

属性表配置要点

属性表配置需要注意以下几点:

  • 权限设置:读、写、读写、加密读、加密写等,需与安全等级匹配。
  • 句柄分配:由协议栈自动完成,开发者无需手动指定,但可通过bt_gatt_attr数组顺序推断。
  • CCCD管理:通知和指示必须配置CCCD,客户端写入0x0001使能通知,0x0002使能指示。
  • MTU协商:默认ATT MTU为23字节,可通过bt_gatt_exchange_mtu()协商更大MTU,nRF支持最大247字节。
  • 服务发现:客户端通过bt_gatt_discover()发现服务、特征和描述符,需按句柄顺序遍历。
在实际项目中,建议先使用nRF Connect for Mobile等通用工具验证广播、连接和GATT服务是否正常,再编写自定义客户端代码。这样可以快速定位是协议栈配置问题还是应用逻辑问题。

总结来说,BLE协议栈的分层结构决定了GAP负责连接建立与角色管理,GATT负责数据组织与交互。广播与扫描是设备发现的基础,连接参数影响通信性能与功耗,属性表则是GATT服务器的核心数据结构。掌握这些基础,才能在nRF平台上构建稳定、高效的BLE应用。