OTA项目回顾(三)-BLE
BLE技术揭秘BLE低功耗蓝牙是蓝牙 4.0 引入的轻量级无线协议专为低功耗 IoT 场景设计。它和经典蓝牙共用 2.4GHz 频段但协议栈完全不同核心分为两层GAP 负责广播和连接建立设备怎么被发现GATT 负责数据传输连上后怎么读写。数据以 Service → Characteristic → Descriptor 的树形结构组织手机作为 Client 发读写请求设备作为 Server 响应或主动 Notify 推送。默认每包只能传 20 字节可用数据通过 MTU 协商可以扩展到更大。参考谷雨BLE1.蓝牙简介蓝牙跑在 2.4GHz 免费频段是一种短距离无线通信技术现在手机、IoT 设备、医疗健康、智能家居到处都是。蓝牙 4.0 以后分两种模式单模BLE只支持低功耗蓝牙功耗极低、连接快、传输距离远但不支持音频数据速率也有限适合小数据量的场景。双模BR/EDR BLE同时兼容经典蓝牙和低功耗蓝牙。经典蓝牙适合音频和大数据量传输比如 A2DP 听歌、SPP 串口透传BLE 适合低功耗数据交互。经典蓝牙在音频和传统数传领域曾经是主力但苹果对经典蓝牙的 SPP 等数据接口有 MFI 认证要求加上功耗偏高在移动互联网时代逐渐被边缘化。BLE 从 iPhone 4S 和 Android 4.3 开始原生支持没有认证门槛功耗又极低所以现在移动互联设备大量采用。不过它不能传音频速率也受限。双模则是两头通吃——既能传音频、也能低功耗传数据兼容性最好功耗不太敏感的场景下是比较理想的选择。2.BLE特点BLE 瞄准的市场主要是移动智能终端、智能家居和 IoT 互联设备几个核心特点低功耗一颗纽扣电池跑几个月甚至几年因为 BLE 不通信时深度休眠只在需要时快速唤醒、传完即睡。快速连接毫秒级完成连接建立经典蓝牙某些场景下甚至要几十秒甚至数分钟。远距离BLE 5.0 后可达数百米而经典蓝牙通常就 10 米左右。除了物理层的优势BLE 另一个关键能力在协议层——标准 Profile 和自定义 Profile。蓝牙联盟为 BLE 定义了一系列标准 Profile可以理解为数据规范只要大家都遵守同一个 Profile不同厂商的设备就能互通。比如无线键鼠用的 HID不管是连 Windows、Mac 还是 iOS都是即插即用——这就是标准化的力量。常见的标准 Profile 还有 Battery Service电池电量、HRP心率采集等。而自定义 Profile 才是 BLE 真正被广泛应用的关键。手机普及之后开发者可以为自己的设备定义任意格式的数据交互规范——比如用一个字节 01 表示开灯、00 表示关灯手机 APP 写过去就实现了智能灯控。能收发任意格式数据、能和任意自定义设备连通这让 BLE 的应用场景从键鼠心率带一路扩展到智能插座、车载诊断、工业传感器等几乎所有小数据场景。3.BLE工作流程3.1角色BLE设备主要分为两种角色主机Master和从机Peripheral只有主机和从机建立链接才可以发送数据主机主机可以发起对从机的扫描连接。例如手机通常作为BLE的主机设备从机从机只能广播并等待主机的连接。例如智能手环是作为BLE的从机设备蓝牙协议栈没有限制设备的角色范围同一个BLE设备可以作为主机也可以作为从机我们称之为主从一体主从一体的好处是每个BLE设备都是对等的可以发起连接也可以被别人连接更加实用。3.2 广播在 BLE 中从机设备如果希望被主机发现通常需要先进入广播状态。设备进入广播状态后会按照设定的时间间隔周期性地发送广播数据包。这个周期性的发送过程称为广播事件Advertising Event两个广播事件之间的基础时间间隔称为广播间隔Advertising Interval。这里描述的是传统广播Legacy Advertising流程也是 ESP32 BLE 开发中最常见的广播方式之一。在每个广播事件中广播包会分别在37,38和39三个信道上依次广播如下图所示。广播时间间隔的范围是从20ms到10.24s广播间隔影响建立连接的时间。广播间隔越大连接的时间越长。另外BLE链路层会在两个广播事件之间添加一个010ms的随机延时保证多个设备广播时不会一直碰撞广播。也就是说设置100ms的广播间隔实际上两次广播事件的时间间隔可能是100110ms之间的任意时间。广播数据包最多能携带31个字节的数据一般包含可读的设备名称设备是否可连接等信息。当主机收到从机广播的数据包后它可以再发送获取更多数据包的请求这个时候从机将广播扫描回应数据包扫描回应数据包和广播包一样可以携带31个字节的数据。3.3扫描扫描是主机监听广播信道、接收从机广播数据的过程。主机通过扫描可以发现周围正在广播的从机设备获取广播数据包如果从机支持扫描响应主机还可以发送扫描请求进一步获取扫描响应数据包。之后主机可以根据扫描到的设备信息选择是否向某个从机发起连接请求从而建立连接并进行通信。扫描过程中有两个比较重要的时间参数扫描窗口 和 扫描间隔。扫描窗口主机真正打开接收窗口、监听广播信道的时间。扫描间隔两个连续扫描窗口开始时刻之间的时间差。可以简单理解为|----------- 扫描间隔 -----------|--------------- ---------------|扫描窗口|休息时间|扫描窗口|--------------- ---------------扫描窗口和扫描间隔需要满足以下关系扫描窗口和扫描间隔的设置值都不能大于 10.24 s。扫描窗口的值不能大于扫描间隔。如果扫描窗口等于扫描间隔表示主机一直处于扫描状态中间没有休息时间。根据是否发送扫描请求BLE 扫描可以分为 被动扫描 和 主动扫描。被动扫描主机只监听广播信道上的数据。当接收到从机发送的广播包后协议栈会把广播数据上报给上层应用。被动扫描不会向从机发送额外请求因此只能获取广播包中携带的数据。主动扫描主动扫描除了接收从机的广播包之外还会在条件允许时向从机发送扫描请求。如果从机的广播类型支持扫描响应那么从机会返回一个 扫描响应数据包。这样主机除了能拿到广播数据外还可以额外获取扫描响应数据。因此主动扫描通常比被动扫描能获取更多信息例如完整设备名称、额外服务 UUID 或厂商自定义数据等。3.4连接BLE 设备在建立连接之后主机和从机就不再依赖广播信道进行普通数据通信而是会切换到数据信道上进行收发。连接建立后双方会按照一定的时间周期进行通信这个周期称为 连接事件。在每个连接事件中主机和从机可以相互发送数据。如果当前没有业务数据需要传输链路层也会通过空包维持连接状态。可以简单理解为连接建立|v----------- ----------- -----------|连接事件||连接事件||连接事件|----------- ----------- -----------||||-- 连接间隔 --||-- 连接间隔 --|连接中比较重要的参数主要有三个连接间隔、从机延迟 和 监督超时。连接间隔Connection Interval连接间隔表示两次连接事件之间的时间间隔单位是 1.25 ms。它的取值范围通常是 7.5 ms ~ 4 s。连接间隔越短通信响应越快但功耗越高连接间隔越长功耗越低但数据交互的延迟也会增加。从机延迟Slave Latency从机延迟表示从机在没有数据需要发送时可以跳过若干个连接事件从而减少唤醒次数、降低功耗。如果从机有数据需要发送则需要在连接事件中进行响应。监督超时Supervision Timeout监督超时用于判断连接是否已经失效。如果在这个时间内没有成功完成连接事件设备就会认为连接断开并回到未连接状态。监督超时的单位是 10 ms取值范围通常是 100 ms ~ 32 s。连接参数之间有一定约束关系。比如监督超时必须大于有效连接间隔否则设备可能还没来得及进行下一次通信就被判断为连接超时。有效连接间隔可以简单理解为从机实际参与通信的最大时间间隔有效连接间隔 连接间隔 × (1 从机延迟)例如连接间隔100ms 从机延迟4有效连接间隔 100 ms × (1 4) 500 ms这表示在没有数据需要发送时从机最多可以每隔 500 ms 参与一次连接事件。实际开发中连接参数通常需要根据应用场景进行权衡如果希望通信响应更快可以适当减小连接间隔但功耗会增加。如果希望降低功耗可以适当增大连接间隔或者设置合理的从机延迟。如果设备需要兼容手机等平台还需要注意不同系统对连接参数可能有额外要求。总的来说BLE 连接阶段的核心就是主机和从机按照连接间隔周期性进入连接事件在连接事件中完成数据收发或维持链路通过连接间隔、从机延迟和监督超时这几个参数可以在通信实时性和功耗之间做平衡。4.GAP和GATT4.1 GAP和GATT的区别蓝牙协议栈分为两类结构控制器Controller和主机Host。每个类别都有子类别这些子类别执行特定的角色。我们将要研究的两个子类别是 通用访问配置文件 GAP和 通用属性配置文件 GATT。GAP是Generic Access Profile的缩写中文含义是通用访问配置文件定义了 BLE网络堆栈的一般拓扑。GATT是Generic Attribute Profile的缩写中文含义是通用属性配置文件详细描述了一旦设备建立连接后如何传输属性数据。GATT特别关注如何根据其描述的规则格式化打包和发送数据。在BLE网络堆栈中属性协议ATT与GATT紧密对齐GATT直接位于ATT的顶部。GATT实际上使用ATT来描述如何从两个连接的设备交换数据。4.2 GAP通用访问配置文件BLE设备可以使用两种机制与外界通信广播或连接。这些机制受通用访问配置文件GAP准则的约束。GAP定义了启用BLE的设备如何使其自身可用以及两个设备如何直接相互通信。4.2.1 建立连接设备可以通过采用GAP中指定的以下角色来加入BLE网络A、广播Broadcasting这些角色不必显式地相互连接即可传输数据。广播者Broadcaster广播公共数据包的设备例如可以广播按下按钮的时间。观察者Observer侦听广播者发送的广告包中数据的设备。广播者和观察者之间没有任何连接。/2、/2B、连接Connecting这些角色必须显式连接和握手才能传输数据。这些角色比广播角色更常用。从机设备Peripheral 通过广播告知其他设备自己的存在以便主机设备可以建立连接。连接后从机设备不再向其他主机设备广播数据而是保持与主机设备的连接。从机设备功耗低因为它们只需要定期发送信标即可。主机设备负责开始与从机设备的通信。手环是BLE外设的一个示例。主机设备Central一种通过侦听广播包来启动与从机设备的连接的设备。主机设备可以连接到许多其他从机设备。当主机设备要连接时它将请求连接数据包发送到从机设备。如果从机设备接受来自主机设备的请求则建立连接。当您的手机连接到手环时就是BLE Central设备的一个示例。4.2.2 建立连接后主机设备可以更新连接参数 主机设备通常在设备与其自身之间建立连接参数。只有主机设备能修改连接参数。但是从机设备可以要求主机设备更改连接参数及从机发送更新参数请求。从机设备或主机设备可以终止连接连接可能由于多种原因而终止例如设备的电池可能耗尽或网络干扰可能导致连接失败。设备还可以主动与对等设备断开连接。4.3 通用属性配置文件4.3.1模型角色GATT分为两种类型注意与从机或主机无关。客户端Client客户端可以发送请求给GATT服务端客户端可以读Read/写Write服务端的属性Attributes 通过属性可以通信数据。服务端Server服务端是用来存储属性Attributes 的每当客户端发送请求时服务端会相应这些请求。4.3.2客户端与服务端的关系一个示例如下手环采集了心跳信息希望计算机读取该信息。手环充当服务端并提供信息。手机充当客户端读取该信息。GAP和GATT模型角色基本上彼此独立从机设备或主机设备都可以充当服务端或客户端这取决于数据的流动方式。在一般的主从机通信时主机可以通过读写从机的属性实现接收和发送数据给从机从机可以通过发送通知的方式实现与主机的通信。因此一般从机是作为GATT的服务端主机作为GATT的客户端。5.协议栈分层协作下面以如何发送一个无线数据包的例子来简单阐述协议栈中各分层的作用和必要性。实际上协议栈的实现可能更加负责它需要考虑方方面面的因素。5.1 发送数据包假设有设备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两者之间的连接。5.2 广播方式我们先来看一下简单的广播情况这种情况下我们把设备A叫advertiser广播者设备B叫scanner或者observer扫描者。广播状态下设备A的LL层API将变成send_LL(0x53,2402M, 0x8E89BED6)。由于设备B可以同时接收到很多设备的广播因此数据包还必须包含设备A的device address0xE1022AAB753B以确认该广播包来自设备A为此send_LL参数需要变成send_LL(0x53,2402M, 0x8E89BED6, 0xE1022AAB753B)。LL层还要检查数据的完整性即数据在传输过程中有没有发生窜改为此引入CRC24对数据包进行检验 (假设为0xB2C78E) 。同时为了调制解调电路工作更高效每一个数据包的最前面会加上1个字节的preamble前导帧preamble一般为0x55或者0xAA。这样整个空中包就变成注空中包用小端模式表示上面这个数据包还有如下问题没有对数据包进行分类组织设备B无法找到自己想要的数据0x53。为此我们需要在access address之后加入两个字段LL header和长度字节。LL header用来表示数据包的LL类型长度字节用来指明payload的长度设备B什么时候开启射频窗口以接收空中数据包如上图case1所示当设备A的数据包在空中传输的时候设备B把接收窗口关闭此时通信将失败同样对case2来说当设备A没有在空中发送数据包时设备B把接收窗口打开此时通信也将失败。只有case3的情况通信才能成功即设备A的数据包在空中传输时设备B正好打开射频接收窗口此时通信才能成功换句话说LL层还必须定义通信时序。当设备B拿到数据0x53后该如何解析这个数据呢它到底表示湿度还是电量还是别的意思这个就是GAP层要做的工作GAP层引入了LTVLength-Type-Value结构来定义数据比如02010502-长度01-类型强制字段表示广播flag广播包必须包含该字段05-值。由于广播包最大只能为31个字节它能定义的数据类型极其有限像这里说的电量GAP就没有定义因此要通过广播方式把电量数据发出去只能使用供应商自定义数据类型0xFF即04FF590053其中04表示长度FF表示数据类型自定义数据0x0059是供应商ID自定义数据中的强制字段0x53就是我们的数据(设备双方约定0x53就是表示电量而不是其他意思)。最终空中传输的数据包将变成AAD6BE898E600E3B75AB2A02E102010504FF5900538EC7B2AA – 前导帧(preamble)D6BE898E – 访问地址(access address)60 – LL帧头字段(LL header)0E – 有效数据包长度(payload length)3B75AB2A02E1 – 广播者设备地址(advertiser address)02010504FF590053 – 广播数据8EC7B2 – CRC24值有了PHYLL和GAP就可以发送广播包了但广播包携带的信息极其有限而且还有如下几大限制无法进行一对一双向通信 广播是一对多通信而且是单方向的通信由于不支持组包和拆包因此无法传输大数据通信不可靠及效率低下。广播信道不能太多否则将导致扫描端效率低下。为此BLE只使用37(2402MHz) /38(2426MHz) /39(2480MHz)三个信道进行广播和扫描因此广播不支持跳频。由于广播是一对多的所以广播也无法支持ACK。这些都使广播通信变得不可靠。扫描端功耗高。由于扫描端不知道设备端何时广播也不知道设备端选用哪个频道进行广播扫描端只能拉长扫描窗口时间并同时对37/38/39三个通道进行扫描这样功耗就会比较高。而连接则可以很好解决上述问题下面我们就来看看连接是如何将0x53发送出去的。5.3 连接方式到底什么叫连接(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将周期性以CIconnection 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这样数据包变成了1B130053L2CAP用来指定connection interval连接间隔比如每10ms同步一次CI不体现在数据包中同时指定逻辑通道编号0004表示ATT命令最后把ATT数据长度0x0004加在包头这样数据就变为040004001B130053LL层要做的工作很多首先LL层需要指定用哪个物理信道进行传输物理信道不体现在数据包中然后再给此连接分配一个Access address0x50655DAB以标识此连接只为设备A和设备B直连服务然后加上LL header和payload length字段LL header标识此packet为数据packet而不是control packet等payload length为整个L2CAP字段的长度最后加上CRC24字段以保证整个packet的数据完整性所以数据包最后变成AAAB5D65501E08040004001B130053D550F6AA – 前导帧(preamble)0x50655DAB – 访问地址(access address)1E – LL帧头字段(LL header)08 – 有效数据包长度(payload length)04000400 – ATT数据长度以及L2CAP通道编号1B – notify command0x0013 – 电量数据handle0x53 – 真正要发送的电量数据0xF650D5 – CRC24值