IoTBLE 协议栈和数据报文解析
Posted 简一商业
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IoTBLE 协议栈和数据报文解析相关的知识,希望对你有一定的参考价值。
1、协议栈:
BLE 协议栈就是实现低功耗蓝牙协议的代码,理解和掌握 BLE 协议是实现 BLE 协议栈的前提。
要实现一个 BLE 应用,首先需要一个支持 BLE 射频的芯片,然后还需要提供一个与此芯片配套的 BLE 协议栈,最后在协议栈上开发自己的应用。
BLE 协议栈是连接芯片和应用的桥梁,是实现整个 BLE 应用的关键。
那 BLE 协议栈具体包含哪些功能呢?
简单来说,BLE 协议栈主要用来对你的应用数据进行层层封包,以生成一个满足 BLE 协议的空中数据包。
BLE 协议栈主要由如下几部分组成:
1.1、PHY 层(Physical layer 物理层)
1)PHY 层用来指定 BLE 所用的无线频段,调制解调方式和方法等。
2)PHY 层做得好不好,直接决定整个 BLE 芯片的功耗、灵敏度以及 selectivity 等射频指标。
1.2、LL层(Link Layer 链路层)
LL 层是整个 BLE 协议栈的核心,也是 BLE 协议栈的难点和重点。
如 Nordic 的 BLE 协议栈能同时支持 20 个 link(连接),就是 LL 层的功劳。
LL 层要做的事情非常多,比如:
1)选择射频通道进行通信;
2)识别空中数据包;
3)具体在哪个时间点把数据包发送出去;
4)保证数据的完整性;
5)接收 ACK;
6)如何进行数据重传;
7)如何对链路进行管理和控制等。
LL 层只负责数据的收发,对数据进行怎样的解析则交给 GAP 或者 ATT 层。
1.3、HCI(Host controller interface)
HCI 是可选的,HCI 主要用于 2 颗芯片实现 BLE 协议栈的场合,用来规范两者之间的通信协议和通信命令等。
1.4、GAP 层(Generic access profile 通用接入规范)
GAP 是对 LL 层 payload(有效数据包)进行解析的两种方式中的一种,而且是最简单的那一种。
GAP 简单的对 LL payload 进行一些规范和定义,因此 GAP 能实现的功能极其有限。
GAP 目前主要用来进行广播,扫描和发起连接等操作。
1.5、L2CAP层(Logic link control and adaptation protocol)
L2CAP 对 LL 进行了一次简单封装,LL 只关心传输的数据本身,L2CAP 就要区分是加密通道还是普通通道,同时还要对连接间隔进行管理。
1.6、SMP(Secure manager protocol)
SMP 用来管理 BLE 连接的加密和安全的,如何保证连接的安全性,同时不影响用户的体验,这些都是 SMP 要考虑的工作。
1.7、ATT(Attribute protocol 属性协议)
简单来说,ATT 层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。
BLE 协议栈中,开发者接触最多的就是 ATT。
BLE 引入了 attribute 概念,用来描述一条一条的数据。
Attribute 除了定义数据,同时定义该数据可以使用的 ATT 命令,因此这一层被称为 ATT 层。
1.8、GATT(Generic attribute profile 通用属性规范)
GATT 用来规范 attribute 中的数据内容,并运用 Group(分组)的概念对 attribute 进行分类管理。
如果没有 GATT,BLE 协议栈也能跑,但设备互联互通就会出问题,也正是因为有了 GATT 和各种各样的应用 profile,BLE 摆脱了 ZigBee 等无线协议的兼容性困境,成了出货量最大的 2.4G 无线通信产品。
GATT 按照层级定义了 4 个概念:
配置文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。
他们的关系是这样的:
Profile 定义了一个实际的应用场景,一个 Profile 包含若干个 Service,一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。
Profile:
Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。
例如心率 Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。
所有官方通过 GATT Profile 的列表可以找到(https://www.bluetooth.com/specifications#GATT)。
Service:
Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。
每个 Service 有一个 UUID 唯一标识。
UUID 有 16 bit 的,或者 128 bit 的。
16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。
官方通过了一些标准 Service,以 Heart Rate Service 为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。
Characteristic:
需要重点提一下 Characteristic,它定义了数值和操作,包含一个Characteristic声明、Characteristic 属性、值、值的描述(Optional)。
通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,对某一个Characteristic 进行读,就是获取这个 Characteristic 的 value。
UUID:
Service、Characteristic 和 Descriptor 都是使用 UUID 唯一标示的。
UUID 是全局唯一标识,它是 128bit 的值,为了便于识别和阅读,一般以 “8位-4位-4位-4位-12位”的16进制标示,比如“12345678-abcd-1000-8000-123456000000”。
但是,128bit的UUID 太长,考虑到在低功耗蓝牙中,数据长度非常受限的情况,蓝牙又使用了所谓的 16 bit 或者 32 bit 的 UUID,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。
除了 “XXXX” 那几位以外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅减少了,例如 16 bit UUID只有有限的 65536(16的四次方) 个。
与此同时,因为数量有限,所以 16 bit UUID 并不能随便使用。蓝牙技术联盟已经预先定义了一些 UUID,我们可以直接使用,比如 “00001011-0000-1000-8000-00805F9B34FB” 就一个是常见于 BLE 设备中的 UUID,当然也可以花钱定制自定义的 UUID。
1.9、示例
下面以如何发送一个数据包为例来讲解 BLE 协议栈各层是如何紧密配合,以完成数据收发任务的。
如何通过无线发送一个数据包:
假设有 BLE 设备 A 和设备 B,设备 A 要把自己目前的电量状态 83%(十六进制表示为 0x53)发给设备 B,该怎么做呢?
作为一个开发者,希望越简单越好,比如调用一个简答的 API send(0x53),实际上我们的 BLE 协议栈就是这样设计的,开发者只需调用 send(0x53) 就可以把数据发送出去,其余的事情 BLE 协议栈帮你搞定。
很多人会想,BLE 协议栈是不是直接在物理层就把 0x53 发出去,如下图所示:
这种方式看似简单,但由于很多细节没有考虑到,实际是不可行的。
首先,它没有考虑用哪一个射频信道来进行传输,在不更改 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 两者之间的连接。
接入地址有两种类型:
广播接入地址和数据接入地址。
广播接入地址:固定为 0x8E89BED6,在广播、扫描、发起连接时使用。
数据接入地址:随机值,不同的连接有不同的值,在连接建立之后的两个设备间使用。
对于数据信道,数据接入地址是一个随机值,但需要满足下面几点要求:
1) 数据接入地址不能超过 6 个连续的 “0” 或 “1”;
2) 数据接入地址的值不能与广播接入地址相同;
3) 数据接入地址的4个字节的值必须互补相同;
4) 数据接入地址不能有超 24 次的比特翻转(比特 0 到 1 或 1 到 0,称为 1 次比特翻转);
5) 数据接入地址的最后 6 个比特需要至少两次的比特翻转;
6) 符合上面条件的有效随机数据接入地址大概有 231 个。
1.7.1、广播方式
我们先来看一下简单的广播情况,这种情况下,我们把设备 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。
前导帧是一个 8 BIT 的交替序列:01010101 或 10101010,取决于接入地址的第一个比特位。
若接入地址的第一个比特为 0,则前导为 01010101,即 0x55;
若接入地址的第一个比特为 1,则前导为 10101010,即 0xAA。
同时,接收端可以根据前导的无线信号强度来配置自动增益控制。
这样,整个空中包就变成(注:空中包用小端模式表示!):
上面这个数据包还有如下问题:
没有对数据包进行分类组织,设备 B 无法找到自己想要的数据 0x53。
为此我们需要在 access address 之后加入两个字段:
LL header 和 lenght 长度字节。
LL header 用来表示数据包的 LL 类型,长度字节用来指明 payload 的长度
设备 B 什么时候开启射频窗口以接收空中数据包?
如上图 case1 所示,当设备 A 的数据包在空中传输的时候,设备 B 把接收窗口关闭,此时通信将失败;
同样对 case2 来说,当设备 A 没有在空中发送数据包时,设备 B 把接收窗口打开,此时通信也将失败;
只有 case3 的情况,通信才能成功,即设备 A 的数据包在空中传输时,设备 B 正好打开射频接收窗口,此时通信才能成功。
换句话说,LL 层还必须定义通信时序:
当设备 B 拿到数据 0x53 后,该如何解析这个数据呢?
它到底表示湿度还是电量,还是别的意思?
这个就是 GAP 层要做的工作,GAP 层引入了 LTV(Length-Type-Value)结构来定义数据,比如 020105,02-长度,01-类型(强制字段,表示广播 flag,广播包必须包含该字段),05-值。
由于广播包最大只能为 31 个字节,它能定义的数据类型极其有限,如这里说的电量,GAP 就没有定义。
因此要通过广播方式把电量数据发出去,只能使用供应商自定义数据类型 0xFF,即 04 FF 5900 53,其中 04 表示长度,FF 表示数据类型(自定义数据),0x0059 是供应商 ID(自定义数据中的强制字段),0x53 就是我们的数据(设备双方约定 0x53 表示电量)。
最终传输的数据包将变成:
AAD6BE898E600E3B75AB2A02E102010504FF5900538EC7B2
AA – 前导帧(preamble)
D6BE898E – 访问地址(access address)
60 – LL 帧头字段(LL header)
0E – 有效数据包长度(payload length)
3B75AB2A02E1 – 广播者设备地址(advertiser address)
020105 04FF590053 – 广播数据(LTV 格式)
8EC7B2 – CRC24值
有了 PHY,LL 和 GAP,就可以发送广播包了,但广播包携带的信息极其有限,而且还有如下几大限制:
1)无法进行一对一通信 (广播是一对多通信,而且是单方向的通信);
2)不支持组包和拆包,无法传输大数据,通信不可靠;
2)广播信道不能太多,否则将导致扫描端效率低下。
为此,BLE 只使用 37(2402MHz) / 38(2426MHz) / 39(2480MHz) 三个信道进行广播和扫描,因此广播不支持跳频。
由于广播是一对多的,所以广播也无法支持 ACK,这些都使广播通信变得不可靠,扫描端功耗高。
由于扫描端不知道设备端何时广播,也不知道设备端选用哪个频道进行广播,扫描端只能拉长扫描窗口时间,并同时对 37/38/39 三个通道进行扫描,这样功耗就会比较高。
而连接则可以很好解决上述问题,下面我们就来看看连接是如何将 0x53 发送出去的。
1.7.2、连接方式
什么叫连接(connection)?
如有线 UART,很容易理解,就是用线(Rx 和 Tx 等)把设备 A 和设备 B 相连,即为连接。
用“线”把两个设备相连,实际是让 2 个设备有共同的通信媒介,并让两者时钟同步起来。
蓝牙连接有何尝不是这个道理,所谓设备 A 和设备 B 建立蓝牙连接,就是指设备 A 和设备 B 两者“同步”成功,其具体包含以下几方面:
1)设备 A 和设备 B 对接下来要使用的物理信道达成一致
2)设备 A 和设备 B 双方建立一个共同的时间锚点,也就是说,把双方的时间原点变成同一个点
3)设备 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) :
1)GATT 层定义数据的类型和分组,方便起见,我们用 0x0013 表示电量这种数据类型,这样 GATT 层把数据打包成 1300 53(小端模式!)
2)ATT 层用来选择具体的通信命令,如:读/写/notify/indicate 等,这里选择 notify 命令 0x1B,这样数据包变成了:1B 1300 53
3)L2CAP 用来指定 connection interval(连接间隔),比如每 10ms 同步一次(CI不体现在数据包中),同时指定逻辑通道编号 0004(表示 ATT 命令),最后把 ATT 数据长度 0x0004 加在包头,这样数据就变为:0400 0400 1B 1300 53
4)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)
0400 0400 – ATT 数据长度,以及 L2CAP 通道编号
1B – notify command
0x0013 – 电量数据handle
0x53 – 真正要发送的电量数据
0xF650D5 – CRC24值
虽然只是简单调用了 API send(0x53),但由于低功耗蓝牙 BLE 协议栈层层打包,最后空中实际传输的数据将变成下图所示的模样,这就既满足了低功耗蓝牙通信的需求,又让用户 API 变得简单。
2、BLE 报文结构
2.1、报文格式
2.2、广播报文报头
报头的内容取决于该报文是广播报文还是数据报文,广播报文的报头如下图所示:
广播报文的报头包含 4bit 广播报文类型、2bit 保留位、1bit 发送地址类型和 1bit 接收地址类型。
1) 广播报文类型
广播报文类型,共有 7 种类型,如下图所示。
每种广播报文类型都具有不同的数据格式及行为。
2) 发送地址类型和接收地址类型
发送地址类型和接收地址类型指示了设备使用公共地址(Public Address)还是随机地址(Random Address)。
公共地址和随机地址的长度一样,都包含 6 个字节共 48 位。
BLE 设备至少要拥有这两种地址类型中的一种,当然也可以同时拥有这两种地址类型。
公共地址(Public Address) 公共地址由两部分组成,如下图。
公共地址由制造商从 IEEE 申请,由 IEEE 注册机构为该制造商分配的机构唯一标识符 OUI(Organizationally Unique Identifier)。
这个地址是唯一的,不能修改。
随机地址有包含两种:
静态地址(Static Device Address)和私有地址(PrivateDevice Address)。
静态地址有如下要求:
a) 静态地址的最高 2 位有效位必须是 1;
b) 静态地址最高 2 位有效位之外的其余部分不能全为 0;
c) 静态地址最高 2 位有效位之外的其余部分不能全为 1。
在私有地址的定义当中,又包含了两个子类:
不可解析私有地址(Non-resolvable Private Address)和可解析私有地址(Resolvable Private Address,RPA)。
2.3、长度
广播报文:长度域包含 6 个比特,有效值的范围是 6~37。
数据报文:长度域包含 5 个比特,有效值的范围是 0~31。
广播报文和和数据报文的长度域有所不同,主要原因是:
广播报文除了最多 31 个字节的数据之外,还必须要包含 6 个字节的广播设备地址。6+31=37,所以需要 6 比特的长度域。
2.4、数据(AdvData)
广播和扫描相应的数据格式如下图所示,由有效数据部分和无效数据部分组成。
1) 有效数据部分:包含 N 个 AD Structure,每个 AD Structure 由 Length,Type 和 Data 组成(LTV 格式)。
其中:
Length:Type 和 Data 的长度。
Type:指示 Data 数据的含义。
查看 Nordic 的 SDK 中的定义,type 的定义在程序的 “ble_gap.h” 头文件中,定义如下:
#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */
#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */
#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */
#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */
#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */
#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */
#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */
#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */
#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */
#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */
#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */
#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */
#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */
refer:
http://www.wowotech.net/bluetooth/ble_broadcast.html
https://www.jianshu.com/p/795bb0a08beb
https://yuedu.baidu.com/ebook/f95a00f9c0c708a1284ac850ad02de80d4d8063b
http://www.cnblogs.com/aikm/p/5022502.html
https://blog.csdn.net/shunfa888/article/details/80140475
http://www.cnblogs.com/iini/p/8834970.html
https://blog.csdn.net/wulazula/article/details/80332777
以上是关于IoTBLE 协议栈和数据报文解析的主要内容,如果未能解决你的问题,请参考以下文章
modbus_RTU协议报文解析我发:01 03 9F 2E 00 04 0A 14收到:01 03 08 00 00 00 01 00 00 00 00 A8 17。