4、影响吞吐量的关键因素:PHY速率、连接间隔、PDU大小、MTU大小
吞吐量测试做到一定阶段,你会发现一个现象:明明两边设备都支持高速率,实际测出来的数据就是上不去。我刚开始做BLE吞吐优化时也遇到过这个问题,折腾了好几天,最后发现是几个基础参数没配对。
说白了,BLE的吞吐量不是由某一个参数决定的,而是由四个关键因素共同制约。就像水管出水,管径、水压、阀门开度、出水口大小,哪个环节卡住,流量都上不去。
核心公式(简化版):
吞吐量 ≈ (PDU有效载荷 × 每连接事件包数) / 连接间隔
这个公式虽然粗糙,但能帮你快速定位瓶颈在哪。
4.1 PHY速率:物理层的硬门槛
PHY(Physical Layer)是BLE的物理层,它决定了无线信号调制的速率。目前主流的有三种:1M PHY、2M PHY和LE Coded PHY。
| PHY 类型 | 理论速率 | 实际有效吞吐 | 适用场景 |
|---|---|---|---|
| 1M PHY | 1 Mbps | 约 300-500 kbps | 兼容性优先,旧设备 |
| 2M PHY | 2 Mbps | 约 700-1200 kbps | 高速数据传输 |
| LE Coded PHY (S=2) | 500 kbps | 约 100-200 kbps | 远距离、抗干扰 |
| LE Coded PHY (S=8) | 125 kbps | 约 30-80 kbps | 超远距离、穿墙 |
我个人习惯,只要设备支持,优先选2M PHY。为什么?因为它的速率翻倍,但功耗增加并不明显。我在一个穿戴设备项目里,从1M切到2M后,吞吐量直接翻了一番,电池续航只少了不到10%。
小技巧: 在nRF Connect或LightBlue里,连接后可以查看当前使用的PHY。如果显示的是1M,而你的设备支持2M,记得在连接参数里主动请求切换。
4.2 连接间隔:时间窗口的博弈
连接间隔(Connection Interval)是主机和从机之间交换数据的周期。单位是1.25ms的倍数,范围从7.5ms到4s。
你想想看,连接间隔越小,数据交换越频繁,吞吐量自然就高。但代价是什么?功耗也上去了。因为设备要更频繁地醒来收发数据。
我在一个医疗设备项目里遇到过这种情况:客户要求数据实时上传,但设备要用纽扣电池撑一个月。最后我们把连接间隔设在15ms,配合较大的PDU,才勉强平衡了吞吐和功耗。
| 连接间隔 | 每连接事件包数 | 理论吞吐(2M PHY) | 功耗 |
|---|---|---|---|
| 7.5 ms | 1-2 | ~1.2 Mbps | 高 |
| 15 ms | 3-4 | ~800 kbps | 中 |
| 30 ms | 5-6 | ~500 kbps | 低 |
| 50 ms | 6-8 | ~300 kbps | 极低 |
注意: 连接间隔不是越小越好。iOS和Android对连接间隔有最低限制,通常不能低于15ms。强行设置过小的间隔,可能导致连接不稳定甚至断开。
4.3 PDU大小:数据包的容量
PDU(Protocol Data Unit)是BLE协议栈里实际传输的数据单元。在BLE 4.2之前,PDU最大只有27字节。4.2之后引入了LE Data Length Extension(DLE),可以把PDU扩展到251字节。
这个提升是巨大的。我举个例子:
- 没有DLE:每次最多发27字节,其中还有3字节是L2CAP头,实际有效载荷只有24字节。
- 启用DLE:每次最多发251字节,L2CAP头还是3字节,有效载荷248字节。
算一下,同样是发100KB数据,没有DLE需要发约4200个包,启用DLE后只需要约410个包。包数量少了,空中碰撞的概率也低了,吞吐量自然就上去了。
关键点: DLE需要在连接建立后由主机或从机主动发起请求。不是所有协议栈都默认开启的,你得在代码里显式调用API。
我曾经在一个固件升级项目里,忘记开启DLE,结果升级一个200KB的固件花了将近3分钟。后来加上DLE,时间缩短到40秒。嗯,这个坑我踩过,所以现在每次做吞吐测试,第一件事就是检查DLE是否启用。
4.4 MTU大小:协议栈的缓冲区
MTU(Maximum Transmission Unit)是L2CAP层的最大数据单元。它和PDU大小是两回事,但很多人容易搞混。
简单来说:
- PDU大小:物理层一次能发多少数据(由DLE控制,最大251字节)。
- MTU大小:应用层一次能提交给协议栈多少数据(由ATT_MTU协商,最大517字节)。
MTU协商是在连接建立后,通过Exchange MTU Request/Response完成的。如果MTU设置得小,即使PDU再大,应用层也只能一次发一点点数据,协议栈会帮你分包。
举个例子:
// MTU协商示例(基于Zephyr)
// 发起MTU请求,建议设为247(兼容大多数设备)
bt_gatt_exchange_mtu(conn, NULL);
// 获取协商后的MTU值
uint16_t mtu = bt_gatt_get_mtu(conn);
printk("Negotiated MTU: %d\n", mtu);
我建议你在做吞吐测试时,先把MTU设到247或更高。为什么是247?因为这是大多数BLE芯片默认支持的最大值,再高的话,有些老设备可能不兼容。
| MTU 大小 | 单包有效载荷 | 适用场景 |
|---|---|---|
| 23(默认) | 20 字节 | 兼容性测试,低数据量 |
| 247 | 244 字节 | 大多数BLE设备,推荐 |
| 517 | 514 字节 | 高性能设备,需双方支持 |
避坑指南: 我曾经在调试时发现,MTU协商成功了,但实际发送数据还是被截断。后来查了半天,发现是应用层的发送缓冲区没调大。记住,MTU协商只是告诉协议栈“我能收这么大”,但你的发送代码也得配合。
4.5 四个因素的协同配置
这四个因素不是孤立的,它们互相影响。我画了一张图帮你理解它们的关系:
实际测试时,我建议你按这个顺序来排查:
- 先确认PHY:是不是2M?如果不是,能不能切?
- 再查MTU:协商后的MTU是多少?有没有被限制?
- 检查DLE:PDU大小是不是251?有些协议栈默认不开。
- 最后调连接间隔:在功耗允许范围内,尽量缩小间隔。
这四个因素都调对了,吞吐量基本不会差。我见过太多人只盯着一个参数调,结果折腾半天没效果。记住,短板效应在这里特别明显。