3. 蓝牙低功耗协议栈基础

BLE 协议栈是 nRF 传感终端能够被手机发现、连接并交换数据的根本。理解它的分层结构,比记住某个 API 更重要:物理层决定信号怎么发,链路层决定两个设备怎么建立可靠的连接,GAP 决定设备如何被看见和连接,GATT 决定连接之后数据以什么形式组织。nRF Connect SDK 把控制器与主机整合进 Zephyr 蓝牙栈,开发者面对的主要是 GAP 与 GATT 的接口,但底层行为仍由这些层次共同决定。

物理层与链路层:从射频到连接

BLE 工作在 2.4 GHz ISM 频段,采用 40 个信道,信道间隔 2 MHz。其中 37、38、39 三个是广播信道,其余 37 个是数据信道。BLE 使用 GFSK 调制,符号速率为 1 Msym/s,即常说的 1M PHY;nRF52/nRF53 系列还支持 2M PHY 和 Coded PHY(125 kbps 与 500 kbps),前者提高吞吐,后者以编码增益换取更远距离和更强抗干扰能力。物理层只负责比特的收发,不关心这些比特属于谁。

链路层(Link Layer)在物理层之上定义了两个设备如何交互。广播态下设备在 37/38/39 信道轮流发送广播包;扫描态设备监听这些信道;发起态设备发送连接请求;连接态下主从双方按连接事件(Connection Event)周期性地在数据信道上收发。链路层还负责跳频、CRC 校验、白名单过滤和加密。连接参数中的连接间隔(Connection Interval)、从机延迟(Slave Latency)和超时时间(Supervision Timeout)都由链路层执行,它们直接决定功耗与响应速度的平衡。

BLE 协议栈分层与数据流向 本机(nRF 传感终端) 应用层 Application 传感器采集 / 业务逻辑 GATT / GAP 服务·特征 / 广播·连接角色 主机 Host(Zephyr 蓝牙栈) ATT / SMP / L2CAP HCI 主机与控制器命令/事件接口 SoftDevice Controller 链路层 Link Layer 物理层 PHY 1M / 2M / Coded · 2.4 GHz 对端(手机 / 网关) 应用 / App nRF Connect 等 GATT Client / GAP 发现服务·读写特征 主机 Host ATT / SMP / L2CAP HCI 命令/事件接口 控制器 Controller 链路层 Link Layer 物理层 PHY 2.4 GHz 射频 空中接口 广播包 / 连接事件

GAP 角色:设备如何被看见与连接

GAP(Generic Access Profile)规定了设备在广播与连接中的角色。广播方称为广播者(Advertiser),扫描方称为扫描者(Scanner);发起连接的一方是中心设备(Central,旧称 Master),接受连接的一方是外围设备(Peripheral,旧称 Slave)。一个传感终端通常作为外围设备,周期性广播自己的存在,等待手机或网关发起连接。GAP 还定义了广播数据(Advertising Data)与扫描响应数据(Scan Response Data),前者最多 31 字节,后者在扫描者发送扫描请求后返回,同样最多 31 字节。BLE 5.0 的扩展广播(Extended Advertising)可突破这一限制,但基础场景仍以 31 字节为主。

广播数据由若干 AD Structure 组成,每个结构以长度字节开头,后跟类型字节和数据。常用类型包括 0x01 标志位、0x09 完整本地名称、0x03 完整 16 位 UUID 列表、0xFF 厂商自定义数据。例如一个温湿度终端可以广播:标志位 0x06(LE General Discoverable + BR/EDR Not Supported)、名称 "TH-Sensor"、以及厂商数据中包含设备 ID 和电池电量。手机扫描时先看到这些信息,无需连接即可判断设备类型。

GATT:连接之后的数据组织

GATT(Generic Attribute Profile)建立在 ATT(Attribute Protocol)之上,用属性(Attribute)描述数据。属性由句柄(Handle)、UUID、权限和值组成。GATT 把这些属性组织成服务(Service)和特征(Characteristic)。一个服务包含若干特征,特征包含值以及可选的描述符(Descriptor)。例如电池服务 UUID 0x180F 下有一个电池电量特征 0x2A19,其值是一个 0~100 的百分比字节;若需要订阅通知,还要有客户端特征配置描述符(CCCD,UUID 0x2902)。

GATT 定义了服务端(Server)与客户端(Client)角色。传感终端通常作为 GATT Server,把自己的传感器数据放在特征值中;手机作为 GATT Client,通过发现服务、读特征、写特征、订阅通知来交互。读操作由客户端发起,服务端返回当前值;通知(Notify)由服务端主动推送,客户端无需确认;指示(Indicate)则需要客户端确认。对于周期性上报的温度数据,Notify 比 Read 更合适,因为它避免了轮询开销。

nRF Connect SDK 中的集成方式

在 nRF Connect SDK 中,蓝牙协议栈由 Zephyr 蓝牙主机与 Nordic 的 SoftDevice Controller 共同组成。SoftDevice Controller 是预编译的二进制库,实现了链路层与部分 HCI 功能,运行在协议栈的下层;Zephyr 蓝牙主机实现 GAP、GATT、ATT、SMP、L2CAP 等上层协议。两者通过 HCI 接口通信,开发者通常不需要直接操作 HCI,而是调用 Zephyr 的蓝牙 API。

工程配置上,需要在 prj.conf 中启用蓝牙与相关功能。一个典型的最小配置如下:

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="TH-Sensor"
CONFIG_BT_DEVICE_APPEARANCE=0
CONFIG_BT_CTLR=y
CONFIG_BT_LL_SOFTDEVICE=y
CONFIG_BT_GATT_DYNAMIC_DB=n
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_ATT_PREPARE_COUNT=2

其中 CONFIG_BT_LL_SOFTDEVICE=y 明确选择 SoftDevice Controller 作为链路层实现,这是 Nordic 器件上的默认选择。若使用其他控制器,则需改为对应选项。MTU 相关配置决定单次 ATT 传输能承载多少字节,影响吞吐与内存占用。

代码层面,初始化流程通常是:先调用 bt_enable() 启动协议栈,然后注册连接回调、配置广播数据、启动广播。下面是一个简化的外围设备广播示例:

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, "TH-Sensor", 9),
};

static const struct bt_data sd[] = {
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x0F, 0x18),
};

void main(void) {
    int err = bt_enable(NULL);
    if (err) { return; }

    err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
                          sd, ARRAY_SIZE(sd));
    if (err) { return; }
}

这段代码中,ad 是广播数据,包含标志位和完整设备名;sd 是扫描响应数据,这里放入了电池服务的 16 位 UUID 0x180F(小端表示为 0x0F, 0x18)。BT_LE_ADV_CONN 表示可连接广播。手机扫描时即可看到设备名,连接后能发现电池服务。

调试时可用 nRF Connect for Mobile 观察广播包中的 AD Structure,确认名称、UUID 和厂商数据是否正确;连接后查看 GATT 表,核对服务与特征的 UUID、权限和 CCCD 是否存在。

连接参数与功耗的权衡

连接建立后,中心设备会下发连接参数。连接间隔越短,响应越快但功耗越高;从机延迟允许外围设备跳过若干连接事件,从而在无数据时休眠。以 1 秒连接间隔、从机延迟 4、超时 6 秒为例,外围设备最多可跳过 4 个连接事件,即约 5 秒才必须响应一次,显著降低平均功耗。但延迟过大也会导致下行数据到达变慢。实际项目中应根据上报周期和响应要求选择参数,并在连接后通过 bt_conn_le_param_update() 请求更新。