BLE基础概念:GAP、GATT、Service、Characteristic、UUID、角色

好,咱们正式开始啃BLE的核心概念。说实话,很多初学者一上来就被这些缩写给唬住了。GAP、GATT、UUID……听着像天书。其实没那么玄乎。我当年刚接触蓝牙时,也花了点时间才理清楚它们之间的关系。今天我就用最直白的方式,把这些概念给你讲透。

1. 角色:Central 和 Peripheral

先聊角色。BLE设备分两种:Central(中心设备)和 Peripheral(外围设备)。

你想想看,你的手机连一个蓝牙手环。手机是Central,手环是Peripheral。Central负责扫描、发起连接。Peripheral负责广播自己、等待连接。就这么简单。

我在项目中遇到过一个问题:一个设备既想当Central又想当Peripheral。这在Zephyr里叫"多角色支持"。嗯,这里要注意,不是所有芯片都能同时跑两个角色。选型时一定要看芯片的RAM和Flash够不够。

核心要点:

  • Peripheral:广播数据,等待连接。功耗低,适合传感器、手环。
  • Central:扫描设备,发起连接。功耗高,适合手机、网关。
  • Zephyr里通过 CONFIG_BT_CENTRALCONFIG_BT_PERIPHERAL 配置角色。

2. GAP:通用访问规范

GAP,全称Generic Access Profile。说白了,它管的是"设备怎么被发现、怎么建立连接"。这是BLE最底层的规范。

GAP定义了四种角色:

  • Broadcaster:只发广播,不连。比如蓝牙信标。
  • Observer:只收广播,不连。比如扫描仪。
  • Peripheral:可被连接的外设。
  • Central:主动连接的中心设备。

我个人习惯把GAP理解成"蓝牙设备的社交礼仪"。它规定了:

  • 广播间隔多久发一次
  • 广播包里放什么数据
  • 连接参数(连接间隔、延迟、超时)怎么协商

举个例子,Zephyr里启动广播的代码是这样的:

// 启动广播
struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM_INIT(
    BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_IDENTITY,
    BT_GAP_ADV_FAST_INT_MIN_2,
    BT_GAP_ADV_FAST_INT_MAX_2,
    NULL);

bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), NULL, 0);

这里 BT_LE_ADV_OPT_CONNECTABLE 表示这个广播是可连接的。如果你只想发信标,去掉这个标志就行。

避坑指南: 我曾经把广播间隔设得太短(20ms),结果功耗飙升,电池两天就没了。后来改成100ms,功耗降了60%。广播间隔不是越快越好,要平衡功耗和响应速度。

3. GATT:通用属性规范

GAP管的是"怎么连",GATT管的是"连上以后怎么传数据"。GATT定义了一套客户端-服务器模型。

在GATT里:

  • GATT Server:提供数据的一方。通常是Peripheral。
  • GATT Client:读取或写入数据的一方。通常是Central。

你想想看,你的手机连上心率带。心率带是Server,它提供心率数据。手机是Client,它读取心率值。当然,角色可以互换。Peripheral也可以主动发数据给Central,这叫"通知"或"指示"。

Zephyr里实现GATT Server的典型流程:

// 定义心率服务
BT_GATT_SERVICE_DEFINE(hrs_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS),
    BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT,
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           NULL, read_hrs_measurement, NULL),
);

这段代码定义了一个心率服务。它包含一个特征值,支持通知(Notify)。当心率数据变化时,Server主动推给Client。

4. Service 和 Characteristic

这两个概念是GATT的核心。我打个比方:

  • Service 就像是一个文件夹。它把相关的数据归类在一起。
  • Characteristic 就像是文件夹里的具体文件。它才是真正存放数据的地方。

一个Service可以包含多个Characteristic。每个Characteristic又有自己的属性:

  • Value:实际的数据值
  • Properties:读写权限(Read、Write、Notify、Indicate等)
  • Descriptors:描述信息,比如单位、配置等

举个例子,电池服务(Battery Service)包含一个电池电量特征值。这个特征值可读,也可通知。当电量变化时,设备主动告诉手机。

设计建议: 我习惯把Service设计得"高内聚低耦合"。一个Service只做一件事。比如温度服务只管温度,湿度服务只管湿度。别把温度、湿度、气压全塞到一个Service里。后期维护会疯掉。

5. UUID:通用唯一标识符

UUID就是每个Service和Characteristic的身份证。BLE使用16位或128位的UUID。

  • 16位UUID:由蓝牙SIG标准定义。比如0x180D是心率服务,0x2A37是心率测量特征值。这些是官方认证的,可以直接用。
  • 128位UUID:自定义服务用的。格式是 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。你可以在网上随便生成一个,保证全球唯一。

Zephyr里使用UUID的示例:

// 使用标准16位UUID
#define BT_UUID_HRS_VAL BT_UUID_16_ENCODE(0x180D)

// 使用自定义128位UUID
#define BT_UUID_CUSTOM_SERVICE_VAL BT_UUID_128_ENCODE( \
    0x12345678, 0x1234, 0x5678, 0x9abc, 0xdef012345678)

我个人建议:能用标准UUID就别自己造。标准UUID的好处是手机端不用装额外App,系统自带的蓝牙工具就能识别。比如iOS的CoreBluetooth对标准服务有原生支持。

注意: 自定义128位UUID一定要保证唯一性。我曾经见过两个团队用了同一个UUID,结果设备互相干扰,数据全乱了。UUID生成器很多,随便用,但别复制粘贴别人的。

6. 它们之间的关系

最后,我把这些概念串起来。一个完整的BLE设备是这样的:

  1. GAP 负责让设备被发现。它广播自己的存在。
  2. GATT 负责定义数据怎么组织。它用Service和Characteristic来结构化数据。
  3. UUID 给每个Service和Characteristic一个唯一的名字。
  4. 角色 决定设备是主动扫描还是被动等待。

用一句话总结:GAP管连接,GATT管数据,UUID管标识,角色管分工。

嗯,这些概念你理解了,后面写代码就会顺手很多。下一章我们开始实战,在Zephyr上搭建第一个BLE外设。到时候你会看到这些概念怎么落地到代码里。

学习建议: 别急着写代码。先拿nRF Connect或者LightBlue这类工具,连一个真实的BLE设备,看看它的Service、Characteristic、UUID长什么样。亲眼见过,比看十遍文档都管用。