【从零开始】手写BLE协议栈(3-1)连接契约:CONNECT_IND
连接契约:CONNECT_IND
前提知识
阅读本文需要具备以下基础:
- 理解广播与主动扫描流程(第 2 篇和 3-1 篇有详细讲解):
CONNECT_IND不是凭空出现的,它发生在 Master 收到可连接广播ADV_IND之后。如果不知道广播信道上的三包握手,就很难理解为什么连接建立还发生在广播信道上。 - 理解 T_IFS 的 150 µs 约束(第 3-2 篇有详细讲解):
CONNECT_IND也是一帧"回应包",它同样必须在上一帧结束后的 150 µs 内发出。本文不再展开计时原理,只聚焦这个包里到底装了什么。 - 理解 BLE 广播 PDU 的基本结构(第 2 篇有详细讲解):
CONNECT_IND虽然语义完全不同,但它仍然是链路层 PDU,仍然有 Header、Length 和 Payload 这些基本组成。
不需要提前了解跳频算法、窗口放宽或调度器细节。本文只回答一个问题:Master 发出的 CONNECT_IND 这 22 字节,到底在约定什么。
一、从“广场广播”到“加密私聊”
在广播阶段,所有设备都在用同一套公共规则通信:
- 公共信道:37、38、39
- 公共接入地址:Access Address 固定是
0x8E89BED6 - 公共 CRC 校验初始值:
0x555555
建立连接之后,两台设备需要私下通话,就要建立一套私密的规则:
- 专属信道:可用的数据信道(Channel Map)?如何跳频?
- 专属接入地址:私有 Access Address
- 专属通信容错机制:通信间隔(Interval)?接收窗口 (WinOffset)?

注意: CONNECT_IND 仍然发生在广播信道上,而不是数据信道上。这一点非常重要。进入数据信道之后具体发生了什么,将在第 4-1 章中详细解释。
二、22 字节的内存布局
CONNECT_IND 的 Payload 固定为 22 字节,按字节顺序排列如下:

CONNECT_IND Payload 内存布局(22 字节):
字节偏移 字段 长度 说明
[0..3] AA 4B 连接私有 Access Address
[4..6] CRCInit 3B 连接私有 CRC 初始值
[7] WinSize 1B 首次接收窗口大小,单位 1.25 ms
[8..9] WinOffset 2B 首次接收窗口起点偏移,单位 1.25 ms
[10..11] Interval 2B 连接间隔,单位 1.25 ms
[12..13] Latency 2B Slave Latency
[14..15] Timeout 2B Supervision Timeout,单位 10 ms
[16..20] ChM 5B 37-bit Channel Map(高 3 bit 保留)
[21] Hop/SCA 1B 低 5 bit 为 Hop,高 3 bit 为 SCA
最后一个字节为什么被拆成两个字段?因为 Hop 只需要 5 bit,SCA 只需要 3 bit,刚好能塞进同一个字节里。BLE 为了节省空口时间,把所有能压缩的位都压到了极致。
这 22 字节看起来短,但信息密度非常高。它不是一组平铺的配置项,而是三类信息混在一起:
- 身份类:AA、CRCInit,决定"以后怎么认出彼此"
- 时间类:WinSize、WinOffset、Interval、Latency、Timeout,决定"什么时候见面、多久算失联"
- 频率类:ChM、Hop、SCA,决定"在哪个信道见面、时钟误差怎么算"
三、身份类字段:从公共广场切到私有通道
3.1 AA:连接专属的接头暗号

AA(Access Address,接入地址)是链路层用来识别一条连接的 32 bit 标识。可以把它理解为这条连接的"门牌号"。
广播阶段大家共用同一个门牌号 0x8E89BED6,因为广播本来就是公开的,任何设备都可以来听。但连接阶段不行。连接一旦建立,Slave 必须只接收属于自己这条连接的数据包,而把旁边其他连接、其他广播全部忽略掉。
所以 Master 必须在 CONNECT_IND 里给这条连接分配一个新的、私有的 AA。之后双方只要在空中检测到前导码后面的 32 bit AA 不匹配,就立刻把整包丢掉,不继续接收。
如果不用私有 AA,会发生什么?最直接的后果是:多个连接之间会彼此串台。你的 Slave 可能会把别人的连接事件误认为自己的连接包,CRC 虽然还能再过滤一层,但空口误触发概率会大幅上升,整条链路变得极不稳定。
3.2 CRCInit:第二层“防串台”

CRCInit 是 CRC 计算的初始值。广播阶段它固定是 0x555555,而连接阶段每条连接使用独立的 CRCInit。
这一步的意义是再加一层"专线属性"。即使某个包恰好在 AA 上碰巧匹配,CRCInit 不同也会让 CRC 校验结果完全不同,进一步降低跨连接误收的概率。
AA 像门牌号,CRCInit 像门锁的齿形。两者配合,广播世界的公共通道才真正被切换成连接世界的私有通道。
四、时间类字段:未来的约会表
4.1 WinSize 与 WinOffset

这两个字段只服务于第一次连接事件。
WinOffset 告诉 Slave:从 CONNECT_IND 收完那一刻开始,再等多久,第一次正式约会才开始。单位是 1.25 ms。
WinSize 告诉 Slave:这次第一次约会的接收窗口有多宽,单位同样是 1.25 ms。Slave 不需要在整个 WinOffset 期间一直开着射频,只需要在规定的窗口里等待 Master 的第一包即可。
很多初学者会问:既然 Master 自己知道什么时候发第一包,为什么还要给一个窗口而不是单点时间?原因是物理世界里时钟永远有误差。Slave 不知道 Master 的定时器和自己的定时器会偏多少,所以第一次约会必须留一个合法窗口,而不是赌一个无限精确的时刻。
4.2 Interval

Interval 是连接间隔,单位 1.25 ms。它定义的是:从一个连接事件锚点到下一个连接事件锚点之间的时间。
它决定了这条连接的基本节奏。Interval 小,双方约会频繁,延迟低,但射频更忙、功耗更高;Interval 大,约会稀疏,功耗下降,但交互延迟上升。
4.3 Latency
Latency 允许 Slave 在没有数据要发时,连续跳过若干个连接事件而不算丢连接。
它的价值在于省电。假设 Interval 是 50 ms,Latency 是 4,那么 Slave 最多可以连续跳过 4 个事件,只在第 5 个事件再起来听一次。这样平均唤醒频率显著下降,电池设备的续航会明显提升。
4.4 Timeout
Timeout 是监督超时,单位 10 ms。它回答的是另一个问题:多久没见到对方,才算这条连接真的死了?
它必须大于一个完整的"允许偷懒周期",否则 Slave 明明只是按 Latency 合法睡了几轮,链路层却先判连接超时断开,这就自相矛盾了。
五、频率类字段:未来在哪些信道见面
5.1 ChM:哪些数据信道可用
ChM(Channel Map)是一个 37 bit 的位图,对应 BLE 的 37 个数据信道(0 到 36)。某 bit 为 1 表示该信道当前允许使用,为 0 表示该信道暂时屏蔽。
为什么需要这个位图?因为 2.4 GHz 频段并不干净。Wi-Fi、微波炉、其他蓝牙设备都可能在局部频点造成干扰。如果 Master 发现某几个信道误码率异常高,就可以通过后续控制包把它们从 ChM 里剔除,减少重传和丢包。
所以 ChM 本质上是一张"当前可通行道路图"。
5.2 Hop:下一个连接事件往哪跳
Hop 是跳频增量,取值范围 5 到 16。它不是"本次用哪个信道",而是"从当前信道推导下一个候选信道时,每次向前跳多少格"。
这个值不能随便取 1、2 或 37,原因在于跳频需要足够好的覆盖性。若增量太小,连接会在少数几个相邻信道之间打转,既降低抗干扰能力,也不符合 BLE 规范对跳频均匀性的要求。第 10 篇会专门展开这个问题。
5.3 SCA:我家时钟大概有多飘

SCA(Sleep Clock Accuracy,睡眠时钟精度)不是实时测得的误差值,而是一个精度等级。Master 用它告诉 Slave:"我的低速时钟大概在这个 ppm 等级上。"
Slave 后续计算 Window Widening(窗口放宽)时,就要依赖这个等级来估算:随着时间拉长,Master 的发送时刻可能向前或向后漂移多少。SCA 越差,Slave 就必须提前更早开接收、延后更晚关接收,功耗也会更高。
本系列教程同款硬件:👇
芯片: nRF 52832 开发板
工具: nRF 52840 BLE Dongle 蓝牙嗅探器
工具: 逻辑分析仪
工具: BPA low energy 蓝牙分析仪
本文版权归作者:ixbwer所有,转载请注明原文链接:https://www.cnblogs.com/ixbwer/p/19796573,否则保留追究法律责任的权利。

浙公网安备 33010602011771号