4. BLE协议栈基础:GAP、GATT、Service、Characteristic、UUID概念解析

做蓝牙开发,绕不开这几个核心概念。很多新手一上来就被GAP、GATT这些缩写搞晕了。我刚开始接触BLE时也是这样,感觉像在看天书。今天咱们就把这几个概念彻底讲明白。

4.1 先搞清楚两个角色:GAP与GATT

GAP和GATT是BLE协议栈里最上层的两个模块。它们分工很明确:

  • GAP(Generic Access Profile):负责设备发现、连接建立、广播这些"连接前"的事情
  • GATT(Generic Attribute Profile):负责连接建立后,设备之间怎么交换数据

说白了,GAP管"怎么找到对方并连上",GATT管"连上之后怎么聊天"。

我个人的理解方式:把GAP想象成两个人见面打招呼的过程——你是谁?我能加你微信吗?而GATT就是加上微信之后,你们怎么发消息、发什么内容。

4.2 GAP详解:广播、扫描与连接

GAP定义了四种角色:

角色 说明 实际场景
Broadcaster(广播者) 只发广播,不连接 信标(Beacon)设备
Observer(观察者) 只扫描广播,不连接 扫描仪、数据采集器
Peripheral(外设) 广播自己,等待连接 传感器节点、智能灯泡
Central(中心) 扫描并主动发起连接 手机、网关

咱们做照明节点,通常就是Peripheral角色。设备上电后开始广播,手机作为Central来扫描并连接。

避坑指南:我曾经在一个项目中,广播间隔设得太短(20ms),导致功耗飙升。后来改成100ms,功耗降了40%。广播间隔不是越短越好,要平衡响应速度和功耗。

4.3 GATT详解:数据交换的"主仆"模式

GATT建立连接后,采用Client-Server架构:

  • GATT Server:持有数据的一方(通常是Peripheral,比如我们的照明节点)
  • GATT Client:读取或写入数据的一方(通常是Central,比如手机App)

嗯,这里要注意:角色不是固定的。有些场景下Peripheral也可以作为Client去读手机的数据,但照明节点里基本用不到。

4.4 Service、Characteristic、UUID:数据怎么组织

这三个概念是GATT的核心。我画个图帮你理解:

GATT Server
  └── Service(服务)
        ├── Characteristic(特征)1
        │     ├── Value(值)
        │     └── Descriptor(描述符)
        └── Characteristic(特征)2
              ├── Value(值)
              └── Descriptor(描述符)

Service:一组功能的集合。比如"灯光控制服务"、"电池信息服务"。

Characteristic:具体的数据点。比如"开关状态"、"亮度值"、"色温值"。

UUID:唯一标识符。用来区分不同的Service和Characteristic。

举个例子

  • Service UUID = 0xFFE0(自定义灯光服务)
  • Characteristic UUID = 0xFFE1(开关状态,可读可写)
  • Characteristic UUID = 0xFFE2(亮度值,可写)

手机App通过UUID找到对应的Characteristic,然后读写数据。

4.5 UUID的分类与选择

UUID分两种:

类型 长度 用途
16-bit UUID 2字节 蓝牙SIG标准服务(如电池服务0x180F)
128-bit UUID 16字节 自定义服务(如0x0000FFE0-0000-1000-8000-00805F9B34FB)

我建议:能用标准UUID就用标准的。比如电池服务直接用0x180F,手机系统会自动识别。自定义功能再用128-bit UUID。

注意:128-bit UUID不是随便写的。我曾经见过有人直接用"12345678-1234-1234-1234-123456789ABC",结果跟别人的服务冲突了。建议用在线生成器生成一个真正的随机UUID。

4.6 实战:照明节点的GATT结构设计

以智能灯泡为例,我通常这样设计:

Service: 灯光控制 (UUID: 0xFFE0)
  ├── Characteristic: 开关 (UUID: 0xFFE1)
  │     ├── Properties: Read, Write
  │     └── Value: 0x00(关) / 0x01(开)
  │
  ├── Characteristic: 亮度 (UUID: 0xFFE2)
  │     ├── Properties: Read, Write
  │     └── Value: 0-100 (百分比)
  │
  └── Characteristic: 色温 (UUID: 0xFFE3)
        ├── Properties: Read, Write
        └── Value: 2700-6500 (开尔文)

Service: 设备信息 (UUID: 0x180A)  // 标准服务
  └── Characteristic: 制造商名称 (UUID: 0x2A29)
        ├── Properties: Read
        └── Value: "MyLighting Inc."

你看,我把控制相关的放一个自定义Service,设备信息用标准Service。这样手机App解析起来也方便。

4.7 几个容易踩的坑

做量产时,这些细节不注意会出大问题:

  • MTU大小:默认23字节,实际可用只有20字节。传输大数据时要协商MTU,我一般设到247字节。
  • Notification vs Indication:Notification不需要确认,速度快但可能丢包。Indication有确认,可靠但慢。照明控制建议用Notification,丢一包用户也感觉不到。
  • Characteristic的Property:一定要设置对。比如只读的设成Write,手机写进去设备没反应,排查半天才发现是Property配错了。

我的经验:量产前一定要用nRF Connect或LightBlue这种通用工具测试一遍所有Characteristic的读写。别只用自己的App测,有时候自己的App做了容错,问题发现不了。

好了,GAP、GATT、Service、Characteristic、UUID这几个概念,说白了就是一套"发现-连接-组织数据-读写数据"的流程。理解了这个框架,后面写代码就顺了。