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再切换。
下面这张图展示了状态机的完整流转关系:
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个条目)。不要试图把几千个设备地址塞进去,那是应用层该干的事。
白名单的配置流程一般如下:
- 调用HCI命令清空白名单
- 逐个添加设备地址(指定地址类型:公共或随机)
- 使能白名单过滤模式
- 启动广播/扫描/发起连接
我在一个门禁项目中用过白名单。当时要求只有注册过的手机才能开锁,我们就把手机的随机地址加入白名单。但有个坑:随机地址是会变的(尤其是隐私地址)。后来我们改用公共地址+白名单的方案才稳定下来。
3.4 随机地址:隐私保护的基石
BLE地址分为两种:公共地址(Public Address)和随机地址(Random Address)。公共地址由IEEE分配,全球唯一。随机地址又分三种:
| 类型 | 高位标志 | 说明 |
|---|---|---|
| 静态随机地址 | 11 | 设备上电时随机生成,重启后不变。不能用于隐私保护 |
| 私有可解析地址 | 01 | 基于IRK(身份解析密钥)生成,可被配对设备解析。用于隐私保护 |
| 私有不可解析地址 | 00 | 完全随机,无法被解析。用于低安全场景 |
你可能会问:为什么要搞这么多种地址?
原因很简单——隐私。如果设备一直用同一个公共地址广播,别人就能跟踪你的位置。私有可解析地址每隔一段时间(通常15分钟)就会变化,但配对过的设备可以通过IRK解析出你的真实身份。我做过一个手环项目,用的就是私有可解析地址,既保护了用户隐私,又能让手机识别出是同一个手环。
避坑指南:我曾经在测试中发现,某些蓝牙芯片对私有可解析地址的解析速度很慢,导致连接建立延迟。后来我们优化了IRK的存储和查找算法,把解析时间从50ms降到了5ms。如果你遇到连接慢的问题,不妨检查一下地址解析这块。
3.5 综合应用:一个典型的连接建立流程
把前面讲的知识串起来,一个典型的BLE连接建立流程是这样的:
- 广播端:进入Advertising状态,使用随机地址(或公共地址)发送广播包
- 扫描端:进入Scanning状态,接收广播包(可配合白名单过滤)
- 发起连接端:收到广播后,进入Initiating状态,发送连接请求(CONNECT_REQ)
- 广播端:收到连接请求后,进入Connected状态
- 发起连接端:确认连接建立,也进入Connected状态
- 双方:在Connected状态下,按照连接事件交换数据包
这个过程中,空中包格式决定了数据怎么封装,地址机制决定了设备怎么识别,白名单决定了谁可以参与。三者缺一不可。