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这几个概念,说白了就是一套"发现-连接-组织数据-读写数据"的流程。理解了这个框架,后面写代码就顺了。