3. BLE 协议栈基础:GAP 与 GATT 角色、广播与扫描、连接建立过程、MTU 与 PDU 结构

做 BLE 开发,绕不开两个核心协议:GAP 和 GATT。

我刚开始接触时,也分不清它们到底管什么。后来踩了几个坑才明白——GAP 管“怎么找到对方”,GATT 管“找到之后怎么交换数据”。说白了,一个管连接前,一个管连接后。

3.1 GAP 角色:谁在说话,谁在听?

GAP(Generic Access Profile)定义了设备在无线网络中的四种角色。你想想看,一个蓝牙设备不可能既是广播者又是扫描者——同一时刻只能选一个。

角色 方向 典型场景
Broadcaster(广播者) 只发不收 信标、温湿度传感器
Observer(观察者) 只收不发 扫描仪、网关
Peripheral(外设) 可连接广播 手环、心率带
Central(中心) 主动扫描并发起连接 手机、平板
我的经验:做低功耗产品时,我习惯把传感器设成 Peripheral 角色,手机做 Central。但如果你做的是网关,Observer 角色就够了——只收不发,功耗更低。

3.2 广播与扫描:连接前的“打招呼”

广播和扫描,是 BLE 连接的第一步。设备通过广播信道(37/38/39)发送数据包,扫描设备则在三个信道上轮流监听。

广播包分两种:

  • 可连接广播(Connectable Advertising):扫描设备可以发起连接请求。
  • 不可连接广播(Non-connectable Advertising):只发不收,适合信标。

扫描也有两种模式:

  • 被动扫描(Passive Scanning):只收广播包,不发请求。
  • 主动扫描(Active Scanning):收到广播后,发送 SCAN_REQ 获取更多数据。
注意:主动扫描会增加功耗。我曾经在一个网关项目里用了主动扫描,结果电池撑不到两天。后来改成被动扫描 + 延长扫描间隔,续航直接翻倍。

3.3 连接建立过程:从广播到数据通道

连接建立,说白了就是“你发广播,我发请求,然后握手”。流程如下:

  1. Peripheral 发送可连接广播(包含设备地址、广播数据)
  2. Central 收到广播后,发送 CONNECT_REQ(包含连接参数)
  3. 双方切换到数据信道(37 个数据信道之一)
  4. 开始按连接间隔交换数据

连接参数里,最关键的三个:

  • 连接间隔(Connection Interval):7.5ms ~ 4s,越小功耗越高,但延迟低。
  • 从机延迟(Slave Latency):允许从机跳过几次数据交换,省电。
  • 监督超时(Supervision Timeout):超过这个时间没收到包,就认为连接断开。
避坑指南:我曾经把连接间隔设成 7.5ms,结果手机端功耗爆炸。后来改成 30ms + 从机延迟 4,功耗降了 60%。记住:低功耗不是靠缩短连接间隔,而是靠拉长间隔 + 合理利用从机延迟。

3.4 GATT 角色:连接后的“数据通道”

连接建立后,GATT(Generic Attribute Profile)开始工作。它定义了两种角色:

  • GATT Server:提供数据的一方(如传感器)
  • GATT Client:读取/写入数据的一方(如手机 App)

GATT 的数据结构是分层的:

  • Service(服务):一组相关功能的集合,比如“电池服务”
  • Characteristic(特征):具体的数据点,比如“电池电量百分比”
  • Descriptor(描述符):特征的附加信息,比如“电量单位是 %”

每个 Characteristic 都有属性:

  • Read:客户端可以读
  • Write:客户端可以写
  • Notify/Indicate:服务端主动推送数据
我的习惯:Notify 比 Indicate 更省电,因为 Indicate 需要客户端确认。做数据采集时,我优先用 Notify,除非数据必须可靠传输。

3.5 MTU 与 PDU 结构:数据包长什么样?

MTU(Maximum Transmission Unit)是 BLE 连接中一次能传输的最大数据长度。默认是 23 字节(包含 3 字节头 + 20 字节有效载荷)。

但你可以通过 MTU 协商把它变大。流程很简单:

  1. 客户端发送 MTU Exchange Request
  2. 服务端回复 MTU Exchange Response
  3. 双方取较小的值作为最终 MTU

MTU 最大能到 247 字节(BLE 4.2)或 512 字节(BLE 5.0)。

注意:MTU 协商必须在连接建立后、数据交换前完成。我见过有人先发数据再协商 MTU,结果数据被截断——嗯,这个坑我踩过。

PDU(Protocol Data Unit)是 BLE 数据包的基本单位。结构如下:

字段 长度 说明
Header 2 字节 包含 PDU 类型、长度、LLID 等
Payload 0~251 字节 实际数据内容
MIC 4 字节 消息完整性校验(加密时才有)

PDU 类型包括:

  • LL_CONNECT_REQ:连接请求
  • LL_DATA:数据包
  • LL_EMPTY:空包(用于保持连接)
  • LL_TERMINATE_IND:断开连接
关键点:MTU 是应用层能用的最大数据长度,PDU 是链路层实际传输的数据单元。MTU 协商后,应用层一次能发更多数据,但链路层还是按 PDU 分包发送。

3.6 知识结构图:GAP 与 GATT 的关系

下面这张图,帮你理清 GAP 和 GATT 在整个 BLE 协议栈中的位置:

BLE 协议栈:GAP 与 GATT 关系图 BLE 协议栈:GAP 与 GATT 关系图 应用层(App / Profile) GAP 广播 / 扫描 / 连接 GATT 服务 / 特征 / 数据交换 链路层(LL) PDU 封装 / 重传 / 加密 物理层(PHY) 1M / 2M / 编码 PHY 说明: GAP 负责连接前的广播、扫描和连接建立;GATT 负责连接后的数据读写和通知。 两者在链路层之上,共用同一个物理层和链路层。

3.7 小结:记住这几点就够了

  • GAP 管连接前,GATT 管连接后
  • 广播是单向的,扫描是接收端的行为
  • 连接建立靠 CONNECT_REQ,参数决定功耗
  • MTU 越大,单次传输数据越多,但协商必须在数据交换前完成
  • PDU 是链路层的数据单元,MTU 是应用层能用的最大长度
最后一句:做 BLE 开发,别急着写代码。先把 GAP 和 GATT 的角色搞清楚,再动手——省得后面改来改去。