基于 BLE 5.1 协议 Core Spec。
目录
9.2.1 Unmapped Event Channel Selection
9.2.2 Event Mapping to Used Channel Index
10、Acknowledgment and flow control
1、建立连接
Connection 的内容较为丰富,前面只是讲了如何进入 Connection State,现在我们拿上放大镜,仔细窥视其中的内在东西。
首先,聊一聊怎么进入 Connection State 吧(虽然前面聊过):
进入连接,有两种方式:
1、针对 Legacy 的情况:Initiator 在 primary advertising channel 上收到期望连接的 ADV,直接回复 CONNECT_IND,则本端进入 Connection State,成为 Master。
2、针对 Extended 的情况,Initiator 在 primary advertising channel 上收到期望连接的 EXT_ADV_IND,并在 secondary advertising channel 上接收到了 AUX_ADV_IND,并发送连接请求 AUX_CONNECT_REQ,对端回复 AUX_CONNECT_RSP,则本端进入 Connection State ,成为 Master。
这里需要说明一下,交互 CONNECT_IND 或者 AUX_CONNECT_REQ/AUX_CONNECT_RSP,这并不代表双方真正的建立起了,连接进入 Connection State,因为空口情况复杂,您并不一定能够保证在这个关键的交互时刻点,对方一定能够收到你发送的这个关键的数据包,万一对方没收到呢?的确存在这种情况(调试的时候,抓包也遇到过),那么怎么样才算是连接真正建立起来了呢?让我们细细道来:
1、首先需要明白的一点是,在建立连接时刻(后期也是),Initiating 端成为 Master Role,ADV 端成为 Slave Role。
2、在一个连接事件中,总是 Master 先发包,Slave 收到 Master 的包,予以回复(Slave 没收到,就不会回复)。
3、连接的维持条件是,相互都有包的交互,即便是不发送数据,也需要发送空包进行交互,以让双方知道,大家都还 Online。
OK,上述 3 点了解后,那么真正意义上的连接建立,是从发送(接收)到 CONNECT_IND 或者 AUX_CONNECT_REQ/AUX_CONNECT_RSP 后,的第一次成功的包交互。
那么万一比较糟糕的情况下,第一个包交互失败了呢?
Spec 定义,在建立连接的过程中(交互 CONNECT_IND 或者 AUX_CONNECT_REQ/AUX_CONNECT_RSP 完成开始算起),连续 6 个 Event 没有包的交互,算是建立连接失败。(类似于考科目三,还没上路,灯光没过,直接 GG)。
2、PHY 的选择
需要注意的一些细节是:对于经典的 CONNECT_IND 来说,总是在 1M PHY 上建立连接,而且建立连接后,在 1M PHY 上进行收发包(没有 Update PHY 的情况下)。
而 BLE 5.0 以后,建立连接是 EXT_ADV_IND 引出的 AUX_ADV_IND,这个 AUX_ADV_IND 可能在任何 PHY 上,所以这种情况建立连接后,默认的收发包,就直接使用这个 AUX_ADV_IND 使用的 PHY 即可。
3、跳频算法的选择
建立连接后,为了抗干扰,保证数据传输,链接双方(Master/Slave)会按照一个大家约定好的公式来计算每一次交互(Event)使用的信道,这个过程称之为跳频。跳频并不是按照顺序来跳,而是通过一个计算来求解。对于 BLE 5.0 之前,仅有一种跳频算法,BLE 5.0 之后引入了跳频算法 #2,所以之前的调频算法就被命名为跳频算法 #1。连接双方必须使用同一套跳频算法,才能计算出同样的结果。那么连接的双方是怎么确定,使用哪一套算法呢?还记得在分析 ADV 包的时候么:
BLE(5)—— 广播态数据包组成(Advertising Packets PDUs)
在 Header 域,有一个 1bit 的 ChSel 标志,如果这个标志位 0‘b 的话,使用算法 #1,否则,为 1’b 的话,使用算法 #2
4、Connection Events
Connection Event 我们简称 CE,代表着一个连接态的 Master/Slave 的交互集合。两个 CE 的起始时间之间的时间差,我们叫做 Connection Interval 也叫 CI。
所以,我们可以看到,在一个 Event 内的收发包,一定是 Master 先发起,Slave 回复。并且是交替进行。如果有数据,那么包体内就是 raw data,没有数据的话,双方也要在一个 Event 内,发送空包交互。
T_IFS = 150us
当双方建立起连接并维持连接的过程中,双方都会记录一个 Event Counter 的东西,顾名思义,这个值代表了当前这是第几个 Connection Event。他是一个 16bits 的数字,从 0x0000 - 0xFFFF.
5、Supervision Timeout
处于连接的两个 BLE,可能因为种种原因导致链路断开,比如:设备移动得太远了,突然断电等等。那么怎么样才算是认为断连呢?在建立连接的初期,交互的包里面(CONNECT_IND 或者 AUX_CONNECT_REQ),含有一个叫 supervison timeout 的字段,这个字段也叫 TO,代表了如果双方一直没有正确的交互包,超过这个时间,后,认为链路已经断开了。
这个好理解吧,比如这个时间为 5s,那么如果 Slave 断电了,但是 Master 并不知道,还在傻傻的按照每个 Interval 在发送包,但是此刻,Master 就不会接收到来自 Slave 的回应,Master 便怀恨在心,一直计数,直到 TO 到了,Master 便死心了,认为 Slave 已经 Offline,上报连接断开。
同样,Slave 也是,如果 Master 不在了,Slave 还是傻傻的每个 Interval 去按时去接收 Master 的包,但是发现总是等不到心爱的 Master,一直到 TO,则 Slave 认为 Master 已经不属于他了。
6、连接建立过程 —— Master Role
现在我们来看看在空口中,连接是如何建立起来的,这里分为两部分阐述,先看 Master。
Master 是由 initiator 转移来的,是发送了 CONNECT_IND 或者 AUX_CONNECT_REQ 并且收到 AUX_CONNECT_RSP 后转型为 Master。连接建立初期的过程,尤为重要,因为他决定了是否能够成功建立连接,这是第一步。每个 CE 的第一个交互点,我们称之为锚点(anchor point),寻找锚点,是个细致的活路,因为空口交互,均是以 us 为单位,马虎不得。
6.1、 Legacy
对于经典的来说,是由 CONNECT_IND 建立的,时序如下:
最开始是可连接的 Advertising packet,T_IFS(150us)后,接着 CONNECT_IND 包。此刻,我们假设双方都正常的收到/发送了这个 CONNECT_IND,那么连接开始 SetUp。
1、经过 transmitWindowDelay
2、在经过 transmitWindowOffset
3、开窗 transmitWindowSize 发送第一个 packet
transmitWindowDelay 的描述为:
The value of transmitWindowDelay shall be 1.25 ms when a CONNECT_IND PDU is used, 2.5 ms when an AUX_CONNECT_REQ PDU is used on an LE Uncoded PHY, and 3.75 ms when an AUX_CONNECT_REQ PDU is used on the LE Coded PHY.
也就是说这个值,是根据 PHY 而变化的固定值。
transmitWindowOffset 和 transmitWindowSize 是包含在 CONNECT_IND 内的,是由 initiator 给出的值。参考:
BLE(7)—— 发起态数据包组成( Initiating Packets PDUs)
所以,这些确定后,Slave 就在那个指定的位置去开窗收包即可。
6.2、 Extended
这种类型的如出一辙,与上一个不一样的是,PHY 和 Channel 不一样了,计算方式和套路一样,直接给图即可,这里需要注意的是,在哪个 PHY 上进行的 AUX_CONNECT_REQ/RSP 的交互,最终建立起连接,就在这个 PHY 上:
7、连接建立过程 —— Slave Role
对应于 Master Role,Slave 这边,则是从 Advertising 状态转移过去的。是接收了 CONNECT_IND 或者 AUX_CONNECT_REQ 并且发送 AUX_CONNECT_RSP 后转型为 Slave。对于 Slave 来说,最主要的是,想办法去接收到 Master发出来的第一个包。前面了解到,Master 发出的第一个包的地方 t 从CONNECT_IND(或者 AUX_CONNECT_REQ)结束后的:
[ transmitWindowDelay + transmitWindowOffset ]
≤ t ≤
[ transmitWindowDelay + transmitWindowOffset + transmitWindowSize ]
那么 Slave 就要在这个时间点去开窗收包(RF 开启 RX)。理论上来说,一定会收到来自 Master 的第一个包。但是这是理论上来说的,但是事与愿违,总有意外发生,万一空口质量不好,没有收到咋办?总要把这些因素考虑进来吧,毕竟建立连接,是 BLE 的头等大事,所以呢,为了更好建立连接, Core Spec 定义,允许在建立连接时候 Slave 错过 Master 的建立期间的包,Slave 错过了这个,那么就要到下一个点去收,所以时序如下:
7.1、Legacy
下一个接收点,在上一个 TransmitWindow 起始位置,往后面推算一个 connection interval 的位置开始开窗接收。
7.2、 Extended
对于 Extended 来说也是一样的:
这里需要注意的是,如果在第二个地方收,那么肯定的,也要跳频到对的那个频段来接收,其次,Event Counter 要相应的增加,这样才能保持和 Master 的 Event Counter 一致性。
那么万一在第二个地方还没接收到咋办,继续在第三个点收呗,以此类推。6 个 Interval 还没收到,GG,建立连接失败!
8、Closing connection events
好了,连接这些都建立上了,我们开始数据传送之旅。BLE 是周期性业务,所以每次的数据交互,只能在每个 Connection Event 中,那么一个 Connection Event 有多长呢?这个就涉及到一个概念,叫 MD(别想歪了,不是“妈的”,是 More Data)。
在数据包的构成中,讲到了连接态的数据包构成,其 Header 中有一个标志位,叫 MD,用1 bit来表示。它的物理含义是,代表了当前的这个包后面还有没有跟进其他的非空数据包。如何理解呢?比如,Master 想发 40 个 Bytes 的数据,(默认情况下,27 Bytes 为一个 Date Length),那么就要分为两包发送,第一次发送 27 Bytes,第二次发送 13 Bytes;那么在第一组包中的 Header 域中的 MD 字段,就要被设置成为 1,由于第二组发包后,发完了,那么第二组数据包的 Header 中的 MD 字段就被设置成为 0;
好了,我觉得 MD 的含义算是讲清楚了,那么这个 MD 是如何与 CE 关联上的呢?
打个比如:如果双方都没有要传输的数据,那么 MD 都为 0,则这次的 CE 就提前结束,CE 就显得比较小。
类似于上面的例子。如果 Master 有 40 Bytes 的数据传送,那么 Slave 也要陪着 Master 玩,那么这时的 CE 就会较大:
当然,只允许 Master 传数据,那是不合理的,Slave 也可以发送据呀,当 Slave 发送据 Master 没数据的时候,Slave 的 MD 就会被设置成为 1,Master 也只有陪玩:
当然,Master 和 Slave 可以同时发数据,MD 都被设置成为 1:
这里我们总算是看出来了,MD 会影响到 CE 的长度;同时可看出来了,CE 最长不能超过 CI 的长度。那么有没有办法指定 CE 呢?当然可以,HCI 命令中,可以指定 CE MIN Length 和 CE MAX Length,如果一个 CE 装不了那么多数据,那么数据顺延到下一个 CI 去发就好,MD 照样置上。
所以 Core Spec 根据上面画的 4 个图的场景,根据 MD 的值,绘制了一个表格,来代表 MD 对 CE 的影响:
9、跳频算法
9.1 跳频算法 #1
这是 BLE 5.0 之前通用的跳频算法,Master 和 Slave 每一个Connection Event,更换一次 Physical Channel。
上面这个是截取 Core Spec 跳频算法 #1 的计算方式:
1)首先,使用一个Basic的算法,利用 lastUnmappedChannel 和 hopIncrement,计算出 unmappedChannel。
a)lastUnmappedChannel在连接建立之初的值是0,每一次Connection Event计算出新的unmappedChannel之后,会更新lastUnmappedChannel。
b)hopIncrement是由Master在连接建立时随机指定的,范围是5到16(可参考3.3中的Hop)。
c)确定unmappedChannel的算法为:unmappedChannel = (lastUnmappedChannel + hopIncrement) mod 37,本质上就是每隔“hopIncrement”个Channel取一次,相当直白和简单。
2)计算出unmappedChannel之后,查找当前的Channel map,检查unmappedChannel所代表的Channel是否为used channel。如果是,恭喜,找到了。
Channel map也是由master,在连接建立时,或者后来的Channel map update的时候指定的。
3)如果不是,将所有的used Channel以升序的方式见一个表,表的长度是numUsedChannels,用unmappedChannel和numUsedChannels做模运算,得到一个index,从按照该index,从表中取出对应的channel即可。
9.2 跳频算法 #2
跳频算法 #2 用于 Connection 和 periodic Advertising。
跳频算法 #2 的总流程如下图所示:
可以看到,跳频算法 #2 的入参有两个:
counter:16 bits 的Event 计数器,对于 Connection,是 Connection Event Counter,对于 periodic Advertising,是 paEventCounter,也就是周期性广播的 Counter 计数
channelIdentifier:是一个16 bits channelIdentifier , 是一个固定的值, 在连接或者周期广播中给出, 计算方式主要是使用了 Access Address: channelIdentifier = (Access Address 31~16 ) XOR (Access Address 15~0 )
结果:
通过算法 #2,出来的是 channel index,也就是下一个跳频的频道。
上面的图分为两个 blocks,姑且我们叫 block1 和 block2 吧。
在介绍 block1 和 block2 的计算之前,先介绍两个运算,因为它们会在后面的运算中使用到:
1、PERM 运算:将16bits的数据, 分为高8bits反转, 低8bits反转:
2、MAM 运算:乘法, 加法, 取模模块(Multiply, Add, and Modulo : MAM),将16bits的数据 a 先乘以 17, 在加上输入的 16bits 的b, 在和 2 的16次方取模, 输出 16bit数据
9.2.1 Unmapped Event Channel Selection
Block1,是这个 Unmapped Event Channel Selection 过程,它包含两部分:
1、生成 Block 2 的需要的输入参数 prn_e
2、生成 Block 2 的需要的输入参数 unmappedChannel
这里用到了上面讲到的 PERM 和 MAM 计算方式。通过层层计算,得出中间参数 prn_e 以及 unmappedChannel。
9.2.2 Event Mapping to Used Channel Index
由 Unmapped Event Channel Selection 生成 prn_e 和 unmappedChannel 后, 这两个数作为 Block 2 的输入;
如果 unmappedChannel 在 channel map 中是 used channel , 则这个值将会作为最终的输出值;
反之, 如果是 unused channel , 则会进行下述的计算来得到将输出的 channel index:
首先计算出一个 remappingindex 的值:
prn_e :上一级的输入参数
N :used channels 的个数
然后在这 N 个 used channels 中进行升序的排列, 以计算出的 remappingindex 作为 index , 在表中取出channel 即可。
10、Acknowledgment and flow control
Connection 状态下数据传递的应答和流控依赖于两个参数:
transmitSeqNum
nextExpectedSeqNum
简称是 SN 和 NESN,这两个也是存在于 Connection State 的包体的 Header 中。
SN 和 NESN 都是由 1 bit 构成,他们两个共同组成了 Connection State 的应答和流控机制(重传)。
SN 的物理含义是当前发送的包的序号
NESN 的物理含义是下一个期待接收的数据包序号
SN 和 NESN 在 Connection 建立时都初始化为 0
1)无论是 Master 还是 Slave,发送 packet 的时候,都会将当前的 sn 和 nesn copy 到 packet 的 SN 和 NESN bit中。
2)无论是 Master 还是 Slave,当接收到一个 packet 的时候,会将该 packet 的 NESN bit 和本地的 sn 比较:如果相同,说明该packet 是对端设备发来的 NAK packet(请求重发),则需要将旧的 packet 重新发送出去;如果不同,说明是对端设备发来的ACK packet(数据被正确接收),则需要将本地的sn加1,接着发送新的 packet 。
3)无论是 Master 还是 Slave,当接收到一个packet的时候,会将该 packet 的 SN bit 和本地的 nesn 比较:如果相同,则说明是一个新的 packet,接收即可,同时将本地的 nesn 加1;如果不同,则说明是一个旧的 packet,什么都不需要处理。
4)当一个设备无法接收新的packet的时候(例如RX buffer已满),它可以采取不增加nesn的方式,发送NAK packet。对端设备收到该类型的packet之后,会发送旧的packet。该设备收到这样旧的packet的时候,不会做任何处理。这就是Link Layer的流控机制(Flow control)