4. 链路层控制:LLID、SN/NESN机制、MD位、流控与重传、CRC校验

链路层(Link Layer)是 BLE 协议栈里最忙的层。它不负责你传的是什么数据,只关心一件事——怎么把数据包可靠地送到对端。说白了,它就是那个在嘈杂的 2.4GHz 环境里,帮你把消息“挤”过去的搬运工。

我刚开始调 BLE 的时候,总觉得链路层就是个“传包”的,没什么技术含量。直到有一次产品在电梯里断连,我才意识到——链路层的流控和重传机制,才是整个连接稳定的基石。今天我们就把它彻底拆开看看。

4.1 LLID:这个包是干嘛用的?

每个 BLE 数据包的开头,都有一个 2 位的字段叫 LLID(Logical Link Identifier)。它告诉接收方:这个包是控制命令,还是普通数据,或者是数据包的结尾。

LLID 值 含义 典型场景
00 LL 控制 PDU 连接参数更新、频道映射、终止连接
01 LL 数据 PDU(续帧) 大数据分片传输,非最后一包
10 LL 数据 PDU(起始或完整帧) 单包数据,或分片的第一包
11 保留 暂未使用
我的习惯:调试时先看 LLID。如果 LLID 是 00 但你不认识那个 opcode,大概率是协议栈版本不匹配。我曾经被一个私有 opcode 坑了三天,后来发现是对方芯片的固件打了补丁。

4.2 SN/NESN:怎么确认对方收到了?

BLE 的确认机制不靠单独的 ACK 包,而是靠两个 1 位的标志位:SN(Sequence Number)和 NESN(Next Expected Sequence Number)。

你想想看,每次发一个包,SN 就翻转一次(0→1→0→1...)。接收方收到后,把 NESN 也翻转一次,表示“我收到了,请发下一个”。如果发送方发现 NESN 没变,就知道包丢了,得重传。

核心逻辑:

  • 发送方:SN 表示当前包的序号
  • 接收方:NESN 表示期望收到的下一个 SN
  • 如果 SN == NESN,说明是重传包(对方没收到)
  • 如果 SN != NESN,说明是新包(对方收到了上一个)

我记得第一次看协议栈源码时,被这两个位的逻辑绕晕了。后来画了个状态机才明白——其实就是个“你发我回”的乒乓游戏。

4.3 MD 位:还有数据要发吗?

MD(More Data)位只有 1 位,但它决定了连接事件(Connection Event)什么时候结束。每次数据交互后,发送方通过 MD 位告诉对方:“我还有数据,别急着收工。”

  • MD = 0:我没数据了,这个连接事件可以结束了
  • MD = 1:我还有数据,继续发

这个机制在 BLE 4.2 的数据长度扩展(DLE)之后变得特别重要。因为一个连接事件里可以连续发多个包,MD 位就是那个“再来一包”的信号。

我曾经踩过的坑:有一次设备在传输大文件时频繁断连,抓空包发现 MD 位一直为 1,但实际已经没有数据了。原因是应用层没有及时清空发送队列,导致链路层误判。后来我在发送回调里加了队列深度检查,问题就解决了。

4.4 流控与重传:怎么保证不丢包?

BLE 的流控是“停等协议”(Stop-and-Wait)的变种。说白了就是:发一个,等确认,再发下一个。虽然效率不高,但在低功耗场景下够用了。

重传的逻辑也很简单:

  1. 发送方发出一个包,启动一个定时器(T_IFS + 一点点余量)
  2. 如果在下一个连接事件里收到 NESN 翻转,说明对方收到了
  3. 如果没收到,或者 NESN 没变,就重传
  4. 重传次数超过上限(默认 4 次),就断开连接
我的建议:不要盲目依赖硬件自动重传。在干扰严重的环境里(比如 2.4GHz Wi-Fi 密集区),适当降低连接间隔(Connection Interval)比增加重传次数更有效。我做过对比测试,连接间隔从 30ms 降到 15ms,重传率下降了 60%。

4.5 CRC 校验:数据到底有没有被篡改?

每个 BLE 数据包末尾都有 3 字节的 CRC(Cyclic Redundancy Check)。它覆盖了 PDU 头和数据部分,但不包括接入地址(Access Address)。

CRC 的生成多项式是固定的:

CRC24 多项式: x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1
初始值: 0x555555

接收方收到包后,用同样的多项式重新计算 CRC。如果结果和包里的 CRC 不一致,就直接丢弃这个包,不会触发任何重传——因为链路层根本不知道这个包来过。

关键点:

  • CRC 校验失败 → 静默丢弃 → 发送方超时重传
  • CRC 校验通过 → 处理包内容 → 翻转 NESN 确认
  • CRC 只检错,不纠错

嗯,这里要注意:CRC 校验失败不一定会导致断连。只要在重传次数内成功一次,连接就能继续。但如果信道质量太差,连续丢包就会触发“连接超时”(Connection Supervision Timeout)。

4.6 整体流程:一个连接事件里发生了什么?

下面这张图展示了主从设备在一个连接事件里的典型交互流程:

BLE 连接事件数据交互流程 主设备 (Master) 从设备 (Slave) 发送数据包 (SN=0, MD=1) 回复数据 (NESN=1, MD=0) 发送数据包 (SN=1, MD=0) 回复空包 (NESN=0, MD=0) 关键观察 SN 和 NESN 在每个成功交互后翻转 MD=0 表示该方向数据发送完毕,连接事件可结束

4.7 实际调试中的常见问题

现象 可能原因 排查方法
连接频繁断开 重传超限 / 连接超时 抓空包看 SN/NESN 是否正常翻转
数据吞吐量低 MD 位未正确设置 检查应用层是否及时填充发送队列
偶发数据错误 CRC 校验失败但未触发重传 检查射频环境,降低连接间隔
从设备不响应 LLID 为 00 的控制 PDU 被忽略 确认 opcode 是否被协议栈支持
避坑指南:我曾经在一个项目中,从设备偶尔不回复数据,抓空包发现 NESN 一直不变。排查了两天,最后发现是从设备的晶振偏差太大,导致时序漂移,接收窗口没对齐。换了个精度更高的晶振就解决了。所以,链路层的问题不一定是协议栈的锅,硬件也可能背锅。

链路层控制是 BLE 连接稳定性的核心。LLID 告诉你包的类型,SN/NESN 保证不丢包,MD 位控制传输节奏,CRC 确保数据完整。这四个机制配合起来,才让 BLE 在嘈杂的 2.4GHz 环境里还能可靠工作。