蓝牙GATT专题之实战篇
蓝牙GATT中,GATT客户端和GATT服务器和蓝牙主机/蓝牙从机的关系 ?GATT客户端是否既可以作为蓝牙主机也可以作为蓝牙从机 ?
我用最清晰、不绕弯的方式给你讲透蓝牙 GATT 里这几组角色关系,保证你一次性彻底懂。
一、先把两个维度拆开
蓝牙 BLE 里有两套完全独立的角色体系:
1. 物理链路角色(谁发起连接 = 链路层)
- 主机(Master / Central):主动发起连接的一方
- 从机(Slave / Peripheral):广播、被连接的一方
2. 数据交互角色(谁读写数据 = GATT 层)
- GATT 服务器(Server):拥有属性表(服务、特征值),被读写的一方
- GATT 客户端(Client):主动去读、写、订阅服务器数据的一方
二、它们的关系是什么?
一句话总结:
链路层角色(主机/从机)和 GATT 角色(客户端/服务器)完全独立,互不绑定。
- 主机 可以是 GATT 客户端,也可以是 GATT 服务器
- 从机 可以是 GATT 客户端,也可以是 GATT 服务器
- 只是现实中常见搭配:
- 手机(主机) ←→ GATT 客户端
- 手环/手表(从机) ←→ GATT 服务器
但这只是习惯,不是规则。
三、核心问题:GATT 客户端是否既可以作为主机,也可以作为从机?
✅ 答案:完全可以,而且非常常见。
举例 1:手机(主机)= GATT 客户端
连手表 → 读心率、写指令
这是最常见。
举例 2:手表(从机)= GATT 客户端
手表主动去读手机上的服务特征,比如读手机电量、读消息。
此时:
- 链路层:手表 = 从机
- GATT 层:手表 = GATT 客户端
完全合法,标准支持。
举例 3:两个设备互相读写
A(主机)= GATT 客户端
B(从机)= GATT 客户端
两边都有服务器,互相读写。
BLE 5.0+ 非常普遍。
四、最关键结论(背会这 3 句)
- 主机/从机 = 谁发起连接(链路层)
- GATT 客户端/服务器 = 谁主动读写数据(GATT 层)
- GATT 客户端既可以是主机,也可以是从机,没有任何限制
五、极简记忆版
- 主/从 = 连接方向
- 客户端/服务器 = 数据读写方向
- 两套角色正交、独立、可任意组合
如果你需要,我可以给你画一张角色关系图,或者给你一段NuttX / Zephyr 里双向 GATT 客户端的实际代码示例。
在实际蓝牙手表中,蓝牙手表要使用GATT时,蓝牙手表是作为GATT服务端还是作为GATT客户端 ?
实际蓝牙手表里的真实角色(99% 场景都是这样)
结论先说
蓝牙手表 = 绝大多数时候是 GATT Server(服务端)
手机 = GATT Client(客户端)
一、最典型、最常见的情况
- 蓝牙手表:GATT Server
- 提供服务:心率、步数、电量、设备信息
- 等着手机来读、来写、来订阅通知
- 手机(APP):GATT Client
- 主动去连手表
- 主动读数据、写指令、开通知
为什么?
因为数据都存在手表里,手表是“数据提供者”,天然就是 Server。
二、那手表会不会也做 GATT Client?
会,但属于次要、辅助角色,不是主角色。
比如:
- 手表主动去读手机的电量
- 手表主动去读手机上的某些特征值
这时:
- 手表 = GATT Client
- 手机 = GATT Server
但这属于双向通信,是附加功能,不是标配。
三、一句话总结(你开发时直接按这个来)
- 手表主要角色:GATT Server
- 手机主要角色:GATT Client
- 手表偶尔可以兼职做 GATT Client,但不是必须
四、对应到你现在的代码场景
你在做小米 VELA / NuttX 手表开发:
- 手表端:实现 GATT Service(服务)
- 定义 Service UUID + Characteristic
- 提供数据,响应手机读写
- 手机APP:作为 GATT Client 去访问手表
这就是标准、正确、工业界通用结构。
需求:从串口操作蓝牙设备,用AT指令去操作蓝牙设备实现2个蓝牙设备的连接配对,数据收发,那么在AT指令去操作数据发送的时候,蓝牙协议栈底层是否建立GATT服务端,GATT客户端,然后用GATT的读写来实现的数据收发 ?

答案是yes,必须建立gatt,因为ble就是用gatt来传输数据的。
如果使用信驰达蓝牙透传模块,那么在使用透传模块收发数据时,是否经过GATT的读写 ?
是的,使用信驰达蓝牙透传模块收发数据时,数据底层是经过 GATT(通用属性协议)的读写操作来完成的。
虽然从你(用户)的角度看,只是在通过串口发送和接收原始数据,但模块内部已经自动帮你完成了复杂的蓝牙协议转换。整个数据流向可以分为“用户视角”和“底层技术视角”两个层面:

1. 用户视角:简单的串口“透传”
从开发者的角度看,整个过程被大大简化了:
- MCU发数据:你的MCU(如STM32)只需要把要发的数据通过串口(UART)发给信驰达模块。
- 手机收数据:手机APP就能收到这个数据。
- 反之亦然:手机APP发来的数据,也会从模块的串口原封不动地输出,被你的MCU接收。
你完全不需要关心蓝牙协议栈内部的细节,就像使用一根无线串口线一样。
2. 技术视角:自动完成的GATT读写
在模块内部,为了让数据在蓝牙无线通道上传输,必须遵循BLE的GATT规范。信驰达透传模块的固件预设了一个“透明传输服务”,这个服务通常定义了3个特定的通道(UUID,即通用唯一标识符)来完成数据的收发:
| 方向 | 使用的GATT通道 (UUID) | 作用 |
|---|---|---|
| MCU → 手机 | Notify (如 0xFFF1) |
模块主动“通知”手机,把从串口收到的数据发出去。 |
| 手机 → MCU | Write (如 0xFFF2) |
手机“写入”数据到这个通道,模块接收后转发到串口。 |
| 服务定义 | Service (如 0xFFF0) |
用于声明这是一个提供透传服务的“容器”。 |
数据接收和发送的具体过程如下:
-
MCU发送数据,手机接收(使用GATT Notify操作)
- 你的MCU通过串口发送数据“ABC”给信驰达模块。
- 模块内部的固件自动将这个数据打包,并通过GATT协议中的 Notify 操作发送出去。
- 手机APP需要先订阅(Enable Notification)这个Notify通道,之后就能收到“ABC”了。
-
手机发送数据,MCU接收(使用GATT Write操作)
- 手机APP将数据“123”通过GATT的 Write 操作,写入到模块预设的Write通道(如
0xFFF2)。 - 模块收到这条写入请求后,会提取出里面的有效数据“123”。
- 最后,模块通过串口将“123”发送给你的MCU。
- 手机APP将数据“123”通过GATT的 Write 操作,写入到模块预设的Write通道(如
重要概念澄清:这里提到的
Service和Characteristic是GATT协议的核心概念。Service是一个服务容器,Characteristic是这个服务下具体的数据通道。透传模块正是利用Notify类型的Characteristic来实现模块→手机的高速数据推送,避免了手机不停地发起查询请求。
总结
总而言之,信驰达透传模块的核心价值就是“封装复杂性”。它将底层的 GATT 协议读写操作(如Write、Notify)与上层的 用户串口数据 做了一个无缝的“翻译”和“转发”。
所以,在使用时你可以完全忽略GATT的存在,像操作普通串口设备一样进行开发。只有当需要自己开发手机APP时,才需要按照模块手册中规定的Service UUID和Characteristic UUID(如0xFFF0, 0xFFF2, 0xFFF1)进行对应的GATT读写编程。
ESP32的官方教程
https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-guides/c.html
ble测试吞吐时,MTU会影响吞吐吗
非常明确、直接的结论:
MTU 对 BLE 吞吐率影响巨大,而且是决定性因素之一。
简单一句话:
MTU 越大,单次能发的数据越多,发包次数越少,吞吐率越高。
1. 为什么 MTU 会影响吞吐?
BLE 并不是“一直发”,而是按数据包发送:
- 默认 MTU = 23 字节
有效应用数据只有 20 字节 - MTU = 247 字节
有效应用数据可达 244 字节
同样发 1024 字节数据:
- MTU23:要发 52 包
- MTU247:只需要 5 包
发包次数越少,空包、间隔、交互开销占比越低 → 吞吐率大幅上升
2. 真实吞吐大概是什么水平?(直观感受)
- MTU 23(默认):
吞吐 ≈ 1~2 KB/s - MTU 128:
吞吐 ≈ 8~12 KB/s - MTU 247(最大):
吞吐 ≈ 15~25 KB/s(取决于连接间隔、Phy 等)
可以看到:
MTU 从 23 → 247,吞吐能翻 10 倍以上。
3. 为什么不是 MTU 越大越好?
有上限:
- BLE 最大 ATT MTU = 247 字节
- 再大协议不支持
另外:
- MTU 过大会增加 单次传输延迟
- 对低功耗不太友好
- 部分老旧设备不支持大 MTU
但测吞吐时,一定开到 247。
4. 除了 MTU,还有谁影响吞吐?
按重要程度排序:
- MTU 大小(你问的这个)
- 连接间隔 Connection Interval
- PHY 速率(2M PHY)
- 是否打开 DLE(数据长度扩展)
- 从机 latency / 监督超时
5. 最终总结(最精炼版)
- MTU 直接决定 BLE 吞吐上限
- MTU 越大,吞吐越高
- 默认 MTU23 吞吐极低,测吞吐量必须 协商到 247
- 信驰达、汇顶、泰凌微等 BLE 模块都一样
如果你需要,我可以给你:
- 一套测 BLE 吞吐的最优参数组合(MTU+Interval+PHY)
- 或者GATT 吞吐测试的简单计算公式
实现一个GATT客户端测吞吐的服务,请解释出你的详细设计
GATT流程中,从GATT注册属性表,到GATT读写数据,完整的流程是什么样的 ?
蓝牙的各种包,广播包,不同的广播包,数据包长什么样子?用抓包软件抓出来的包长什么样子 ?
在蓝牙连接成功后,用GATT交互的成功中的抓包是什么样的 ?
小米使用NimBLE蓝牙协议栈
乐鑫esp32使用的蓝牙协议栈是NimBLE吗---乐鑫ESP-IDF 同时提供两套 BLE 主机协议栈:Bluedroid(默认)和 NimBLE(可选)
怎么理解特征?怎么理解描述符 ?怎么理解属性 ?
1,特征,到底什么是特征,是存储实际业务数据内容的载体。比如:心率/血氧/电量。
2,首先,概念混淆,BLE中没有描述符这个概念,有特征描述符,特征描述符是专门针对特征的配置/额外信息。
那么,特征描述符配置了特征的哪些功能 ?---特征描述符配置通知/指示功能的打开和关闭。
3,属性是什么,初始化时注册的属性表,就是注册了特征,描述符,所以属性是特征/特征描述符的集合。
蓝牙的服务有16bit的UUID和128bit的UUID,什么场景下用16bit的UUID,什么场景下使用128bit的UUID ?
超清晰区分:16bit UUID vs 128bit UUID 使用场景
一句话结论
- 16bit UUID:只用于 蓝牙 SIG 官方定义的标准服务(你不能自己定义)
- 128bit UUID:用于 厂商自定义服务、私有协议、你自己写的业务(你随便用)
1. 什么时候用 16bit UUID?
满足两个条件才用:
- 这个服务/特征是 蓝牙 SIG 官方规定 的
- 你要实现的是标准蓝牙 Profile
典型场景:
- 设备信息服务:
0x180A - 电池服务:
0x180F - 心率服务:
0x180D - 串口透传标准 SPP 对应 BLE 标准服务(如果有的话)
- 血糖、体重、温度计等行业标准 Profile
特点:
- 短、省空间、协议效率高
- 你不能自己申请、不能自己瞎编
- 只有 SIG 有权分配
2. 什么时候用 128bit UUID?
只要不是官方标准服务,全部用 128bit
典型场景:
- 智能手表/手环 自定义协议(步数、控制、升级、消息)
- 你和手机APP之间 私有通信协议
- 厂商自己的透传服务(信驰达、安信可等模块的串口透传)
- 任何你自己发明的业务功能
- 不想和别人冲突、需要全局唯一的服务
特点:
- 极长,几乎不可能重复
- 你可以自己随便生成
- 厂商私有业务标配
3. 实际开发中的真实规则(非常重要)
你的蓝牙手表场景:
- 系统服务、电池、设备信息 → 16bit UUID
- 你的APP和手表之间的自定义通信 → 128bit UUID
信驰达 BLE 模块透传:
- 它用的就是 128bit UUID
例如:6E400001-B5A3-F393-E0A9-E50E24DCCA9E - 因为这是厂商自定义串口服务,不是SIG标准
4. 极简记忆口诀
- 官方标准 → 16bit
- 自己写的 → 128bit
- 私有业务、透传、自定义协议 → 一律 128bit
如果你愿意,我可以
直接给你生成一个不重复、可用于你手表项目的 128bit UUID,拿来就能用。
蓝牙测吞吐时,客户端使用的是GATT的写属性,还是读属性,还是通知或者指示 ?
测吞吐用 Write Without Response(ATT Write Command),配合 LE 2M PHY + 517 MTU + 短连接间隔,可达到蓝牙 LE 最大吞吐(约 1.4 Mbps 应用层净荷)

GATT中通知和指示的区别?
之前一看概念,一个有响应,一个无响应,但具体哪个有响应,哪个无响应,这个忘记了。


第二次看这个区别的时候,发现通知是在BLE有数据变化时才发送给对端的动作,知道使用具体场景了。
GATT中读写操作是都开启的吗?还是可以只开其中一个操作?
通知和指示是怎么使用的 ?是必须使用的吗 ?
用心率服务来解释下GATT的流程中上面问题是怎么处理的?
不是都开启,不用读写操作就可以不开启,直接用通知就行,心率上报给手机就是直接用通知来实现的。
通知和指示不是必须使用的,是可以在特征描述符中进行配置,是否打开或者关闭通知/指示。
如果只是心率数据的话,直接用GATT中的通知就行,不需要数据读写。
BLE中的连接间隔对吞吐的影响是什么
BLE(蓝牙低功耗)中的连接间隔(Connection Interval) 对吞吐量有直接影响,这是一个核心的性能调优参数。
连接间隔与吞吐量的关系
基本公式
理论最大吞吐量 = 每包数据字节 × 每连接事件包数 × (1000ms / 连接间隔)
| 连接间隔 | 每秒连接事件数 | 典型吞吐趋势 |
|---|---|---|
| 7.5 ms(最小) | ~133 次 | 最高吞吐、最高功耗 |
| 50 ms | 20 次 | 中等吞吐、平衡功耗 |
| 100 ms | 10 次 | 较低吞吐、较低功耗 |
| 4000 ms(最大) | 0.25 次 | 最低吞吐、最低功耗 |
关键机制
1. 连接事件(Connection Event)
时间线:
|---[连接事件]---|---[连接事件]---|---[连接事件]---|
↑ 数据交换 ↑ 数据交换 ↑ 数据交换
间隔 = 连接间隔
- 每个连接事件是主从设备短暂通信的时间窗口
- 间隔越短 → 通信窗口越频繁 → 吞吐越高
- 间隔越长 → 通信窗口越稀疏 → 吞吐越低
2. 每事件多包传输
在一个连接事件内,设备可以连续发送多个数据包:
- BLE 4.2/5.0+ 支持 6 个包/连接事件(甚至更多)
- 有效吞吐 = 单包载荷 × 每事件包数 × 每秒事件数
实际数据对比
| 参数配置 | 理论数据速率 | 适用场景 |
|---|---|---|
| 7.5ms 间隔 + 251 字节 PDU | ~100+ kbps | OTA 升级、大批量数据传输 |
| 50ms 间隔 + 251 字节 PDU | ~20 kbps | 传感器数据上报 |
| 100ms 间隔 + 27 字节 PDU | ~2 kbps | 低功耗心跳包 |
代码配置示例
// 从机请求的连接参数(以 Nordic SDK 为例)
ble_gap_conn_params_t conn_params = {
.min_conn_interval = MSEC_TO_UNITS(7.5, UNIT_1_25_MS), // 最小 7.5ms
.max_conn_interval = MSEC_TO_UNITS(20, UNIT_1_25_MS), // 最大 20ms
.slave_latency = 0, // 从机延迟
.conn_sup_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS), // 监督超时
};
// 主机(手机)可能接受或拒绝,最终值在 min~max 之间协商
权衡:吞吐量 vs 功耗
高吞吐 ←————————————————→ 低功耗
短间隔 长间隔
7.5ms 100ms+
频繁唤醒 深度睡眠
高功耗 电池友好
优化建议
| 场景 | 推荐策略 |
|---|---|
| 需要高吞吐(文件传输、OTA) | 协商最短连接间隔(7.5-20ms),使用 Data Length Extension(251 字节 PDU) |
| 平衡模式(一般传感器) | 50-100ms 间隔,根据数据量动态调整 |
| 超低功耗(信标、门锁) | 延长到 500ms-4s,牺牲吞吐换取续航 |
其他影响因素
连接间隔只是吞吐的一个维度,还需配合:
- MTU 大小:更大的 MTU 减少包头开销
- Data Length Extension:BLE 4.2+ 支持 251 字节 PDU(vs 经典 27 字节)
- PHY 速率:BLE 5.0 的 2M PHY 直接翻倍吞吐
- 从机延迟(Slave Latency):允许从机跳过 N 个事件,节能但不影响峰值吞吐
总结:连接间隔是吞吐与功耗的核心权衡参数,缩短间隔能线性提升吞吐,但会显著增加功耗。实际应用中应根据业务场景协商最优值。
浙公网安备 33010602011771号