3、链路层核心机制:设备状态机、空中包格式、白名单与随机地址

链路层(Link Layer)是BLE协议栈的心脏。我常说,搞懂了链路层,你就掌握了BLE的命脉。这一章我们聚焦三个核心:设备状态机、空中包格式、以及地址与白名单机制。说白了,就是搞清楚设备在干什么、怎么说话、以及认不认识对方。

3.1 设备状态机:五种状态的流转

BLE链路层定义了五种设备状态:待机(Standby)、广播(Advertising)、扫描(Scanning)、发起连接(Initiating)、已连接(Connected)。这五个状态构成了所有BLE设备的行为骨架。

我刚开始做BLE开发时,总觉得状态机很抽象。后来调试一个广播不出来的问题,才发现是状态机卡在Standby没跳出去。嗯,从那以后我每次初始化都会检查状态机是否真的进入了预期状态。

核心要点:同一时刻,设备只能处于一种状态。状态切换必须遵循规范定义的路径,不能随意跳转。

状态切换路径如下:

  • Standby → 任何状态(起始点)
  • Advertising → Standby 或 Connected
  • Scanning → Standby
  • Initiating → Standby 或 Connected
  • Connected → Standby(断开连接后)

这里有个容易踩的坑:Initiating状态不能直接切到Scanning。我曾经在项目中想当然地认为发起连接前可以先扫描一下,结果协议栈直接报错。后来查规范才发现,Initiating和Scanning是互斥的,必须回到Standby再切换。

下面这张图展示了状态机的完整流转关系:

BLE 链路层状态机 Standby Advertising Scanning Initiating Connected 广播参数配置 扫描参数配置 连接参数配置 停止广播 停止扫描 连接失败/超时 收到连接请求 发送连接请求成功 断开连接 注意:Initiating 和 Scanning 不能直接切换,必须经过 Standby Connected 状态下,设备以连接事件(Connection Event)为单位进行通信

3.2 空中包格式:链路层的数据帧

BLE空中包(Air Interface Packet)是设备间通信的基本单位。你想想看,两个设备之间传数据,总得有个统一的格式吧?不然谁听得懂谁?

链路层包格式分为两种:广播信道包和数据信道包。它们的结构略有不同,但核心骨架一致。

3.2.1 通用包结构

字段 长度(字节) 说明
Preamble 1 前导码,用于接收端同步时钟。广播包固定为0xAA,数据包为0xAA或0x55
Access Address 4 访问地址。广播包固定为0x8E89BED6;数据包使用连接期间分配的随机值
PDU 2~257 协议数据单元,包含头部和有效载荷
CRC 3 24位循环冗余校验,用于检测传输错误

这里有个细节我特别想强调:Access Address。广播包的访问地址是固定的,所以任何扫描设备都能收到。但数据包的访问地址是连接建立时随机生成的,这就保证了不同连接之间的数据不会串扰。我在项目中遇到过两个设备明明没配对却收到了对方的数据,一查就是Access Address冲突了——虽然概率极低,但确实存在。

3.2.2 广播信道PDU格式

广播信道PDU分为两种类型:广播指示(Advertising PDU)和扫描响应(Scan Response PDU)。它们的头部结构相同:

广播PDU头部(2字节):
  - PDU类型(4位):0x00 可连接广播,0x01 定向广播,0x02 不可连接广播,0x03 扫描响应
  - RFU(2位):保留位,必须为0
  - ChSel(1位):信道选择算法
  - TxAdd(1位):发送地址类型(0=公共地址,1=随机地址)
  - RxAdd(1位):接收地址类型
  - 长度(6位):有效载荷长度(6~37字节)
  - RFU(2位):保留位

实战经验:广播包的有效载荷最大31字节。如果你需要广播更多数据,可以考虑使用扫描响应(Scan Response)或者扩展广播(LE Extended Advertising,BLE 4.2+)。我早期做beacon项目时,31字节塞得满满当当,后来发现有些手机扫描不到完整数据,就是因为广播包超长被截断了。

3.2.3 数据信道PDU格式

数据信道PDU分为两种:LL Data PDU(传数据)和LL Control PDU(传控制信息)。

字段 长度(字节) 说明
LLID 2位 数据包标识:0x01=数据续包,0x02=数据起始包,0x03=控制包
NESN 1位 下一个期望序列号,用于流量控制和重传
SN 1位 当前包序列号
MD 1位 更多数据标志:1表示发送方还有数据要发
长度 8位 有效载荷长度(0~251字节,BLE 4.2+支持数据长度扩展)

数据信道的流控机制很有意思。SN和NESN两个位就实现了确认重传——说白了就是:你发一个包,我回一个包,通过序列号告诉对方我收到了没。我曾经调试一个吞吐量上不去的问题,发现是NESN一直不翻转,导致发送端不断重传同一个包。最后定位到是接收端的ACK处理有bug。

3.3 白名单:让连接更安全

白名单(White List)是链路层提供的一种地址过滤机制。说白了,就是只允许我认识的人跟我说话。

白名单可以应用在以下场景:

  • 广播端:只允许白名单中的设备扫描或连接我
  • 扫描端:只处理白名单中设备的广播包
  • 发起连接端:只连接白名单中的设备

注意:白名单存储在链路层控制器中,容量有限(通常8~16个条目)。不要试图把几千个设备地址塞进去,那是应用层该干的事。

白名单的配置流程一般如下:

  1. 调用HCI命令清空白名单
  2. 逐个添加设备地址(指定地址类型:公共或随机)
  3. 使能白名单过滤模式
  4. 启动广播/扫描/发起连接

我在一个门禁项目中用过白名单。当时要求只有注册过的手机才能开锁,我们就把手机的随机地址加入白名单。但有个坑:随机地址是会变的(尤其是隐私地址)。后来我们改用公共地址+白名单的方案才稳定下来。

3.4 随机地址:隐私保护的基石

BLE地址分为两种:公共地址(Public Address)和随机地址(Random Address)。公共地址由IEEE分配,全球唯一。随机地址又分三种:

类型 高位标志 说明
静态随机地址 11 设备上电时随机生成,重启后不变。不能用于隐私保护
私有可解析地址 01 基于IRK(身份解析密钥)生成,可被配对设备解析。用于隐私保护
私有不可解析地址 00 完全随机,无法被解析。用于低安全场景

你可能会问:为什么要搞这么多种地址?

原因很简单——隐私。如果设备一直用同一个公共地址广播,别人就能跟踪你的位置。私有可解析地址每隔一段时间(通常15分钟)就会变化,但配对过的设备可以通过IRK解析出你的真实身份。我做过一个手环项目,用的就是私有可解析地址,既保护了用户隐私,又能让手机识别出是同一个手环。

避坑指南:我曾经在测试中发现,某些蓝牙芯片对私有可解析地址的解析速度很慢,导致连接建立延迟。后来我们优化了IRK的存储和查找算法,把解析时间从50ms降到了5ms。如果你遇到连接慢的问题,不妨检查一下地址解析这块。

3.5 综合应用:一个典型的连接建立流程

把前面讲的知识串起来,一个典型的BLE连接建立流程是这样的:

  1. 广播端:进入Advertising状态,使用随机地址(或公共地址)发送广播包
  2. 扫描端:进入Scanning状态,接收广播包(可配合白名单过滤)
  3. 发起连接端:收到广播后,进入Initiating状态,发送连接请求(CONNECT_REQ)
  4. 广播端:收到连接请求后,进入Connected状态
  5. 发起连接端:确认连接建立,也进入Connected状态
  6. 双方:在Connected状态下,按照连接事件交换数据包

这个过程中,空中包格式决定了数据怎么封装,地址机制决定了设备怎么识别,白名单决定了谁可以参与。三者缺一不可。