3、链路层基础:状态机、角色、连接建立与数据包格式

链路层(Link Layer)是 BLE 协议栈里最核心的一层,没有之一。它直接控制着射频的收发、状态切换、连接管理。说白了,你上层再怎么花哨,最终都得靠链路层把数据变成无线信号发出去。

我个人习惯把链路层理解成一个“有限状态机”——它永远只处于某一种状态,收到特定事件后才会跳转到下一个状态。搞懂了状态机,你就搞懂了 BLE 设备在干什么。

3.1 链路层状态机

BLE 链路层定义了 5 种基本状态:

  • Standby(待机)—— 啥也不干,射频关闭。
  • Advertising(广播)—— 向外发送广播包,让别人发现我。
  • Scanning(扫描)—— 监听广播包,看看周围有没有设备。
  • Initiating(发起连接)—— 收到广播后主动发起连接请求。
  • Connection(连接)—— 已经建立连接,按固定间隔收发数据。

状态之间的跳转关系,我画了一张图,你一看就明白:

BLE 链路层状态机 BLE 链路层状态机 Standby Advertising Scanning Initiating Connection 开始广播 开始扫描 发起连接 停止广播 收到连接请求 发送连接请求 断开连接 说明:实线箭头表示主动跳转,虚线箭头表示停止/断开后回到 Standby。 Connection 是唯一不能直接回到 Standby 以外的状态。

嗯,这里要注意:Connection 状态是终点。一旦进入连接,就只能断开回到 Standby,不能直接跳到广播或扫描。为什么?因为连接是点对点的,已经占用了固定的射频资源,不能再干别的事。

我的经验:调试时经常遇到设备“假死”——其实它还在 Connection 状态,只是连接参数设得太激进,导致频繁超时。先查连接间隔和 supervision timeout,别急着怀疑硬件。

3.2 设备角色:Master 与 Slave

连接建立后,两个设备就有了明确的分工:

角色 职责 典型设备
Master(主设备) 决定连接时序,发起数据包交换 手机、平板、PC
Slave(从设备) 跟随主设备的时序,响应数据包 手环、传感器、耳机

说白了,Master 是“发令枪”,Slave 是“跟跑者”。Master 在每个连接事件(Connection Event)开始时先发一个包,Slave 收到后才能回复。如果 Slave 没收到,那就得等下一个连接事件。

我记得刚做 BLE 项目时,有个同事把 Master 和 Slave 搞反了——从设备一直主动发数据,结果主设备根本不收。折腾了两天才发现是角色配置错了。你想想看,如果连谁先说话都没定好,通信肯定乱套。

3.3 连接建立过程

连接建立不是一蹴而就的,它分三步走:

  1. 广播阶段:Slave(通常是外设)在广播信道上发送 ADV_IND 或 ADV_DIRECT_IND 包。
  2. 扫描阶段:Master 在广播信道上扫描,收到广播包后,如果感兴趣,就进入 Initiating 状态。
  3. 连接请求:Master 发送 CONNECT_REQ 包,里面包含了连接参数(连接间隔、延迟、超时时间等)。

一旦 Slave 收到 CONNECT_REQ 并确认,双方就进入 Connection 状态。从这一刻起,它们不再使用广播信道,而是切换到数据信道(37 个数据信道之一)进行通信。

关键点:CONNECT_REQ 包里的连接参数是 Master 决定的。Slave 只能接受或拒绝(拒绝就是不理它)。所以,如果你想让从设备有更多话语权,可以在广播数据里带上自己期望的连接参数,让 Master 参考。

3.4 数据包格式

BLE 链路层的数据包结构非常简洁。我直接给你看格式:

字段 长度(字节) 说明
Preamble 1 同步序列,固定为 0xAA 或 0x55
Access Address 4 连接唯一标识,广播时为 0x8E89BED6
PDU 2~257 协议数据单元,包含头部和有效载荷
CRC 3 24 位循环冗余校验

PDU 内部又分两种:

  • 广播信道 PDU:用于广播、扫描、发起连接。头部包含 PDU 类型、发送地址类型、接收地址类型等。
  • 数据信道 PDU:用于连接后的数据交换。头部包含 LLID(链路层标识)、NESN/SN(序列号)、MD(更多数据)等。

这里有个坑,我踩过:Access Address 在广播阶段是固定的 0x8E89BED6,但连接建立后,Master 会在 CONNECT_REQ 里分配一个新的 Access Address。如果你在抓包时发现数据信道上的 Access Address 不是这个值,别慌,那是正常的。

避坑指南:我曾经在调试时发现 Slave 收不到数据,抓包一看,Access Address 写错了。原因是固件里把广播信道的 Access Address 直接复制到了数据信道。记住:连接后的 Access Address 是 Master 随机生成的,必须从 CONNECT_REQ 里解析出来。

3.5 数据包示例

给你看一个实际的广播包 PDU 结构(十六进制):

广播信道 PDU 示例:
02 00 00 00 01 02 03 04 05 06

解析:
- 第一个字节 0x02:PDU 类型 = ADV_IND(可连接的非定向广播)
- 第二个字节 0x00:发送地址类型 = 公共地址
- 第三~八字节:广播地址 = 00:00:01:02:03:04
- 后续字节:广播数据(本例中为 05 06)

数据信道 PDU 稍微复杂一点,因为它要处理流控和重传:

数据信道 PDU 示例(带 LLID 和数据):
1E 08 00 01 02 03 04 05

解析:
- 第一个字节 0x1E:LLID=01(数据 PDU 继续),NESN=1,SN=1,MD=0
- 第二个字节 0x08:有效载荷长度 = 8 字节
- 后续字节:实际数据

你想想看,如果没有这些序列号和确认机制,丢包了怎么办?BLE 的链路层通过 NESN/SN 实现了简单的停止等待 ARQ(自动重传请求)。Slave 收到包后,如果 CRC 校验通过,就翻转 NESN 表示确认;如果没通过,就不翻转,Master 会重传。

我的习惯:调试链路层问题时,我一般先看 PDU 头部的 NESN/SN 是否在交替变化。如果一直不变,说明对端没收到确认,大概率是射频干扰或距离太远。

好了,链路层的基础就这些。状态机决定了设备的行为,角色决定了谁控制时序,连接建立是广播到连接的桥梁,数据包格式则是通信的“语法”。把这些搞明白,后面讲 GAP 和 GATT 时你会觉得轻松很多。