1. BLE基础与GATT协议概述
各位同学好,我是老周。做嵌入式蓝牙开发十几年了,今天咱们聊聊BLE和GATT协议。
蓝牙低功耗,也就是BLE,跟经典蓝牙完全是两码事。我刚开始接触时也以为BLE只是经典蓝牙的省电版,后来踩了不少坑才明白——它俩连协议栈都不一样。
蓝牙低功耗技术简介
BLE为什么省电?说白了,它大部分时间都在睡觉。
经典蓝牙要持续保持连接,像打电话一样一直占着线。BLE不一样,它广播一下、传完数据、立马断开,然后继续睡。我做过一个温湿度传感器,一颗纽扣电池撑了两年多——这在经典蓝牙时代想都不敢想。
BLE的几个关键特性:
- 峰值功耗低:发射电流仅几mA到十几mA
- 连接速度快:从广播到建立连接,最快3ms搞定
- 数据包小:一个包最多传251字节,适合传感器数据
- 频段:2.4GHz ISM频段,40个信道
BLE协议栈架构
BLE协议栈分两层:Controller和Host。中间有个HCI接口隔开。
我画了一张图,帮你理清这个结构:
这张图你得记住。做BLE开发,大部分时间都在跟GAP和GATT打交道。
各层简单说:
- PHY/射频:负责发0和1,1Mbps或2Mbps
- LL链路层:管理广播、扫描、连接、重传
- HCI:Host和Controller之间的通信桥梁
- L2CAP:数据分包和重组,把大包切成小片
- ATT:属性协议,GATT的底层传输机制
- GATT:定义数据怎么组织和访问
- GAP:定义设备怎么发现和连接
GATT与GAP的区别
很多新手搞混这两个。我简单说:
GAP管"怎么找到你",GATT管"找到你之后怎么读数据"。
举个例子:你进一个图书馆,GAP就是门口的导引牌,告诉你图书馆在哪、几点开门。GATT就是书架上的分类标签和索书号,告诉你哪本书在哪层哪列。
| 对比项 | GAP | GATT |
|---|---|---|
| 全称 | 通用访问规范 | 通用属性规范 |
| 职责 | 设备发现、连接建立、广播 | 数据读写、通知、服务发现 |
| 角色 | Broadcaster/Observer/Peripheral/Central | Server/Client |
| 典型场景 | 手机扫描到蓝牙手环 | 手机读取手环的心率值 |
关键点:GAP角色和GATT角色是独立的。一个Peripheral设备可以是GATT Server,也可以是GATT Client。我在项目中就做过一个网关,它作为Peripheral广播自己,同时又作为Client去读传感器数据。
GATT角色:Server与Client
GATT定义了两种角色:
- Server:持有数据的一方。比如心率传感器,它存着心率值,等着别人来读。
- Client:发起读写请求的一方。比如手机App,它主动去读心率传感器的数据。
嗯,这里要注意:Server和Client不是固定的。同一个设备可以同时是Server和Client。我做过一个数据采集器,它作为Server把采集到的数据暴露给手机,同时又作为Client去读另一个传感器的数据——这种场景很常见。
GATT通信的基本流程:
- Client发现Server有哪些Service
- Client发现Service里有哪些Characteristic
- Client读写Characteristic的值
- Server主动Notify/Indicate给Client
Profile与Service的概念
这两个概念,我换个说法你就能懂:
Profile 是一份"说明书"。它规定了一个设备应该有哪些Service、怎么工作。比如心率Profile(Heart Rate Profile)规定:心率设备必须包含心率Service(Heart Rate Service)和设备信息服务(Device Information Service)。
Service 是具体的数据集合。一个Service包含多个Characteristic(特征值),每个Characteristic代表一个数据点。
举个例子:
心率Profile (Heart Rate Profile)
└── 心率Service (Heart Rate Service) [UUID: 0x180D]
├── 心率测量特征 (Heart Rate Measurement) [UUID: 0x2A37]
│ ├── 属性: Notify
│ └── 值: 心率数值 (uint8)
└── 传感器位置特征 (Body Sensor Location) [UUID: 0x2A38]
├── 属性: Read
└── 值: 传感器位置 (uint8枚举)
└── 设备信息服务 (Device Information Service) [UUID: 0x180A]
├── 制造商名称特征 [UUID: 0x2A29]
└── 序列号特征 [UUID: 0x2A25]
你看,Service用UUID标识。标准Service用16位UUID(比如0x180D),自定义Service用128位UUID。
| 层级 | 说明 | 例子 |
|---|---|---|
| Profile | 规范文档,定义设备行为 | 心率Profile、电池Profile |
| Service | 功能模块,包含多个特征 | 心率Service (0x180D) |
| Characteristic | 单个数据点,可读/写/通知 | 心率测量值 (0x2A37) |
| Descriptor | 特征的描述或配置 | 客户端特征配置描述符 (CCCD) |
最后说一句:GATT这套东西,说白了就是"属性表"的增删改查。你把它想象成一个Excel表格——Service是Sheet页,Characteristic是单元格,Descriptor是单元格的备注。理解了这一点,后面写代码就顺了。