深⼊浅出低功耗蓝⽛(BLE)协议栈
BLE协议栈为什么要分层?怎么理解BLE“连接”?如果BLE协议只有ATT层没有GATT层会发⽣什么?
协议栈框架
⼀般⽽⾔,我们把某个协议的实现代码称为协议栈(protocol stack),BLE协议栈就是实现低功耗蓝⽛协议的代码,理解和掌握BLE协议是实现BLE协议栈的前提。在深⼊BLE协议栈各个组成部分之前,我们先看⼀下BLE协议栈整体架构。
如上图所述,要实现⼀个BLE应⽤,⾸先需要⼀个⽀持BLE射频的芯⽚,然后还需要提供⼀个与此芯⽚配套的BLE协议栈,最后在协议栈上开发⾃⼰的应⽤。可以看出BLE协议栈是连接芯⽚和应⽤的桥梁,是实现整个BLE应⽤的关键。那BLE协议栈具体包含哪些功能呢?简单来说,BLE协议栈主要⽤来对你的应⽤数据进⾏层层封包,以⽣成⼀个满⾜BLE协议的空中数据包,也就是说,把应⽤数据包裹在⼀系列的帧头(header)和帧尾(tail)中。具体来说,BLE协议栈主要由如下⼏部分组成:
PHY层(Physical layer物理层)。PHY层⽤来指定BLE所⽤的⽆线频段,调制解调⽅式和⽅法等。PHY层做得好不好,直接决定整个BLE芯⽚的功耗,灵敏度以及selectivity等射频指标。
LL层(Link Layer链路层)。LL层是整个BLE协议栈的核⼼,也是BLE协议栈的难点和重点。像Nordic的BLE协议栈能同时⽀持20个link(连接),就是LL层的功劳。LL层要做的事情⾮常多,⽐如具体选择哪个射频通道进⾏通信,怎么识别空中数据包,具体在哪个时间点把数据包发送出去,怎么保证数据的完整性,ACK如何接收,如何进⾏重传,以及如何对链路进⾏管理和控制等等。LL层只负责把数据发出去或者收回来,对数据进⾏怎样的解析则交给上⾯的GAP或者GATT。
HCI(Host controller interface)。HCI是可选的(),HCI主要⽤于2颗芯⽚实现BLE协议栈的场合,⽤来规范两者之间的通信协议和通信命令等。
GAP层(Generic access profile)。GAP是对LL层payload(有效数据包)如何进⾏解析的两种⽅式中
的⼀种,⽽且是最简单的那⼀种。GAP简单的对LL payload进⾏⼀些规范和定义,因此GAP能实现的功能极其有限。GAP⽬前主要⽤来进⾏⼴播,扫描和发起连接等。
L2CAP层(Logic link control and adaptation protocol)。L2CAP对LL进⾏了⼀次简单封装,LL只关⼼传输的数据本⾝,L2CAP就要区分是加密通道还是普通通道,同时还要对连接间隔进⾏管理。
SMP(Secure manager protocol)。SMP⽤来管理BLE连接的加密和安全的,如何保证连接的安全性,同时不影响⽤户的体验,这些都是SMP要考虑的⼯作。
ATT(Attribute protocol)。简单来说,ATT层⽤来定义⽤户命令及命令操作的数据,⽐如读取某个数据或者写某个数据。BLE协议栈中,开发者接触最多的就是ATT。BLE引⼊了attribute概念,⽤来描述⼀条⼀条的数据。Attribute除了定义数据,同时定义该数据可以使⽤的ATT命令,因此这⼀层被称为ATT层。
GATT(Generic attribute profile )。GATT⽤来规范attribute中的数据内容,并运⽤group(分组)的概念对attribute进⾏分类管理。没有GATT,BLE协议栈也能跑,但互联互通就会出问题,也正是因为有了GATT和各种各样的应⽤profile,BLE摆脱了ZigBee等⽆线协议的兼容性困境,成了出货量最⼤的2.4G⽆线通信产品。
我相信很多⼈看了上⾯的介绍,还是不懂BLE协议栈的⼯作原理,以及每⼀层具体⼲什么的,为什么要
这么分层。下⾯我以如何发送⼀个数据包为例来讲解BLE协议栈各层是如何紧密配合,以完成发送任务的。
如何通过⽆线发送⼀个数据包
假设有设备A和设备B,设备A要把⾃⼰⽬前的电量状态83%(⼗六进制表⽰为0x53)发给设备B,该怎么做呢?作为⼀个开发者,他希望越简单越好,对他⽽⾔,他希望调⽤⼀个简单的API就能完成这件事,⽐如send(0x53),实际上我们的BLE协议栈就是这样设计的,开发者只需调⽤send(0x53)就可以把数据发送出去了,其余的事情BLE协议栈帮你搞定。很多⼈会想,BLE协议栈是不是直接在物理层就把0x53发出去,就如下图所⽰:
profile中文意思
这种⽅式初看起来挺美的,但由于很多细节没有考虑到,实际是不可⾏的。⾸先,它没有考虑⽤哪⼀个射频信道来进⾏传输,在不更改API 的情况下,我们只能对协议栈进⾏分层,为此引⼊LL层,开发者还是调⽤send(0x53),send(0x53)再调⽤send_LL(0x53,2402M)(注:2402M为信道频率)。这⾥还有⼀
个问题,设备B怎么知道这个数据包是发给⾃⼰的还是其他⼈的,为此BLE引⼊access address概念,⽤来指明接收者⾝份,其中,0x8E89BED6这个access address⽐较特殊,它表⽰要发给周边所有设备,即⼴播。如果你要⼀对⼀的进⾏通信(BLE协议将其称为连接),即设备A的数据包只能设备B接收,同样设备B的数据包只能设备A接收,那么就必须⽣成⼀个独特的随机access address以标识设备A和设备B两者之间的连接。
⼴播⽅式
我们先来看⼀下简单的⼴播情况,这种情况下,我们把设备A叫advertiser(⼴播者),设备B叫scanner或者observer(扫描者)。⼴播状态下设备A的LL层API将变成send_LL(0x53,2402M, 0x8E89BED6)。由于设备B可以同时接收到很多设备的⼴播,因此数据包还必须包含设备A的device address(0xE1022AAB753B)以确认该⼴播包来⾃设备A,为此send_LL参数需要变成(0x53,2402M, 0x8E89BED6,
0xE1022AAB753B)。LL层还要检查数据的完整性,即数据在传输过程中有没有发⽣窜改,为此引⼊CRC24对数据包进⾏检验 (假设为
0xB2C78E) 。同时为了调制解调电路⼯作更⾼效,每⼀个数据包的最前⾯会加上1个字节的preamble(前导帧),preamble⼀般为0x55或者0xAA。这样,整个空中包就变成(注:空中包⽤⼩端模式表⽰!
):
上⾯这个数据包还有如下问题:
1. 没有对数据包进⾏分类组织,设备B⽆法到⾃⼰想要的数据0x53。为此我们需要在access address之后加⼊两个字段:LL header和
长度字节。LL header⽤来表⽰数据包的LL类型,长度字节⽤来指明payload的长度
2. 设备B什么时候开启射频窗⼝以接收空中数据包?如上图case1所⽰,当设备A的数据包在空中传输的时候,设备B把接收窗⼝关闭,此
时通信将失败;同样对case2来说,当设备A没有在空中发送数据包时,设备B把接收窗⼝打开,此时通信也将失败。只有case3的情况,通信才能成功,即设备A的数据包在空中传输时,设备B正好打开射频接收窗⼝,此时通信才能成功,换句话说,LL层还必须定义通信时序。
3. 当设备B拿到数据0x53后,该如何解析这个数据呢?它到底表⽰湿度还是电量,还是别的意思?这个就是GAP层要做的⼯作,GAP层
引⼊了LTV(Length-Type-Value)结构来定义数据,⽐如020105,02-长度,01-类型(强制字段,表⽰⼴播flag,⼴播包必须包含该字段),05-值。由于⼴播包最⼤只能为31个字节,它能定义的数据类型极其有限,像这⾥说的电量,GAP就没有定义,因此要通过⼴播⽅式把电量数据发出去,只能使⽤供应商⾃定义数据类型0xFF,即04FF590053,其中04表⽰长度,FF表⽰数据类型(⾃定义数据),0x0059是供应商ID(⾃定义数据中的强制字段),0x53就是我们的数据(设备双⽅约定0x53就是表⽰电量,⽽不是其他意思)。
最终空中传输的数据包将变成:
AAD6BE898E600E3B75AB2A02E102010504FF5900538EC7B2
AA – 前导帧(preamble)
D6BE898E – 访问地址(access address)
60 – LL帧头字段(LL header)
0E – 有效数据包长度(payload length)
3B75AB2A02E1 – ⼴播者设备地址(advertiser address)
02010504FF590053 – ⼴播数据
8EC7B2 – CRC24值
有了PHY,LL和GAP,就可以发送⼴播包了,但⼴播包携带的信息极其有限,⽽且还有如下⼏⼤限制:
1. ⽆法进⾏⼀对⼀双向通信(⼴播是⼀对多通信,⽽且是单⽅向的通信)
2. 由于不⽀持组包和拆包,因此⽆法传输⼤数据
3. 通信不可靠及效率低下。⼴播信道不能太多,否则将导致扫描端效率低下。为此,BLE只使⽤37(2402MHz) /38(2426MHz)
/39(2480MHz)三个信道进⾏⼴播和扫描,因此⼴播不⽀持跳频。由于⼴播是⼀对多的,所以⼴播也⽆法⽀持ACK。这些都使⼴播通信变得不可靠。
4. 扫描端功耗⾼。由于扫描端不知道设备端何时⼴播,也不知道设备端选⽤哪个频道进⾏⼴播,扫描端
只能拉长扫描窗⼝时间,并同时
对37/38/39三个通道进⾏扫描,这样功耗就会⽐较⾼。
⽽连接则可以很好解决上述问题,下⾯我们就来看看连接是如何将0x53发送出去的。
连接⽅式
到底什么叫连接(connection)?像有线UART,很容易理解,就是⽤线(Rx和Tx等)把设备A和设备B相连,即为连接。⽤“线”把两个设备相连,实际是让2个设备有共同的通信媒介,并让两者时钟同步起来。蓝⽛连接有何尝不是这个道理,所谓设备A和设备B建⽴蓝⽛连接,就是指设备A和设备B两者⼀对⼀“同步”成功,其具体包含以下⼏⽅⾯:
设备A和设备B对接下来要使⽤的物理信道达成⼀致
设备A和设备B双⽅建⽴⼀个共同的时间锚点,也就是说,把双⽅的时间原点变成同⼀个点
设备A和设备B两者时钟同步成功,即双⽅都知道对⽅什么时候发送数据包什么时候接收数据包
连接成功后,设备A和设备B通信流程如下所⽰:
如上图所⽰,⼀旦设备A和设备B连接成功(此种情况下,我们把设备A称为Master或者Central,把设备B称为Slave或者Peripheral),设备A将周期性以CI(connection interval)为间隔向设备B发送数据包,⽽设备B也周期性地以CI为间隔打开射频接收窗⼝以接收设备A的数据包。同时按照蓝⽛spec要求,设备B收到设备A数据包150us后,设备B切换到发送状态,把⾃⼰的数据发给设备A;设备A则切换到接收状态,接收设备B发过来的数据。由此可见,连接状态下,设备A和设备B的射频发送和接收窗⼝都是周期性地有计划地开和关,⽽且开的时间⾮常短,从⽽⼤⼤降低系统功耗并⼤⼤提⾼系统效率。
现在我们看看连接状态下是如何把数据0x53发送出去的,从中⼤家可以体会到蓝⽛协议栈分层的妙处。
对开发者来说,很简单,他只需要调⽤send(0x53)
GATT层定义数据的类型和分组,⽅便起见,我们⽤0x0013表⽰电量这种数据类型,这样GATT层把数据打包成130053(⼩端模式!)
ATT层⽤来选择具体的通信命令,⽐如读/写/notify/indicate等,这⾥选择notify命令0x1B,这样数据包变成了:1B130053
L2CAP⽤来指定connection interval(连接间隔),⽐如每10ms同步⼀次(CI不体现在数据包中),同时指定逻辑通道编号0004(表⽰ATT命令),最后把ATT数据长度0x0004加在包头,这样数据就变为:040004001B130053
LL层要做的⼯作很多,⾸先LL层需要指定⽤哪个物理信道进⾏传输(物理信道不体现在数据包中),然后再给此连接分配⼀个Access address(0x50655DAB)以标识此连接只为设备A和设备B直连服务,然后加上LL header和payload length字段,LL header标识此packet为数据packet,⽽不是control packet等,payload length为整个L2CAP字段的长度,最后加上CRC24字段,以保证整个packet 的数据完整性,所以数据包最后变成:
AAAB5D65501E08040004001B130053D550F6
AA – 前导帧(preamble)
0x50655DAB – 访问地址(access address)
1E – LL帧头字段(LL header)
08 – 有效数据包长度(payload length)
04000400 – ATT数据长度,以及L2CAP通道编号
1B – notify command
0x0013 – 电量数据handle
0x53 – 真正要发送的电量数据
0xF650D5 – CRC24值
虽然开发者只调⽤了 send(0x53),但由于低功耗蓝⽛协议栈层层打包,最后空中实际传输的数据将变成下图所⽰的模样,
这就既满⾜了低功耗蓝⽛通信的需求,⼜让⽤户API变得简单,可谓⼀箭双雕!
上⾯只是对BLE协议栈实现原理做了⼀个简单概述,即便如此,由于都是关于BLE协议栈底层的东西,很多开发者还是会觉得⽐较枯燥和晦涩,⽽且对很多开发者来说,他们也不关⼼BLE协议栈是如何实现
的,他们更关⼼的是BLE协议栈的使⽤,即怎么开发⼀个BLE应⽤。BLE 应⽤是实打实的东西,不能像上⾯讲述协议栈⼀样泛泛⽽谈,必须结合具体的蓝⽛芯⽚和蓝⽛协议栈来讲解,为此后⾯将以Nordic芯⽚及协议栈作为范例,来具体讲解如何开发BLE应⽤,以及如何通过代码去理解BLE协议中定义的⼀些概念和术语。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。