先说结论,省得你读到最后才发现这篇没有奇迹:我把延迟从 100 ms 压到了 12 ms, 但没能压到 5 ms 以下。剩下的那部分,我至今不知道是什么,我把已知的全部写在这里。

事情起因于一个很具体的数字。我的 ESP32 通过 micro-ROS 往 Agent 发 sensor_msgs/LaserScan, 实测可靠吞吐只有约 1 Hz。雷达本身是 5 Hz 的,也就是说有 80% 的数据在路上丢了。 查吞吐之前,我先查了最底层的东西:

agent$ ping 192.168.1.11 · 100 包
100 packets transmitted, 100 received, 0% packet loss
    rtt min/avg/max/mdev = 11.402/98.713/341.228/62.019 ms

零丢包,平均 98.7 ms,最大 341 ms,标准差 62 ms。这三个数字里,最要命的不是平均值,是标准差。 平均 100 ms 的链路勉强能干活;抖动 62 ms 的链路没法干活,因为你不敢设超时。

01先分清:是 Wi-Fi 还是 ROS?

这是第一个岔路口。我用一个很土但有效的办法:跑纯 ping 的同时跑 ROS 话题,看谁先崩。

  • 不跑 ROS,只 ping:平均 96 ms。
  • 跑 ROS,同时 ping:平均 101 ms,话题频率 1 Hz。
  • 只跑 ROS,用 ros2 topic hz 看:1.02 Hz。

ping 基本没变化,说明 DDS 不是延迟的制造者,它只是受害者—— eProsima 默认的可靠传输在乱序和抖动面前会疯狂重传,重传又制造更多抖动,于是吞吐塌方。

把问题从"A 和 B 之间有问题"缩小到"A 和 B 之间的链路本身有问题",这一步省了我至少半天。 很多人一上来就调 DDS 的 QoS 参数,那是给漏水的管子缠胶带。

02iperf:吞吐其实不差

延迟高,通常伴随吞吐低。但测出来不是这样:

iperf3 · TCP · 60 秒
[ ID] Interval           Transfer     Bitrate         Retr
    [  5] 0.00-60.00 sec     412 MBytes   57.6 Mbits/sec   189   sender
    [  5] 0.00-60.00 sec     411 MBytes   57.4 Mbits/sec          receiver

57 Mbps,对 2.4 GHz 单流来说是很正常的成绩。189 次重传不算多。 吞吐正常、延迟爆炸,这种组合几乎总是指向缓冲(buffer)—— 某个环节在排队,把延迟堆了起来。这就是所谓的 bufferbloat。

延迟分布 · 500 次 ping 10 50 90 130 170 210 250 290 330 ms 众数 ≈ 65 ms
图 01 — 延迟不是均匀分布,主峰在 60–90 ms,另有一条拖到 330 ms 的长尾。长尾才是致命的部分。

03三个嫌疑,逐个排除

嫌疑一:信道拥塞

扫下来 2.4 GHz 一共 13 个网络,1、6、11 三个互不重叠的信道全被占,我所在的是 6, 上面有 5 个 AP。这确实不好,但换个场景验证一下就知道分量:凌晨三点复测, 平均延迟从 98 ms 降到 71 ms。有改善,但不是全部。

嫌疑二:ESP32 的 Wi-Fi 省电

默认的 WIFI_PS_MIN_MODEM 会让 RF 周期性休眠,代价就是延迟和抖动。 我改成 WIFI_PS_NONE

firmware · 改后复测
// esp_wifi_set_ps(WIFI_PS_NONE);  // 关掉 modem sleep
    100 packets transmitted, 100 received
    rtt min/avg/max/mdev = 6.118/43.206/188.440/31.507 ms

平均从 98 ms 掉到 43 ms。这是单一改动里收益最大的一步, 代价是功耗——电池供电的项目要自己权衡。

嫌疑三:Agent 侧的 USB 无线网卡

我的 Agent 主机用的是一块 USB 无线网卡。把它拔掉,改接有线到同一个路由器:

改有线后 · ESP32 仍走 Wi-Fi
rtt min/avg/max/mdev = 4.902/12.884/51.306/9.774 ms

12.9 ms。到这里,1 Hz 的吞吐涨到了稳定 5 Hz——正好是雷达的物理频率, 也就是说链路不再是瓶颈了。

原来 100 ms 里,有大约 55 ms 花在 ESP32 的 modem sleep 上, 有大约 30 ms 花在 Agent 那块 USB 网卡和它的驱动上,剩下的才是空气里本来该有的时间。

04剩下那部分,我没解决

mdev 还有 9.8 ms,最大 51 ms。对大多数应用够用了,但对我要做的实时控制链路不够—— 舵机闭环时,51 ms 的单次抖动意味着一次明显的过冲。

我试过但没用的几件事,一并记下来,省得你再走一遍:

  • RMW 的 QoS:把 reliability 从 RELIABLE 改成 BEST_EFFORT,吞吐没再涨(本来就已经到物理频率上限了)。
  • 改 MTU:从 1500 降到 1200,无变化。局域网不分片,这不是问题所在。
  • 绑定信道带宽 20 MHz / 40 MHz:40 MHz 吞吐更好但抖动更差,最后选了 20 MHz。
  • 把路由器换成另一个品牌:平均降到 9 ms,但 mdev 依然有 7 ms。说明空气里的时间不是零, 2.4 GHz 的 CSMA/CA 天生就有 contention 开销。

所以这篇的结尾是开放的:我能把 Wi-Fi 上跑 ROS 2 做到"够用",但做不到"确定"。 如果你的项目对延迟有硬要求,我的建议很朴素——能走线就走线。 我最后的控制链路改成了串口,把 Wi-Fi 留给那些不关心 50 ms 的数据。

工具本身没有错,错的是把它用在它不擅长的地方。这句话我大概每年都要重新学一次。

2026.09.02 补充
有读者问为什么不上 5 GHz。ESP32-WROOM 只支持 2.4 GHz,这个型号没得选;ESP32-C5 / S3 的部分型号才支持 5 GHz。
2026.08.28 修订
初稿把 43 ms 写成"最终值",那是只改了省电模式的中间结果。加上有线对比后应为 12.9 ms,已更正。