ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

BLE广播、扫描与连接:协议详解与工程组合实践

BLE广播、扫描与连接:协议详解与工程组合实践 一提到蓝牙大多数人的第一反应是“配对、连接、传文件”。但这个印象在BLE项目里会带来麻烦。BLE低功耗蓝牙的底层逻辑是把“发现设备”和“通信会话”彻底分开设备之间可以不建立任何连接就交换数据也可以只为“让被人找到”而广播还可以在连接建立之后把广播停掉所有数据都走点对点通道。广播、扫描、连接是三种独立的基础操作它们的类型和组合方式直接决定一个BLE项目的功耗、时延、稳定性和产品可用性。这篇文章想聊的就是我对广播、扫描、连接从协议层到工程层的理解包括各自有哪些类型、关键参数怎么设、容易在哪儿踩坑以及实际项目里如何把它们组合成合理的架构。文章内容适合刚开始接触BLE的嵌入式开发、App开发也适合那些已经做了几个方案但总在功耗和稳定性上纠结的工程师。1. 先用一张“草图”理解广播、扫描、连接的关系1.1 三者各自解决什么问题广播是一个“单向发布”的动作。设备把自己的存在、身份、数据以广播包的形式在无线信道里发出去谁愿意听谁听不需要事先配对也不需要接收方确认。打个比方广播就像一个人把一张写了信息的纸条贴在公共公告栏上路过的人都能看一眼但贴纸条的人并不知道谁看了。扫描是“单向收集”的动作。扫描方在信道里侦听广播包把周围设备发出来的信息收下来解析。它不主动给广播方回数据只说“我来看看公告栏上有哪些纸条”。连接则是“双向会话”的动作。当广播方愿意被连接扫描方也有连接需求时扫描方会主动发送连接请求双方达成一个点对点的连接之后数据交换都在连接事件里进行。这个阶段不再需要广播来传递业务数据广播最多作为“被发现”的信号存在。把三件事拆开看是BLE区别于经典蓝牙最核心的设计思想。经典蓝牙从寻呼到连接再到通信一步也不能少而BLE允许你在不需要双向通信的场景里只用广播和扫描就完成整个业务闭环。1.2 为什么这样设计从工程角度看这种拆分首先带来的是功耗优势。广播和扫描都能在很短的时间内完成设备可以“醒过来发一个包再睡”非常适合电池供电场景。比如一个温湿度传感器放在仓库角落只按固定周期广播数据读取网关扫描到广播并解析数据整个过程不需要建立连接传感器的平均电流可以做到非常低。其次它带来了极大的灵活性。需要下发指令时设备可以边广播边保持可连接手机扫描到之后再发起连接不需要下发指令时设备可以选择不可连接的广播类型把自己变成一个纯粹的数据发布者。实际产品中“先广播、再扫描、后连接”的组合方式是绝大多数BLE应用的基本套路。正是因为这个“三角色”模型BLE设备的角色也被明确划分为Broadcaster广播者、Observer观察者、Central中心设备、Peripheral外围设备几种。理解角色比背任何协议文档都重要。2. 广播类型与广播参数拆解2.1 四种广播类型怎么选BLE广播包按“能否被连接”和“能否被扫描”两个维度划分为四种类型。规范里的标准名称分别是ADV_IND、ADV_DIRECT_IND、ADV_NONCONN_IND、ADV_SCAN_IND。我用一个表格把它们的特性列清楚广播类型能否被连接能否响应扫描请求最大负载典型场景ADV_IND可连接不可定向广播可以可以广播31B 扫描响应31B智能手环、蓝牙耳机等等待手机连接ADV_DIRECT_IND可连接定向广播可以仅限指定设备一般不响应广播31B已配对设备之间的快速重连ADV_NONCONN_IND不可连接不可定向广播不可以不可以广播31B信标、传感器数据上报ADV_SCAN_IND可扫描不可定向广播不可以可以广播31B 扫描响应31B需要发布较多数据但不需要连接的节点ADV_IND是使用频率最高的类型。它的特点是“可连接且可扫描”也就是说扫描方不仅能收到广播包还可以主动发送扫描请求从广播方那里再拿一份额外的扫描响应数据。它适合做那些需要被手机“找到并连接”的设备。比如手环在待连接状态下用ADV_IND广播自己的设备名和服务的UUID手机扫描到之后发起连接业务数据都走连接通道。ADV_DIRECT_IND是面向指定设备的定向广播。这种广播里通常会带上目标设备的地址或者配合白名单使用目标明确、建立连接速度快。典型场景是已配对设备靠近后快速回连比如耳机靠近手机自动连接。定向广播的缺点是功耗比ADV_IND高而且在广播期间基本不接受其他设备打扰。ADV_NONCONN_IND是纯广播类型不可连接也不可扫描。它适合那些“只需要发数据不需要被连接”的设备。iBeacon、防丢标签、传感器节点都用这种类型。好处是广播协议最简、干扰最小、功耗最低。代价是丢掉连接能力一旦设备需要被配置参数就得切换广播类型或者在特定时间段进入可连接模式。ADV_SCAN_IND是一种比较“折中”的类型。它不可连接但可以被扫描而且可以回应扫描请求。如果设备想发布的数据超过了31字节又不想承担连接带来的开销就可以用ADV_SCAN_IND把扩展数据放在扫描响应里让扫描方通过主动扫描拿到完整数据。实际选型时很多人习惯默认用ADV_IND理由是“万一以后需要连接呢”。但如果产品定位是纯采集、纯上报用ADV_NONCONN_IND或ADV_SCAN_IND会更合适。广播类型决定了扫描方和连接方的交互行为定错类型后面功耗和连接逻辑都会别扭。2.2 广播间隔与三个物理信道广播不是在一个信道上连续发而是在三个专用广播信道37、38、39上轮流发送。这三个信道分别位于2.4GHz频段的2402MHz、2426MHz、2480MHz特意避开了Wi-Fi常用的1、6、11信道目的是减少Wi-Fi信号的干扰。所以工程师调试时不要用家用路由器的频段状态去推测蓝牙广播区域两者信道划分完全不同。广播间隔Advertising Interval是广播事件之间的时间间隔它决定设备“多久去三个信道各发一次”。BLE规范允许的范围是20ms到10.24s且每次广播事件会叠加一个0到10ms的随机延迟避免多个设备间的广播包在信道上固定碰撞。间隔越短设备被发现的速度越快但功耗也越高。这个很好理解同样是一个广播事件假设发射电流约15mA、单次占用射频约2ms如果间隔为100ms平均广播电流大约就是15mA × 2ms / 100ms 0.3mA。如果把间隔收到30ms平均电流会涨到接近1mA对纽扣电池来说压力非常大。不同产品适合的间隔差异很大。我做过一个室内定位信标广播间隔设为200ms因为需要设备被较快发现同时电池容量还算充裕做过一个户外资产标签电池是CR2032要求续航两年间隔直接放到800ms还做过一个商场导览Beacon数据更新不频繁间隔设到1s接收端体验也没有明显变差。实际部署时给广播间隔加随机延迟不是可选操作而是必须。大量设备同时广播如果间隔完全相同它们会在某些时刻持续“撞车”。规范里的AdvDelay就是干这个的控制器会自动处理但设计时还是要留意识别不要自己手动把广播间隔写成一个固定值剥夺硬件层的随机化能力。2.3 广播数据包结构广播数据包由2字节头部和多达31字节的数据组成。如果要发更多数据配合主动扫描还能拿到一个31字节的扫描响应。整个数据区域由若干个AD StructureAdvertising Data Structure拼接而成每个结构是“1字节长度1字节类型N字节数据”的格式。常见的AD类型包括Flags0x01标识设备的物理能力比如是否支持LE、是否支持BR/EDR等。Complete Local Name0x09完整的设备名App显示名称时常用。Shortened Local Name0x08截断的设备名。Service UUID列表0x02、0x03、0x06、0x07等按16位或128位UUID格式广播服务标识。Manufacturer Specific Data0xFF厂商自定义数据区项目中最常用。工程上有几个习惯做法。设备名通常放广播包里因为扫描列表直接展示名称自定义的业务数据比如温湿度、电压、状态标志通常放厂商数据区如果数据太多就把一部分挪到扫描响应里。还有一种做法是广播包里不放可读名称只放UUID和自定义数据靠服务UUID识别设备这样能省出更多字节给业务负载。需要注意广播包长度一旦超过31字节控制器会直接报错或者截断。我在早期做项目时把设备名、电量、服务UUID、三组业务数据全塞进广播包结果长度爆了调试时只看到空包。后来改成把低频更新的数据放扫描响应问题就解决了。规划广播内容时先算长度再分配位置不是写完再压缩。3. 扫描类型与扫描参数拆解3.1 主动扫描和被动扫描怎么选扫描方有两种工作方式被动扫描Passive Scanning和主动扫描Active Scanning。被动扫描只监听信道上的广播包不发任何额外数据。它拿到的信息就是广播包本身不含扫描响应。功耗最低适合那些不需要完整数据的场景。主动扫描在收到广播包后会主动给广播方发送一个扫描请求SCAN_REQ请求对方把扫描响应数据再发一次。能够回应扫描请求的广播类型上文中已经列出ADV_IND和ADV_SCAN_IND都可以回应ADV_NONCONN_IND和ADV_DIRECT_IND则不能。主动扫描能拿到最多62字节数据量更完整代价是每次扫描会多一次收发过程功耗略高。实际产品选型时手机App端的扫描基本都是主动扫描因为手机不差这点功耗但需要拿到完整的设备信息来做展示和过滤。自研网关长期24小时扫描时则要根据环境选择。如果周边都是ADV_NONCONN_IND这种不支持扫描响应的设备主动扫描就是白费电被动扫描反而更合适。如果广播数据量被拆分到扫描响应里就只能用主动扫描。3.2 扫描窗口与扫描间隔的配合扫描也不是一直开着接收机的扫描仪同样有占空比。两个参数要一起看扫描间隔Scan Interval和扫描窗口Scan Window。扫描间隔是两次扫描之间的间隔时间扫描窗口是每次扫描的持续时间。比如设定scanInterval400ms、scanWindow100ms意思是每400ms周期里只扫描100ms占空比25%。占空比越大发现广播包的速度越快功耗也越高。当scanWindow等于scanInterval时就是连续扫描设备一直处于接收状态。例算一下如果广播端把间隔设为200ms扫描端按50ms扫描窗口、250ms扫描间隔工作理论上每250ms里有50ms在听一个周期内大概能听到1到2个广播包发现延迟基本在250ms以内。如果扫描间隔拉到1s可能就要1s以上才能发现设备这在一些需要快速响应的场景里是不能接受的。扫描端还要记得在37、38、39三个信道上轮转。如果三个信道都在扫描窗口内被轮完并且扫描窗口大于3倍的广播间隔基本可以保证至少抓到一次广播。设计扫描参数时建议把扫描间隔和广播间隔错开不要做成完全等长否则可能错过一半以上的信道。3.3 按UUID、RSSI、设备地址过滤扫描数据量大时过滤策略比扫描参数更影响性能。BLE控制器允许预设过滤规则最常用的是按服务UUID过滤。比如一个App只关心自家温控器那么扫描时只允许包含厂商特定服务UUID的广播上报给CPU其余全部丢弃。硬件过滤比软件过滤高效得多也省电。按RSSI过滤也很实用。比如网关部署在复杂环境中周边可能有很远设备的“微弱信号”如果把这些广播都收上来解析CPU占用率会飙升。给扫描滤波器设一个RSSI阈值比如-80dBm以上才上报能有效筛掉远距离杂散设备。按设备地址过滤则适合定向场景比如系统只关心固定的一组传感器。白名单机制在规范层就是这么用的。综合策略通常是“硬件地址UUID软件RSSI”三层过滤层层缩窄最终数据处理量非常小。4. 连接类型与连接参数拆解4.1 主设备和从设备的角色分配BLE连接建立后会有两个明确的角色发起连接的一方是Central主设备被连接的一方是Peripheral从设备。常见组合里手机是Central手环是Peripheral网关是Central传感器是Peripheral。Central可以同时和多个Peripheral维持连接但每个连接都要占用资源和射频时间。Peripheral通常只能维持一个连接具体能连几个看芯片实现。连接建立后广播可以停止数据都走连接事件。蓝牙规范并不强制连接后停止广播但绝大多数产品会这么做因为广播和连接同时开射频调度复杂度高功耗也高。工程上有个容易混淆的地方扫描方不一定是连接发起方。按照BLESpec扫描和发起连接是两个状态设备可以先扫描再对目标广播包发起连接。但芯片实现里通常有一个“发起连接”的独立状态跟普通扫描区分开。调试时如果发现设备一直扫描但连不上先检查是否启用了发起连接功能而不是仅停留在扫描状态。4.2 连接参数间隔、潜伏、超时连接参数是连接质量的命门主要包括三个连接间隔Connection Interval、从设备延迟Slave Latency、监督超时Supervision Timeout。连接间隔是以1.25ms为单位的整数范围从6到3200即7.5ms到4s。它决定了每个连接事件之间的时间。间隔越短双向通信延迟越低吞吐越高但两边设备的唤醒频率也越高功耗显著上涨。间隔越长省电但延迟大。做高实时性遥控器interval可以设到7.5ms到15ms做低频率上报的传感器interval设到200ms以上完全够用。从设备延迟Slave Latency允许从设备在连续若干个连接事件中不监听、不回复从设备直接就休眠直到它需要发数据或者延迟周期结束才醒。这个参数对省电非常关键。比如connInterval100msSlaveLatency4那么从设备最多可以在400ms内不理会主设备。延迟越大功耗越低但主设备发数据后从设备响应的最大延迟也越大。监督超时是连接断开保护的兜底值范围100ms到32s。规范要求它必须大于“连接间隔×(1从设备延迟)×2”。因为如果主设备发数据后从设备因为延迟在睡眠超时过短就会被误判为断连。比如connInterval30ms、SlaveLatency2时超时下限至少是(30ms 30ms×2)×2 180ms实际项目我会留足余量设置成2s甚至更大避免干扰导致的误断开。这三个参数之间没有绝对最优解只有取舍。做设备交互的产品低延迟优先把interval拉低、latency设为0做数据采集的产品低功耗优先把interval调大、latency调大用缓存数据批量上报。4.3 连接状态机与连接事件连接建立后双方在每个连接间隔里开展“连接事件”。每次连接事件由主设备发送数据包开始从设备在150微秒的帧间间隔后回复。没有数据要传时双方也要按一定节奏发送空包维持连接活跃否则监督超时会触发并断开连接。这也解释了为什么很多低功耗设备断开连接“没有征兆”——主设备连续几次连接事件没收到从设备的回复控制器就决定断开连接。出现这种情况排查方向通常不是业务代码而是射频环境、信号强度、连接参数设置是否超出了双方协商范围。BLE连接参数可以协商请求。从设备有更合适的参数时可以向主设备发送“连接参数更新请求”。手机端的CoreBluetooth对参数更新有一套主机策略未必会立即接受。我在做智能锁App连设备时经常遇到参数更新请求被系统拒绝的情况解决办法是尽早把设备端的默认连接参数调好而不是依赖连接后的协商。5. 广播、扫描、连接的类型组合方式5.1 四种主流组合模式不同类型的广播和连接需求构成了BLE产品的主要架构。常见组合模式大概有四类组合模式广播侧类型扫描/连接侧典型产品纯广播模式ADV_NONCONN_IND被动/主动扫描不连接信标、电子围栏、资产标签广播扫描响应ADV_SCAN_IND主动扫描拿完整数据环境传感节点、气象站可连接广播模式ADV_IND扫描到后发起连接手环、耳机、智能锁定向广播连接ADV_DIRECT_IND白名单发起连接已配对设备快速回连第一种纯广播模式最适合“一端发布、一端读取”的业务。比如商场里的导览Beacon它只是不停广播位置ID顾客手机扫描到之后App显示对应楼层信息整个过程手机不需要和Beacon建立连接Beacon也无需知道自己被谁扫描。这种模式下Beacon的功耗极低广播间隔也能拉大电池续航按年算。第二种广播扫描响应的模式适合数据量略大但不需要连接的场景。一个农业环境信息节点可以广播设备ID、传感器类型把具体的测量数据放大扫描响应里面。网关用主动扫描把两部分数据都拉回来一次采集周期就完成了。这个模式比纯广播信息量大比建立连接简单设备可以维持非常低的平均电流。第三种可连接广播模式是互动类产品的标配。智能锁平时发ADV_IND广播手机扫描到后连接App下发开锁指令完成后再断开。这样既保证了“设备在线可被找到”也保证了操作期间的低延迟通信。这种模式下广播间隔不宜太长否则手机扫不到连接参数也得为操作实时性做优化interval尽量短。第四种定向广播模式很特别它主要用在已配对设备靠近后恢复连接的场景。耳机和手机之间已经保存了彼此地址耳机向外广播一个带有目标地址的直接广播包手机收到后直接发起连接。因为通信目标明确建立连接的速度非常快不需要去扫描列表里筛选设备。5.2 组合中的冲突和取舍很多人都问过“我能不能让设备在保持连接的同时继续广播”答案是可以但没必要轻易尝试。连接期间的射频调度和广播期的射频调度完全两码事广播和连接事件都依赖于同一个射频前端同时使用时会在时间上互相挤占实际表现是丢包率升高、连接事件被跳过去甚至广播也被截断。工程上推荐的做法是连接建立后主动停止广播连接断开后再恢复广播。多设备共存时的广播冲突也需要关注。数百个节点同时广播如果大家的广播间隔没有随机扰动某些信道上会出现持续碰撞。规范里AdvDelay的主要作用就是打散这种碰撞。另一个办法是尽量把广播间隔设置得散开比如分布在300ms到500ms之间而不是所有设备都固定成400ms。还有一个很容易被忽略的取舍是“发现速度”和“功耗”的矛盾。产品经理通常要求“手机一点就能马上发现设备”但设备端为了续航又希望广播间隔尽量大。我常用的折中方案是设备平时用低速广播维持存在感进入“配对模式”后临时把广播间隔缩到30ms左右持续几分钟。这样既满足日常低功耗也满足用户主动操作时的高响应要求。5.3 两个完整组合设计示例举一个我在实际项目中做过的传感器网关方案。节点使用ADV_NONCONN_IND广播广播数据里放厂商ID、节点ID、温湿度值和电池电量广播间隔设为1000ms节点平均电流极低。网关侧持续主动扫描scanInterval设500ms、scanWindow设250ms通过UUID过滤只接收节点广播解析后通过有线或蜂窝网络上报。这种组合不需要连接节点数量可以轻松扩展到上千个。另一个是智能锁方案。锁在待机时用ADV_IND广播广播间隔100ms广播内容包含厂商UUID、锁状态、以及一个随机数用于防重放。手机App扫描到后发起连接锁在建立连接后停止广播。连接参数设置为connInterval15ms、SlaveLatency0、SupervisionTimeout2s保证开锁命令的低延迟传输。锁与App完成指令交互后主动断开连接重新恢复广播。这种“扫描发现快速连接命令交互断开恢复”的闭环很多可穿戴设备也都是这么跑的。6. 常见问题与排查技巧实录6.1 广播扫描阶段的问题速查现象可能原因排查建议扫描端完全看不到广播广播类型为定向广播目标地址不匹配或广播间隔过大、扫描窗口太短先用nRF Connect直接看原始广播确认设备在发什么广播包数据读不全广播包超出31字节被截断或扫描方式为被动扫描拿不到扫描响应检查广播长度改用主动扫描确认广播类型支持扫描响应近距离能看到远一点就丢RSSI滤波阈值设置太高把弱信号广播滤掉了把滤波阈值放宽或用扫描日志看RSSI分布多个设备互相干扰发现不稳定广播间隔未加随机量设备间频繁同频碰撞给广播间隔加随机量或把各设备间隔散开手机扫描时好时坏Android后台扫描限制、iOS后台机制导致扫描停止排查系统对后台扫描的限制前台保持应用活跃状态6.2 连接阶段的问题速查现象可能原因排查建议能扫描到但连不上设备广播类型为ADV_NONCONN_IND或ADV_SCAN_IND不可连接改用ADV_IND或ADV_DIRECT_IND确认代码里广播类型配置连接成功后很快断开监督超时设置过短或信号波动跳过多个连接事件验证超时参数是否满足规范要求适当加大超时值连接休眠后假死Slave Latency设置过大主设备等太久没有善后或连接间隔过大权衡功耗与响应调小Slave Latency重新测试高吞吐场景数据频繁丢包连接间隔太大且没有启用数据长度扩展DLE、PHY速度不足缩短连接间隔启用2M PHY或调整MTU更新物理层手机与设备连接后几乎无信号手机系统对连接参数更新请求策略不同尽量在连接前就协商好参数连接后依赖系统配合不要硬杠6.3 调试工具的实战经验调试BLE最有效的组合我在项目里反复使用的是“nRF Connect App 空中抓包工具 芯片日志”三件套。nRF Connect在手机端可以看到原始广播包包括AD Structure的逐字节解析、RSSI、广播类型直接在应用市场能下载。排查“设备明明在广播但App找不到”的问题第一步就是拿这个App扫一下设备确认物理层是否正常工作。空中抓包工具用Wireshark配合支持BLE监听芯片的硬件可以抓到每个广播包、扫描请求、连接请求和连接事件。排查“连接建立后握手异常”这类问题光看手机日志是不够的因为手机系统很多细节不会暴露。空中抓包能直接看到谁在哪个信道上发送了什么定位到是射频问题还是协议问题还是应用层逻辑问题。芯片日志则用来确认控制器内部状态。很多控制器会把广播事件完成、连接事件发生、超时触发等事件打印到日志里配合空中抓包一起看基本能还原整个链路。经验是任何BLE稳定性问题都要先区分问题发生节点是“物理层”还是“主控固件层”还是“对端系统层”分层排查比来回乱试高效得多。6.4 一些独家避坑心得第一不要把广播数据和业务数据混在一个事件里发太多。广播负载一旦占满31字节控制器处理时间会变长而且广播事件时间延长会增加碰撞概率。合理分配广播和扫描响应超过负载就考虑升级到连接模式或扩展广播如果有支持。第二做多设备协同产品时广播数据的格式要设计成“固定字段可变字段”而不是一长串自定义二进制。因为后续加字段时不仅要改设备端还要改App和网关解析格式没有预案升级就是事故。第三参数不要拍脑袋写死把广播间隔、扫描间隔、连接参数做成可配置项放在产品固件里留调试接口。我在现场测过很多次同样的硬件用不同的广播间隔抗干扰表现完全不同。固化参数等于把测试能力也焊死。第四遇到手机兼容性问题时不要第一时间怀疑自己的服务器或后台。BLE连接成功后App和云端通信失败先确认手机与设备连接是否在再查数据链路。这个顺序错了时间成本会翻倍。我个人在实际操作中最想叮嘱的是BLE调试里“慢”往往比“快”有价值。看到一个现象先停下来看是哪个角色在哪个阶段出了问题再动手改参数。广播/扫描/连接这三类操作虽然拆开来独立但它们最终靠“组合”完成业务。把每个角色和每个参数都摸到边界再去做产品级调优就会顺很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进