ESP32-S3 SPI: ~100 SPI transactions per packet cap TCP at ~4 Mbit/s

Hardware: XIAO ESP32-S3 + Wio-WM6180 (MM6108/FGH100M-H), SPI at 40 MHz (confirmed), morsemicro/halow 2.11.2-esp32-2, ESP-ID 5.4.4. AP: Raspberry Pi 4 on Morse OpenWrt 3.0.2, 8 MHz channel, US, MCS 7 both ways at −25 dBm.

TCP tops out at about 4 Mbit/s against an air rate of 32.5 Mbit/s. Your iperf example measures the same on this hardware, so it isn’t application code. The AP is idle (95%+), and TCP window size makes almost no difference (16 KB vs 32 KB vs 50 KB), while
CPU clock does (160 → 240 MHz gave +34%), so it looked CPU/latency-bound. Profiling during a 5 MB TCP upload (about 3,600 packets):

  • drv task: 56% of one core; lwIP tiT: 6%; over a core idle.
  • SPI accounts for 88% of transfer wall time.
  • Per packet: 89.9 calls to mmhal_wlan_spi_rw (one byte each), about 10 writes of 2–64 bytes, and 3 block writes of 512 bytes (the payload).
  • Each ESP-IDF SPI transaction costs 18–22 µs regardless of size (about 4,500 cycles at 240 MHz); a byte on the wire takes 0.2µs. So roughly 93% of SPI time is protocol chatter, not payload.
  • sdio_spi.c sends CMD53 command bytes individually and polls for response tokens one byte per transaction via morse_receive_spi().

Two findings that may help others on ESP-IDF hosts:

  • Calling spi_device_acquire_bus() once and holding it (nothing else is on the bus) cut per-byte cost from 20.9 to 17.5 µs, about +10% throughput. Could the shim do this by default?
  • The remaining cost is ESP-IDF’s per-transaction overhead, so batching would help most: could the token-polling loop read 8–16 bytes at a time and scan the result, and could command bytes go out in one transfer?

Questions: is about 4 Mbit/s expected for the ESP32-S3 SPI port? Is a batched SPI path planned or available? Is SDIO supported on ESP32-S3 hosts as an alternative?

Yes AI written but seems to really be a problem in the driver. Thanks!

Hi @jone951

Currently the lower SPI throughput you’ve measured is expected. We do plan on improving this performance in the way you have described in upcoming versions but our priority was ensuring the port remains portable across all targets.

SDIO isn’t currently supported, but it also wouldn’t be hard to add.

Hi @jone951
I’ve been chipping away at optimising throughput on an ESP for a while, i’ve built an easy to use embedded wifi 4 <> Halow router and reasonable throughput has been essential for my customers (TX-E if your interested).

When connected to a Halowlink1 I’m currently benchmarking: TCP upload - 6.5mbps, download - 9.5mbps.
Halow TX is the key bottleneck and if there is a way to do batching that would really help address the issue.

I imagine that your next optimisation step would be to leverage IRAM within the driver, it is a fairly straightforward change which from memory was a reasonable boost.