3. 连接建立过程深度分析:CONNECT_REQ 字段解析与参数协商

抓包抓得多了,你会发现一个规律:连接建立那一刻,决定了整个通信的质量。我见过太多项目,前期广播挺正常,一连接就掉线,或者吞吐量上不去。问题往往就出在 CONNECT_REQ 这个报文上。

今天咱们就把这个报文掰开揉碎,看看里面到底藏了什么玄机。

3.1 CONNECT_REQ 报文结构

先看整体结构。CONNECT_REQ 是 Master 发给 Slave 的,长度固定为 34 字节。我习惯把它分成三块来看:

  • 头部(2 字节):PDU 类型、长度等
  • 连接参数(26 字节):核心参数都在这里
  • 链路层数据(6 字节):AA、CRCInit 等

抓包时,你看到的第一个连接相关报文就是它。嗯,这里要注意:CONNECT_REQ 是在广播信道上发送的,所以抓包工具必须开启广播信道扫描才能抓到。

3.2 核心字段逐项解析

咱们一个一个来。我按实际调试中踩坑的频率排序。

3.2.1 AA(Access Address,4 字节)

AA 是连接的唯一标识。每个连接都有一个独立的 AA,用于数据信道上的数据包过滤。

关键点:AA 不能是 0x8E89BED6(这是广播信道的 AA),也不能是 0x00000000 或 0xFFFFFFFF。我曾经见过一个芯片的协议栈 bug,AA 随机到了 0x8E89BED6,结果广播包和数据包混在一起,抓包软件直接崩溃。

抓包时,你可以在 CONNECT_REQ 里直接读出 AA。后续所有数据信道上的包,AA 字段都必须匹配这个值。

3.2.2 CRCInit(3 字节)

CRC 初始值。每个连接都有自己的 CRCInit,用于计算数据包的 CRC 校验。

为什么需要这个?说白了,就是防止不同连接之间的数据包互相干扰。即使 AA 相同(理论上极小概率),CRCInit 不同也能区分。

3.2.3 WinSize(1 字节)

连接窗口大小,单位是 1.25ms。这个参数决定了 Master 和 Slave 之间建立连接时的时序容差。

我建议你把这个值设大一点(比如 20 以上),尤其是在产品初期。为什么呢?因为 Slave 的时钟精度有限,WinSize 太小容易导致连接建立失败。我遇到过一例:WinSize 设成 10,结果 10 台设备里有 3 台连不上,改成 20 就好了。

3.2.4 Interval(2 字节)

连接间隔,单位是 1.25ms。范围是 6(7.5ms)到 3200(4s)。

这是最关键的参数之一。间隔越小,延迟越低,但功耗越高。我一般这样选:

  • 低延迟场景(如音频):7.5ms - 15ms
  • 常规数据(如传感器):30ms - 100ms
  • 低功耗场景(如信标):100ms - 1s

小技巧:抓包时,你可以通过连续抓取几个连接事件,计算实际间隔是否与 CONNECT_REQ 中声明的一致。有些芯片的协议栈实现有 bug,实际间隔会漂移。

3.2.5 Latency(2 字节)

从设备延迟。Slave 可以跳过多少个连接事件而不必回复。

这个参数很有意思。它允许 Slave 在无数据时休眠,降低功耗。但要注意:Latency 不能超过 Interval 的 1/2(实际是 (Interval * 2) / 1.25ms 的限制)。

我曾经踩过一个坑:把 Latency 设成 10,Interval 设成 30ms,结果 Slave 经常超时断开。后来一查,Latency 太大,Master 以为 Slave 丢了。

3.2.6 Timeout(2 字节)

连接超时时间,单位是 10ms。范围是 10(100ms)到 3200(32s)。

这个参数决定了连接断开的速度。如果 Master 在 Timeout 时间内没收到 Slave 的任何包,就会认为连接断开。

警告:Timeout 必须大于 (Interval * (Latency + 1)) * 2。否则,即使 Slave 正常工作,也可能因为时序抖动而超时断开。我见过一个产品,Timeout 设成 500ms,Interval 设成 100ms,Latency 设成 3,结果一算:100 * (3+1) * 2 = 800ms > 500ms,必断无疑。

3.2.7 ChM(Channel Map,5 字节)

信道映射,共 40 位,对应 37 个数据信道(0-36)和 3 个广播信道(37-39)。

每个位表示该信道是否可用。1 表示可用,0 表示不可用。抓包时,你可以看到 Master 跳频时跳过了哪些信道。

我建议你在调试时关注这个字段。如果某个信道被屏蔽了(比如 Wi-Fi 干扰严重),ChM 里对应的位就是 0。

3.2.8 Hop(5 位)

跳频增量。每次连接事件后,信道号会增加 Hop 值(模 37)。

默认值是 5。为什么是 5?因为 5 和 37 互质,能保证遍历所有信道。如果你改了 Hop,要确保它和 37 互质,否则有些信道永远跳不到。

3.2.9 SCA(Sleep Clock Accuracy,3 位)

睡眠时钟精度。Master 告诉 Slave 自己的时钟精度,Slave 据此计算窗口偏移。

SCA 值越小,精度越高。0 表示 500ppm,7 表示 50ppm。我一般用 7(50ppm),因为大多数芯片的晶振都能达到这个精度。

3.3 连接参数协商与更新

CONNECT_REQ 里的参数是 Master 单方面决定的。但 Slave 可以发起参数更新请求。

3.3.1 参数更新流程

流程很简单:

  1. Slave 发送 LL_CONNECTION_PARAM_REQ
  2. Master 回复 LL_CONNECTION_PARAM_RSP
  3. 双方协商一致后,Master 发送 LL_CONNECTION_PARAM_UPDATE_IND
  4. 新参数在指定时间生效

抓包时,你可以看到这些 LLID 为 0x03 的报文。我建议你关注一下协商过程,有时候 Slave 请求的参数 Master 不同意,就会一直重试。

3.3.2 参数更新触发条件

什么时候需要更新参数?我总结了几种场景:

场景 原参数问题 新参数建议
吞吐量不足 Interval 太大 减小 Interval
功耗过高 Interval 太小 增大 Interval
连接不稳定 Timeout 太小 增大 Timeout
延迟过高 Latency 太大 减小 Latency

经验之谈:我建议你在产品中预留一个参数更新接口。这样在调试时,可以动态调整参数,不用每次都重新烧录固件。我曾经靠这个功能,在客户现场半小时就解决了连接不稳定的问题。

3.4 连接建立过程流程图

下面这张图展示了从广播到连接建立的完整流程,以及 CONNECT_REQ 中各参数的作用时机。

BLE 连接建立流程与 CONNECT_REQ 参数作用时机 BLE 连接建立流程与 CONNECT_REQ 参数作用时机 1. 广播(Advertising) 2. CONNECT_REQ(Master → Slave) 3. 解析 CONNECT_REQ 参数 AA, CRCInit, WinSize, Interval, ... 4. 连接事件(Connection Events) 5. 参数协商(可选) 6. 参数更新生效 CONNECT_REQ 关键参数 • AA:数据信道标识 • CRCInit:CRC 初始值 • WinSize:连接窗口 • Interval:连接间隔 • Latency:从设备延迟 • Timeout:超时时间 • ChM:信道映射 参数协商流程 1. Slave 发送 PARAM_REQ 2. Master 回复 PARAM_RSP 3. Master 发送 UPDATE_IND 4. 新参数生效

3.5 抓包实战建议

最后,分享几个我在实际项目中总结的抓包技巧:

  • 抓 CONNECT_REQ 时,一定要开启广播信道扫描(37/38/39)。很多工具默认只扫数据信道。
  • 关注 Interval 和 Timeout 的比值。如果 Timeout 接近 Interval * 2,说明连接很脆弱。
  • 检查 ChM 中是否有信道被屏蔽。如果屏蔽的信道太多,跳频效果会变差。
  • 参数协商时,抓取 LL_CONNECTION_PARAM_REQ 和 RSP,看双方是否达成一致。

避坑指南:我曾经遇到一个 case,Slave 请求把 Interval 从 50ms 改成 100ms,Master 同意了,但实际生效后,Slave 的 Latency 参数没同步更新,导致连接超时。所以,参数更新后,一定要抓包验证所有参数是否都正确生效了。

好了,CONNECT_REQ 的解析就到这里。记住一句话:连接参数是 BLE 通信的基石。参数设好了,后面的工作就顺了。