ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BLE透传速率上不去?一文详解连接间隔、MTU与2M PHY调优实战

BLE透传速率上不去?一文详解连接间隔、MTU与2M PHY调优实战 1. 蓝牙透传速率为什么总上不去先说个我自己的例子。之前做一款运动传感器通过BLE把九轴姿态数据往上抛频率50Hz、每包90字节按算法工程师给的预算物理链路至少得跑50kbps以上。结果实测呢连接倒是快可数据一多就丢包延迟还忽高忽低整包数据在手机上拼起来断断续续。我一开始怀疑是天线问题、是射频干扰折腾了三天最后发现根本不是硬件的事而是连接参数压根就没配对。BLE透传的麻烦就在这——它不像串口那样把线接上就能跑满也不像Wi-Fi那样带宽富余。BLE的物理层速率本来就有限再加上协议栈层层封装、连接事件是周期性调度的、收发双方还得互相等应答真正留给应用的带宽远低于你看到的理论值。如果你之前只看过“BLE 2M PHY理论速率2Mbps”这句话那我建议你把“理论”两个字划掉心里默念三遍有效吞吐率通常只有理论值的三分之一到五分之一。这篇文章是BLE透传速率调优的第二篇上一篇我聊过基础链路是怎么建立的这篇我会把重点放在“怎么把速率真正调上去”这件事上。我会先用大白话讲清楚影响速率的四个关键参数再给出可以照着抄的ESP32工程配置步骤最后整理几组我在不同场景下实测下来的数据以及踩过的坑。适合谁看做过BLE透传但是速率不满意、连接参数不知道从哪下手的嵌入式工程师还有写手机端BLE工具、碰到吞吐率瓶颈想从协议层找原因的小伙伴。看完之后你至少能判断出你的设备速率上不去到底是参数没调对还是协议栈本身的锅。2. 决定吞吐率的四个核心参数你必须吃透2.1 连接间隔决定“传送带”的转数BLE主机和从机之间不是随时都能传数据的它们约定好一个时间周期每隔一段时间同步一次数据这个周期就叫连接间隔Connection Interval单位是1.25ms的整数倍。你可以把它理解成一条传送带——每隔一段时间转过来一格格子上放数据。连接间隔越小传送带转得越快单位时间内能搬运的数据就越多。但是连接间隔不能无限缩小。BLE 4.2标准里规定最小连接间隔是7.5ms最大是4s。实际能不能跑到7.5ms取决于两个因素从机的射频处理速度能不能在每个连接事件内及时完成收发手机或主机的协议栈是否支持这么短的间隔。苹果的设备比较严格要求连接参数必须符合它定义的规则才能正常使用。安卓那边高通、联发科、三星平台各自都不一样有的能轻松跑到7.5ms有的嘛连30ms都不稳定。举个例子。假设连接间隔是30ms每个连接事件实际数据收发时间只占其中很小一部分其余时间都在空转等待。如果把连接间隔从30ms调到7.5ms传送带转速直接4倍吞吐率上限也跟着拉高4倍。这就是为什么很多人一改连接间隔吞吐率立刻翻倍的原因。2.2 MTU与ATT Payload一次能搬多少货传送带转速上来了但每次能搬多少货取决于协议栈的MTUMaximum Transmission Unit。BLE里面有个ATT层负责“应用数据”的读写和通知它允许通信双方协商一个更大的MTU值。默认MTU是23字节减去3字节的ATT头实际每包只能带20字节数据。这在小数据量场景下没啥问题要是想跑几十上百kbps20字节一包就是纯纯的灾难——包头开销比数据本身还大。把MTU从23提到247字节这是BLE 4.2之后常见的最大值每包有效数据从20字节涨到244字节。注意这多出来的不只是容量关键是减少了同等数据量下的包数量和交互次数。比如你要发2440字节的数据用20字节一包得发122次用244字节一包只需10次效率天差地别。但是需要提醒一句有些老设备或者低成本的BLE从机模块协议栈固件里只实现了默认23字节MTU协商请求发过去对方不响应。这时候就得看模块手册确认支持“MTU Exchange”这个特性。2.3 数据长度扩展相当于给单包“扩容”MTU解决的是ATT层一次能放多少数据但物理层也不是你想塞多少就能塞多少。标准BLE数据包PDU里单包数据最长只有27字节包含2字节头。也就是说即使ATT层协商到247字节的MTU底层还要把这一大包拆成好几个LE Data PDU去发。数据长度扩展DLEData Length Extension是BLE 4.2引入的能力它允许把单包PDU的有效载荷从27字节扩展到251字节。这样一来ATT层的一个大包可以更少地分包链路层的交互次数也减少吞吐率会有非常明显的提升。用一组数字给你直观感受一下基于2M PHY、连接间隔30ms的估算配置组合理论最大有效吞吐率实际参考值默认MTU 23 无DLE约10-20 kbps更低可能不到10kbpsMTU 247 无DLE约50-80 kbps提升有限受单包限制MTU 247 DLE 251 2M PHY约300-500 kbps实测有到200kbps以上的案例看到没单改MTU收益有限只有把MTU、DLE、连接间隔三者同时调到位吞吐率才会上一个台阶。2.4 PHY速率模式从“1车道”换到“2车道”甚至“4车道”BLE的物理层速率也就是PHY参数决定了传送带本身的线速度。BLE 4.2及以前的设备只有1M PHY1Mbps物理速率BLE 5.0之后新增了2M PHY物理速率翻倍到2Mbps和Coded PHY长距离模式以降低速率为代价换取更远的通信距离。想调速率优先选2M PHY这是“免费的午餐”——物理层速率翻倍对功耗和距离的影响几乎可以忽略。需要注意的是2M PHY不仅要求从机支持手机这边也得支持。iPhone 8及以上的机型基本都支持2M PHY安卓这边近几年的中高端芯片平台基本没问题但一些低端平板还是只有1M。Coded PHY是反方向——为了距离牺牲速率数据速率只有2M PHY的四分之一甚至更低调优场景下一般不会主动选它。2.5 这几层参数的关系用一句话总结连接间隔决定传送带多久转一次MTU和DLE决定一次能搬多少货PHY决定传送带本身跑多快。三者是乘法关系任何一个卡住整体速率就上不去。再打个比方运输货物传送带转速快连接间隔小但一次只能放一个箱子MTU小箱子本身还只能塞一点东西无DLE那整体运输效率还是拉不起来反过来一次能塞一卡车货MTU大、DLE长但传送带半天转一次连接间隔大同样白搭。对了还有一个隐藏参数值得提一下——从机延迟Slave Latency。它允许从机跳过某些连接事件目的在于省电但对速率是负作用从机延迟越大实际有效连接间隔越大吞吐率越低。如果目标是调速率而不是省电这个参数必须设为0。3. 实操ESP32上做一轮完整的透传速率调优3.1 从哪开始调先看协议栈支持什么我用ESP32举例主要是因为ESP-IDF对BLE的封装比较完整各种参数都能通过API控制而且网上资料多、实验板子便宜。不管你是用nRF52832、DA14531还是其他芯片思路都是一样的只是API名不同。在动手之前先用下面这段代码初始化协议栈并确认几个关键能力#include esp_bt.h #include esp_gap_ble_api.h #include esp_gatts_api.h static void example_ble_ini(void) { esp_bt_controller_config_t bt_cfg BT_CONTROLLER_INIT_CONFIG_DEFAULT(); bt_cfg.bt_max_sync_conn 3; bt_cfg.bt_scan_duplicate_mode SCAN_DUP_TYPE_DISABLE; esp_bt_controller_init(bt_cfg); esp_bt_controller_enable(ESP_BT_MODE_BTDM); esp_bluedroid_init(); esp_bluedroid_enable(); esp_ble_gatt_set_local_mtu(517); // 有的SDK可以设到更大 }这里有几个关键点esp_ble_gatt_set_local_mtu(517)是设置本端允许的最大MTU。实际协商结果是主从两边取较小值所以两端都得支持大MTU才行。是否支持DLE不用手动API开ESP-IDF默认就是开的但需要确认你的ESP-IDF版本够新旧版本的协议栈在某些模式下可能没有启用2M PHY。3.2 设置连接参数BLE的大多数连接参数是主机侧发起的但作为从机你可以在广播里带一个“Peripheral Preferred Connection Parameters”字段告诉手机“我希望用多大的连接间隔”。或者你可以在收到连接事件后主动发送“Connection Parameter Update Request”。ESP32上推荐用后者因为广播里带的参数只是建议很多手机不理会。代码如下static void update_conn_params(uint16_t interval_min, uint16_t interval_max, uint16_t latency, uint16_t timeout) { esp_ble_gap_update_conn_params( interval_min, // 连接间隔最小值单位1.25ms interval_max, // 连接间隔最大值 latency, // 从机延迟 timeout // 超时时间单位10ms ); } // 示例7.5ms * 2 15ms 连接间隔 update_conn_params(12, 12, 0, 600);注意一个细节连接间隔参数的单位是1.25ms。想设成7.5ms就写6设成15ms就写12设成30ms就写24。我一般会把min和max设为一样原因很简单如果你给的是区间iOS大概率会按某个中间值来Android上各家也可能取不同的值。直接设成一个固定值行为最可控。3.3 协商MTU和数据长度扩展从机侧收到手机发来的MTU交换请求时要把允许的最大长度设置好static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_MTU_EVT: // MTU协商完成实际值在 param-mtu 里 ESP_LOGI(TAG, MTU negotiated: %d, param-mtu); break; default: break; } }手机端主动发起MTU协商的话从机这边基本上是自动响应的。但如果你用的是nRF Connect或者自己写的工具记得做一步“Request MTU”。DLE这块在ESP32上有个容易踩坑的地方——双模BR/EDRBLE模式下的控制器资源分配会影响2M PHY和DLE是否同时生效。你用默认的BT_CONTROLLER_INIT_CONFIG_DEFAULT()时BLE和经典蓝牙的模式是自动分配的。如果速率一直上不去可以先确认你的固件跑的是否是纯BLE模式esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);这行代码释放了经典蓝牙占用的内存给BLE留出更多控制器资源2M PHY和DLE在高负载下才更稳定。3.4 用GATT Notification持续发包来压测参数配置只是第一步接下来要用真实数据压测否则你看不到效果。我通常的做法是把GATT Server的某个Characteristic配置为Notify属性然后让Server端用“for循环”持续Notify一包数据手机端用nRF Connect或者自己写的App接收并统计吞吐量。ESP32端核心代码片段static void continuous_notify_task(void *param) { uint8_t data[244]; memset(data, A, sizeof(data)); while (1) { esp_ble_gatts_send_indicate( gatts_if, conn_id, char_handle, sizeof(data), data, false // false 表示Notifytrue 表示Indicate ); vTaskDelay(pdMS_TO_TICKS(5)); // 间隔5ms发包一次看实际能否跟上 } }这里有个容易忽略的点esp_ble_gatts_send_indicate其实是把应用层数据交给协议栈真正发送是在下一个连接事件里。这意味着你的应用层写的再快实际吞吐率还是由连接参数决定的。如果你发得比协议栈快协议栈内部会排队、丢新包否则操作失败。我见过不少同学在这里以为“代码越快速率越快”实际上完全不是这回事。测速时先用小连接间隔大MTUDLE2M PHY这套最优组合记录基准吞吐率然后逐个关掉其中一项对比数据就能直观看到每个参数对速率的贡献。3.5 调优后的实测数据参考拿我手上一块ESP32-WROOM-32E模组做测试手机用iPhone 13iOS 17系统和一台骁龙芯片的安卓机nRF Connect测吞吐率结果如下场景配置iPhone 13 实测安卓骁龙实测默认1M PHYMTU 247连接间隔30ms约42kbps约51kbps2M PHYMTU 247连接间隔30ms约82kbps约104kbps2M PHYMTU 247连接间隔15ms约148kbps约181kbps2M PHYMTU 247连接间隔7.5ms约196kbps约233kbps注意iPhone上的数值普遍比安卓低一点这不是硬件问题而是iOS的蓝牙协议栈在某些参数组合下会主动降级或做一些流量整形。做实际产品时要在iOS端单独验证一次。我另外试过在ESP32和另一个ESP32做双从机透传一个做Server、一个做Client中间的链路间隔设成7.5ms2M PHYDLE吞吐率能到280kbps左右。再往上我就没继续压了因为再快的场景已经超出BLE透传的合理使用范围了。4. 场景取舍调速率和低功耗打架时怎么办4.1 高速率还是长续航这是产品定义问题连接间隔越小、PHY速率越高、DLE越长吞吐率越高但代价是功耗变大。很多人一上来就套用“最优参数”结果设备半天就没电了。结合实际产品场景我一般这样取舍应用场景推荐配置原因运动传感器、姿态数据低频、小包1M PHY连接间隔30msMTU 247DLE开数据量小不需要极限速率省电优先音视频辅助数据、OTA升级、文件传输2M PHY连接间隔7.5msMTU 247DLE开临时大数据传输速率优先功耗可接受低功耗按钮、门磁、温湿度计1M PHY连接间隔100-200ms从机延迟可设较大值极少传数据续航是第一位如果你的产品既要低功耗又要偶尔高速率可以考虑“双模式”平时用低功耗连接参数在需要高速传输时由主机发起参数更新到高速模式传完再退回低功耗。4.2 一个现实问题Android手机不按你的参数来设置完连接参数回连后发现手机实际给的连接间隔跟你要的不一样太常见了。原因有几个Android 8.0以上有“BLE Connection Priority”机制App层可以设置CONNECTION_PRIORITY_HIGH、CONNECTION_PRIORITY_BALANCED、CONNECTION_PRIORITY_LOW_POWER系统会根据这个值覆盖设备侧配置。很多Android App根本没设HIGN默认就是BALANCED连接间隔可能被拉到十几甚至三十毫秒。系统还会根据射频环境、设备电量做自动调整。部分国产定制系统不点名了在锁屏或者后台时会进一步限制BLE。所以Android端速率上不去不一定是你的固件问题先让App把Connection Priority请求拉到HIGH。原生API大概长这样val bluetoothGatt ... bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)CONNECTION_PRIORITY_HIGH会请求7.5ms的连接间隔但最终结果仍由系统决定。4.3 iOS端的几个特殊限制iPhone这边iOS对连接参数有一套严格的验收规则如果你的请求不满足它的约束它不会报错而会静默选择一个它认为合适的值。典型约束包括连接间隔乘以从机延迟1必须小于等于某个值且结果不能超过超时时间的一半连接间隔不得小于15ms部分iOS版本实测需要大于等于15ms7.5ms请求会被忽略PHY 2M在iOS 11之后支持但老机型不配合。所以如果你做的是需要iOS和Android同时跑高速率的通用型外设我建议折中方案连接间隔15ms而不是7.5ms。在Android上损失一点上限但换来iOS上的兼容性和稳定性。我个人实测过iOS 17上对7.5ms连接间隔的处理时好时坏有时候能协商上有时候协商不上。做量产产品如果用7.5ms必须在多个iOS版本和大版本机型上反复验证否则到了交付阶段才发现问题就尴尬了。4.4 别忘了“连接事件”内的有效时间还有一个大家不太关注的细节在一个连接事件里主从双方不是只能收发一次而是可以连续交互多次直到达到一个最大时间限制。这个限制由链路层的TxWindow等参数决定。很多协议栈默认给的时间窗口并不大。假设连接间隔7.5ms但每个连接事件里只能交互一次那实际数据窗口只有几百微秒大部分时间还是在等下一个连接事件。如果你的连接间隔已经调到很小速率还是没有线性上涨就要怀疑是不是单事件交互次数被限制了。部分协议栈允许通过配置增加每次连接事件内的数据包数量能力上等同于提升了窗口利用率。不过ESP32的默认配置一般够用这块不一定需要你手动调。5. 常见问题与排查技巧实录5.1 连接不稳定传一会就断开这个问题在我调速率时碰到很多次尤其是把连接间隔拉到7.5ms之后手机端经常过几十秒就断开或者出现大量重传。排查步骤先看超时参数连接超时时间设了多少如果太短在密集传输时有几包没来得及应答就可能触发超时断开。建议超时时间至少为连接间隔的6倍以上。再看射频环境连接间隔7.5ms意味着设备必须非常频繁地唤醒射频稍微有点干扰就容易丢包。可以用手机自带的Systrace或者BLE抓包工具看一下实际的丢包和重传情况。最后看模块天线如果天线馈线过长、阻抗不匹配在高速率、高占空比下很容易出现误码表现就是连接参数看着正常但速率上不去、连接不稳定。这种情况跟参数没关系单纯是硬件问题。5.2 明明开了2M PHY协商之后还是1M排查顺序从机SDK是否支持2M PHYESP32有的旧版本IDF默认没开。手机的协议栈是否支持iPhone 8以下不支持2M PHY。通信双方是否有一方在广播里明确拒绝了2M有些蓝牙芯片在做兼容性设计时会主动排斥2M PHY的协商需要在初始化时显式打开。在ESP-IDF里确认本端支持情况esp_ble_gap_get_local_used_addr_and_irk(); // 或者在连接事件里主动读取PHY5.3 同样是NFC大小的数据包为什么MTU协商一直失败两边都说支持247字节MTU但真正协商时另一端死活不给大MTU。常见原因是对端的GATT Server没有实现Exchange MTU这个请求的处理或协议栈版本太老。有的Module把MTU请求当成“不支持的功能”直接忽略这时候只能看日志确认有没有收到MTU请求事件。还有一个细节MTU协商是双向的不是从机说247就247也不是主机说247就247协商结果是两边取min值。如果你用了一颗老款蓝牙透传模块主机只能到23那从机就算支持517也白搭。5.4 测试工具的选择影响判断用nRF Connect测速率时它收到Notify后默认会回ACK这个ACK走的是链路层的流控所以测出来的吞吐率比真实应用层可用速率略高。如果你用自己写的小工具测又可能因为TextView刷新、日志打印太多导致吞吐率虚低。我建议测速时关闭一切UI刷新和日志打印纯统计字节数定时打印一行平均速率就行。如果两套工具测出来的数值差异超过30%优先怀疑测试方法而不是协议栈。5.5 低功耗模式下“适度打开BLE”导致丢包之前有热点词提到“esp32轻度睡眠打开BLE”这里提一嘴。ESP32在Light Sleep下BLE连接是能保持的但唤醒时间、射频重新同步时间会占地一部分连接事件。如果连接间隔很小而模块还在频繁进睡眠可能导致连接事件还没来得及完成收发就进入睡眠表现为丢包、速率大幅下降。调优时把Light Sleep先关掉确认速率达标后再开启并测试连接间隔和睡眠周期的配合是否OK。一般来说连接间隔15ms以下就不建议Light Sleep了粗糙做个“高速率不睡、低速率睡”的状态切换比较合理。5.6 手机上“粘包”“断流”怎么排查双端都开了高MTU之后Android上偶尔会出现收到的数据块之间多出几个垃圾字节或者某一段时间收不到数据、突然又一下子收到一坨。大概率是接收端处理速度跟不上缓存溢出。Android系统层BLE队列是有限的你应用层读取不及时协议栈缓冲区被塞满之后要么丢包要么重排。解决办法是提高接收线程优先级、用独立线程批量处理数据不要在回调里做耗时的业务逻辑。iOS端相对好一些它底层有比较大的缓冲但代价就是数据到应用层有延迟实时性反而差一点。6. 一套可以直接抄的“高吞吐调优清单”把上面的经验浓缩成一份清单方便你拿到新项目时直接照着走确认芯片/协议栈版本支持2M PHY、DLE、大MTU不支持的话先升级SDK。广播数据里带上Preferred Connection Parameters建议minmax1215mslatency0。连接建立后从机主动发一次Connection Parameter Update把间隔固定到预期值。主从双方都开启MTU协商目标值至少247。发送大包数据时确保DLE已启用单包PDU更长减少链路层交互。Android App侧设置CONNECTION_PRIORITY_HIGH。iOS侧留意是否把间隔降级必要时接受15ms以上。压测时关闭UI刷新和日志统计纯字节数。分析数据时把连接间隔、MTU、PHY、DLE四个变量的贡献分开测。这套流程我跑了十几个项目从运动健康外设到工业数据采集通常都能在半天内定位到速率瓶颈。最大的感受是BLE透传速率调优80%的工作量不在调参本身而在正确理解参数之间的耦合关系以及排除协议栈和手机系统层的“隐形干预”。如果你已经按上面调了一遍还是不行下一步就要考虑是不是数据传输协议设计的问题——比如频繁分包、ACK机制太重、应用层重传次数过多。这些内容等有空我再单独写一篇这儿篇幅差不多了。另外分享一个我常备的调试小技巧把每次调参前后的实际连接参数连接间隔、MTU、PHY都打印出来搭配系统日志确认最后生效的值。很多时候你以为调了实际没生效你以为没调实际手机端早就按它自己的逻辑改掉了。参数“眼见为实”别靠猜。
返回列表