☰
BLE打流工具设计与吞吐拉满实录:从20Kbps到1296Kbps
2026/10/4 16:37:04 网站建设 项目流程

上一组文章讲了经典蓝牙 SPP 打流工具的设计和排障(吞吐从 83 修到 1208 Kbps),有朋友问:那 BLE 呢?BLE 的吞吐极限怎么看、怎么拉满?

这篇就讲这个。先给结论:同一份打流代码,BLE 上的吞吐可以从 20 Kbps 做到 1296 Kbps,相差 65 倍——决定权不在代码,在三个开关。三个都拨到位,BLE 能追平 BT SPP;有一个没开,数字直接砍到零头。

一、先说业务背景:为什么 BLE 要做吞吐测试

BLE 大部分时间传的是命令和状态这类小数据,极限吞吐用得少。真正吃吞吐上限的是三件事:OTA 固件升级、传图片、传音频。一次 OTA 传 500KB 镜像,700 Kbps 和 20 Kbps 的差别就是 6 秒和 3 分钟——用户握着手机等进度条的时候,这个差别非常具体。

但还有一半价值在测试评估侧。BLE 上没有标准的吞吐测试工具,原因值得说透:iperf 打流本质上是上层业务性质的测试,不依赖某个特定的底层通信协议,但它需要一个标准化的承载通道才可能成为通用工具。

WiFi 下有 UDP/TCP 这类标准传输层,iperf 才有得依附;经典蓝牙有 SPP 这个标准串口通道。而 BLE 连接建立之后,必须先创建 GATT 通道才能收发数据,标准协议里并没有规范一个专门用于打流的 GATT 服务和通道——每个厂家都得自己建 GATT 通道、自己定义打流协议。承载通道本身没有标准化,通用工具自然无从谈起。

行业现状也印证了这一点:大部分厂家根本没做过 BLE 饱和打流,即便做,也多是拿手机和设备对传一下——手机侧协议栈自己就有调度和缓冲限制,链路根本打不满。测到的是"能跑通",不是"链路极限"。所以大部分产品对自己的 BLE 真实极限吞吐是不清楚的。

所以打流工具只能自研,机制参考 WiFi 的 iperf:自建 GATT 通道,一端发送、一端按秒统计,跑满链路看真实上限。跑出来的数字顺便回答了另一个问题:BLE 协议栈的成熟度怎么样——满带打流能把调度、缓冲、连接稳定性层面的毛病全暴露出来,"命令能通"是看不出来的。

二、应用层先把该做的做满:244 字节包型的算术

打流代码本身很简单。先说一个方向上的细节:打流要选"不需要协议栈回复"的 GATT 接口。主机方向用 Write-No-Response(写完不等 ACK,栈才能连发);从机方向用 notify(主动上报,同样免响应)。如果主机用带响应的 Write、从机用 indication,每包都要等一次协议栈回执,吞吐直接被 ACK 往返拖垮——这和 SPP 篇里"流控交给链路层"是一个思路。

发送线程(主机方向,write no rsp)就是一个不带任何延时的忙发循环:

static uint32_t iperf_write_task(void *arg) { ble_iperf_data_t pkt = { .head = 0xAA55, .length = 240, .data = {0}}; write.handle = conn->write_no_rsp_handle; write.len = sizeof(pkt); /* 2 + 2 + 240 = 244 */ while (running) { ret = gatt_write_no_rsp(&write); if (ret != BLE_OK && ret != BLE_BUSY) break; } }

每包 244 字节整包交栈,返回 BUSY 当没发生继续塞,其余错误退出。没有 sleep、没有节流——什么时候等,交给协议栈的流控反馈,应用层不瞎掺和。这一点和之前 SPP 排障篇里"固定延时是节流不是流控"是同一个道理。

还有一个容易忽略的配置:忙发线程的优先级。它是个while(1)死循环,优先级定错了两头出问题——高于底层收发接口,它会抢占协议栈线程,底层来不及收发,吞吐反而掉甚至丢包;太低,又会被统计、打印这类耗时线程压住,发送空转。合理的位置是夹在中间:略低于底层收发、高于统计打印。这一条不看代码、只看配置表,跟主频一样属于"白送的吞吐"。

关键是 244 这个数怎么来的。三个参数咬合在一起:

MTU 协商到 247 → ATT PDU 数据区 = 247 - 3(ATT 头) = 244 字节 iperf 包 = head(2) + length(2) + data(240) = 244 字节 → 恰好填满一个 ATT PDU,无分片、无 padding

每包如果超过 MTU-3,一包会被拆成多个 ATT 传输,开销翻倍;小于则有 padding 浪费。MTU 247、数据 240、包头 4,加起来正好压线,这是一包一个 ATT PDU 的最优解。

不过吞吐公式的大头不在这。在连接事件:

吞吐 ≈ 单个连接事件能传的 LL 字节数 ÷ 连接间隔

测试固定连接间隔 100ms,每秒只有 10 个连接事件。如果每个事件只能发 1 个 27 字节的 LL 包(DLE 未开、Data Length 还是默认值),算出来才 2 Kbps 量级。要拉满,必须让每个连接事件内连续塞多个 LL 包——这就靠两个底层机制:

  • DLE(Data Length Extension):把单个 LL PDU 从默认 27 字节扩到 251 字节,发送时间片扩到 2120μs。扩完之后单个连接事件能装的数据量才够看。
  • more data bit:链路层发完一包,如果上层还有数据待发,就把包头的 MD 位置 1,告诉对方"我这还有",连接事件继续拉数据,直到时间片用完。100ms 这种稀疏间隔下,吞吐天花板完全由"单个连接事件能塞下多少字节"决定,而塞不塞得满,就看 MD 这一位。

所以应用层的配置清单是:MTU 247、DLE 开满(251 字节 / 2120μs)、每包 244 对齐 MTU、写用 Write-No-Response 免 ACK、发送循环零节流。该做的全做了,数字是多少?

三、第一个开关:PHY,1M 到 2M

实测第一轮,1M PHY:

0-1 sec avg tp 743 Kbps, rssi -23 1-2 sec avg tp 743 Kbps, rssi -24 2-3 sec avg tp 743 Kbps, rssi -22 3-4 sec avg tp 743 Kbps, rssi -22 4-5 sec avg tp 743 Kbps, rssi -22

743 Kbps,逐秒恒定,RSSI 很近、链路很干净——这不是"有问题",这就是 1M PHY 的满带。

BLE 默认跑 LE 1M PHY,标称 1 Mbit/s 原始码率,但前导、访问地址、包头、CRC 这些固定开销,加上连接事件时间片的空转,实际应用层载荷封顶就在 700-750 Kbps 这档。应用层配得再满也破不掉物理层的顶。这里要区分一下:SPP 那篇里"逐秒恒定的低"是代码节流的指纹,而这次恒定的 743 是物理上限的指纹——同样恒定,一个在代码里,一个在空口上,判断依据是数字靠不靠近理论上限。

要突破,只有把 PHY 从 1M 协商到 2M(LE 2M PHY,BLE 5 的特性,需要双方都支持,走 PHY update 流程)。这是吞吐能否翻倍的先决条件,也是最容易被忽略的一项——很多团队调参数调半天,从来没确认过自己实际跑在几 M 的 PHY 上。

协商到 2M 之后:

10-11 sec avg tp 1296 Kbps, rssi -24 11-12 sec avg tp 1296 Kbps, rssi -24 12-13 sec avg tp 1296 Kbps, rssi -23 13-14 sec avg tp 1296 Kbps, rssi -22

1296 Kbps,1.75 倍,基本追平了 BT SPP 优化后的 1208 Kbps。经典蓝牙和 BLE,维度对齐打满之后是一个量级的。

四、另外两个开关:都是踩过的坑

2M PHY 是最后一个拨上的开关,之前还有两个坑各砍掉过一大截吞吐。

开关二:CPU 主频

同一颗 CPU,有两种主频模式:默认低功耗跑 32 MHz,不休眠可跑 128 MHz。同一份打流代码,两种主频下:

  • 32 MHz:~200 Kbps
  • 128 MHz:~500 Kbps

差 2.5 倍。原因不复杂:满带打流时协议栈收发、LL 包处理、数据搬运全是 CPU 活,主频不够,连接事件的时间片内处理不过来,链路再好也喂不满。打流前先确认 CPU 跑在哪个档,这一条不看代码、只看配置,却经常被漏掉。

开关三:串口打印的实现方式

这条最狠。调试打流时顺手加了几条打印,吞吐直接从 ~700 Kbps 掉到~20 Kbps,35 倍。

罪魁是打印的实现方式:这个平台的 printf 是"关全局中断 + 忙等发完一整行"的阻塞式。发一行打印的功夫,整个系统是冻结的——协议栈、定时器、调度全部停摆,不只是打流线程被拖慢。打流热路径上每打一次,链路就断一截。

所以关于打印有三个层次的对策:

  1. 少打:逐包打印全删,收敛到每秒一条统计(观测密度的下限);
  2. 快打:串口波特率能上 2M 就上 2M,缩短阻塞时长;
  3. 换实现:确认平台的打印是不是"关中断+忙等"型——若是,热路径上要么换非阻塞异步打印(入队后台发),要么一根都不碰。

第 3 条是最容易被漏的:大家习惯性以为"加条打印没关系",但在这种阻塞实现上,打印不是观测,是把吸管插进吞吐里。

五、三个开关拨完的完整链路

状态吞吐卡在哪
阻塞打印在热路径 + 32MHz + 1M PHY~20 Kbps每行打印冻结整个系统
32MHz + 1M PHY(打印收敛后)~200 KbpsCPU 处理不过来连接事件
128MHz + 1M PHY~743 Kbps1M PHY 物理上限
128MHz + 2M PHY~1296 Kbps链路满带,与 BT SPP 持平

回头看,这三个开关没有一个是"BLE 知识":PHY 是配置意识、主频是系统意识、打印是工程习惯。和 SPP 那三个坑一样,全是普通工程问题,只是都长在性能路径上。

排障时对着这张症状表查:

症状最可能的位置先查什么
吞吐离谱地低(几十 Kbps 级)打印实现是否"关中断+忙等"阻塞串口、逐包打印
吞吐卡在 200 Kbps 档上不去CPU 配置实际主频档位
吞吐卡在 700-750 Kbps 恒定PHY当前连接实际 PHY 是 1M 还是 2M、对端是否支持 2M
吞吐离 251 字节包模型算的数差好几倍链路层配置DLE 是否真的开到 251、MD 是否在单事件连发

六、验收清单

BLE 吞吐性能要立得住,测试前过一遍:

  • 链路侧:当前连接实际 PHY(不是"应该协商到了",要实测确认)、DLE 已扩到 251 且对端同步开启、MTU 247 双端一致、连接间隔确认是预期值(别信参数换算,看连接更新回读值)
  • 系统侧:CPU 主频档位确认、打流线程优先级夹在"低于底层收发、高于统计打印"中间
  • 观测侧:打印收敛到每秒一条、波特率 2M、确认打印实现非阻塞型
  • 数据侧:逐秒统计窗口的均值+方差,多轮取稳态,RSSI 仅作参考(以综测仪为准)

这些开关都拨到位之后,BLE 和 BT SPP 的满带吞吐就在同一量级了——"BLE 天生慢"是个误会,它只是默认配置低。


上一篇讲了打流工具的设计(分层、双向角色、990B 包型),第二篇讲了 SPP 排障(83→1208 Kbps),这篇是 BLE 侧的拉满实录(20→1296 Kbps)。三篇合起来是一个完整的故事:测试工具值得设计,测试结果值得较真。

有用的话点个在看,让更多工程师看到。你测 BLE 吞吐时踩过哪个开关?评论区聊聊。


系列文章

  • 蓝牙SPP打流工具设计篇
  • 蓝牙SPP排障篇:83→1208 Kbps

标签:BLE 低功耗蓝牙 吞吐优化 2M PHY 嵌入式

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询