ESP32-Based Wireless Communication Link Latency Test Results
English | 中文
By RichJ233
The tested solutions include:
- ESP32-S3 UART ↔ Wi-Fi UDP
- ESP32-S3 ↔ ESP32-C3 ESP-NOW
- ESP32-S3 native USB local loopback
- Dual ESP32-S3 ESP-NOW
- A pair of nRF24-like wireless UART transparent-transmission modules
Hardware
| Device | Purpose | Notes |
|---|---|---|
| ESP32-S3 development board | Wi-Fi UDP, ESP-NOW, native USB, and dual-S3 tests | Taobao item 669443108979 |
| ESP32-C3 development board | Early S3↔C3 ESP-NOW tests | Tmall item 730121383834 |
| Paired wireless UART transparent-transmission modules | 115200 8N1 UART transparent-transmission tests | Taobao item 618869877874 |
| Test 11 wireless UART transparent-transmission module | 115200 8N1 UART transparent-transmission test | Taobao item 563716927856 |
| Windows PC | Runs the Python test programs | Host-side timing uses time.perf_counter_ns |
Experiments
| Name | Timing scope |
|---|---|
| One-way host-visible latency | From the PC writing data to one endpoint until the PC receives the complete data at the other endpoint |
| Radio RTT | Wireless A→B→A round trip measured inside the MCU; excludes the PC, Python, and USB |
| Host RTT | From Python sending a request until Python receives the matching response |
| Complete closed-loop latency | Total time for state uplink, PC computation, and command downlink |
| Delivery rate | Percentage of data eventually received successfully |
| Deadline hit rate | Whether data arrives within the target period |
1. ESP32-S3 UART ↔ Wi-Fi (UDP) ↔ PC
Test Path
Serial device/PC
→ UART
→ ESP32-S3
→ Wi-Fi UDP
→ PC
Configuration:
- UART RX: GPIO18
- UART TX: GPIO17
- UART baud rate: 115200
- UDP port: 2333
- PC Wi-Fi IP: 192.168.0.233
- ESP32 IP: 192.168.0.2
- PC serial port: COM4
- Wi-Fi router: TP-Link 5626
- Data sent: 32 bytes
Results
Bidirectional transparent transmission worked reliably, with a latency of approximately 29 ms. The reverse UDP→ESP32-S3→UART path produced a similar result.
2. ESP32-S3 ↔ ESP32-C3 ESP-NOW One-Way Test
Test Method
The program alternated between the two directions:
S3 → C3
C3 → S3
- ESP-NOW channel: 6
- Wi-Fi power saving disabled
- The two directions were not transmitted continuously at the same time.
Results
- S3→C3: typically about 3.3–3.5 ms
- C3→S3: typically about 3.1–7 ms
- TIMEOUT events occurred during the test
S3↔C3 ESP-NOW stop-and-wait RTT:
| Metric | Result |
|---|---|
| Valid | 1000/1000 |
| Median | 4.374 ms |
| P95 | 10.910 ms |
| Maximum | 25.573 ms |
3. ESP32-S3 ↔ ESP32-C3 Bidirectional Communication Test
Test Path
→ C3 UART
→ ESP-NOW
→ S3 native USB
→ PC
→ S3 native USB
→ ESP-NOW
→ C3 UART
All later tests used fixed 32-byte binary frames containing a frame type, sequence number, and CRC16.
100 Hz Results
The target period was 10 ms, with 1000 measured samples.
| Metric | State uplink | Command downlink | Complete round trip |
|---|---|---|---|
| Valid | 996/1000 | 961/1000 | 961/1000 |
| Mean | 8.989 ms | 43.291 ms | 51.985 ms |
| Median | 5.937 ms | 29.801 ms | 38.834 ms |
| P90 | 22.034 ms | 100.828 ms | 108.076 ms |
| P95 | 25.079 ms | 132.754 ms | 137.270 ms |
| P99 | 33.683 ms | 174.612 ms | 179.279 ms |
| Maximum | 57.814 ms | 190.135 ms | 200.578 ms |
| ≤10 ms / planned | 69.80% | 22.30% | 7.70% |
Comparison of 50, 75, and 100 Hz Results
| Target frequency | Period | Complete median | Complete mean | Complete P95 | Deadline hit |
|---|---|---|---|---|---|
| 50 Hz | 20 ms | 26.140 ms | 32.040 ms | 74.988 ms | 39.30% |
| 75 Hz | 13.333 ms | 148.256 ms | 314.213 ms | 1125.351 ms | 2.30% |
| 100 Hz | 10 ms | 38.834 ms | 51.985 ms | 137.270 ms | 7.70% |
The 75 Hz test produced many ESP_ERR_ESPNOW_NO_MEM, pending overwrite, and retry events. The results may be affected by the ESP-NOW queue and transmission state.
Active + Latest Test
When newer data arrives, the old waiting packet is replaced to prevent stale data from continuously accumulating.
One submitted active packet
+
One waiting latest packet
Results at 100 Hz with 2000 samples:
| Metric | State uplink | Command downlink | Complete round trip |
|---|---|---|---|
| Valid | 1891/2000 | 1436/2000 | 1436/2000 |
| Median | 6.400 ms | 5.414 ms | 14.635 ms |
| Mean | 9.839 ms | 9.654 ms | 17.722 ms |
| P95 | 26.359 ms | 29.141 ms | 43.264 ms |
| ≤10 ms / planned | — | — | 24.10% |
The board-side submit count matched the peer-side radio receive count.
4. ESP32-S3 Native USB Local Loopback Latency Test
Test Path
→ PC
→ S3 native USB
→ Echo
→ S3 native USB
→ PC
Results
| Metric | Result |
|---|---|
| Valid | 1000/1000 |
| Mean | 0.529 ms |
| Median | 0.501 ms |
| P95 | 0.660 ms |
| P99 | 0.934 ms |
| Maximum | 1.397 ms |
5. Dual ESP32-S3 ESP-NOW RTT Test
Test Path
→ S3-A firmware
→ ESP-NOW PING
→ S3-B firmware
→ ESP-NOW PONG
→ S3-A firmware
Timing was measured on the boards using esp_timer_get_time and excludes Windows, Python, and USB.
Results
| Metric | Result |
|---|---|
| Valid | 2000/2000 |
| Minimum | 2.973 ms |
| Median | 3.602 ms |
| Mean | 4.442 ms |
| P90 | 6.936 ms |
| P95 | 8.476 ms |
| P99 | 12.363 ms |
| Maximum | 23.785 ms |
| ≤10 ms / planned | 97.30% |
Board-side statistics:
- A: 2100 transmissions, 0 timeouts, 0 send errors
- B: 2100 receptions, 2100 replies, 0 send errors
6. Dual-S3 Host-Triggered RTT Test
Test Path
→ Python
→ USB
→ S3-A
→ ESP-NOW
→ S3-B
→ ESP-NOW
→ S3-A
→ USB
→ Python
Results
| Metric | Internal radio RTT | Complete host RTT |
|---|---|---|
| Valid | 1714/2000 | 1714/2000 |
| Median | 3.606 ms | 4.774 ms |
| Mean | 4.927 ms | 6.029 ms |
| P95 | 11.925 ms | 13.028 ms |
| P99 | 18.070 ms | 19.223 ms |
| Maximum | 41.194 ms | 42.363 ms |
7. Dual-S3 Single-USB Stop-and-Wait Test
Test Path
→ Python
→ USB A
→ S3-A
→ ESP-NOW
→ S3-B
→ ESP-NOW
→ S3-A
→ USB A
→ Python
S3-B's USB connection was not included in the timed path.
Only one transaction was allowed in flight at a time. The test recorded the complete host RTT, internal ESP-NOW RTT, and host+USB overhead.
Results
| Metric | Run 1 | Run 2 |
|---|---|---|
| Valid / planned | 2436/3000 | 2470/3000 |
| Complete median | 4.515 ms | 4.342 ms |
| Complete mean | 5.866 ms | 5.697 ms |
| Complete P95 | 13.070 ms | 12.728 ms |
| Complete P99 | 18.406 ms | 16.814 ms |
| Complete maximum | 30.694 ms | 43.885 ms |
| Radio median | 3.743 ms | 3.541 ms |
| Radio mean | 5.099 ms | 4.941 ms |
| Host+USB median | 0.709 ms | 0.712 ms |
| Host+USB P95 | 1.018 ms | 1.041 ms |
The board-side statistics for both tests recorded 3100 complete wireless round trips:
- A submitted 3100 packets and B received 3100 packets
- B replied 3100 times and A received 3100 replies
- No radio queue drops
- No NO_MEM events
- No callback failures
- No loop timeouts
- Python recorded 589 and 547 host timeouts
8. Dual-S3 Asynchronous Bidirectional Communication Test
Test Method
- State data was transmitted continuously at 100 Hz without waiting for the previous command to return
- Python used two independent reader threads to continuously read both USB ports
- A command was calculated and sent immediately after receiving state data
- The controller processed only the latest state in each received batch
Results
| Metric | State uplink | Command downlink | Complete round trip |
|---|---|---|---|
| Valid / planned | 2996/3000 | 2482/3000 | 2482/3000 |
| Minimum | 1.948 ms | 1.955 ms | 4.047 ms |
| Median | 3.671 ms | 3.426 ms | 11.894 ms |
| Mean | 8.346 ms | 8.838 ms | 14.328 ms |
| P90 | 21.906 ms | 25.891 ms | 29.685 ms |
| P95 | 22.700 ms | 32.874 ms | 36.668 ms |
| P99 | 28.675 ms | 42.358 ms | 47.351 ms |
| Maximum | 51.027 ms | 62.686 ms | 64.884 ms |
| ≤10 ms / planned | 63.77% | 58.47% | 38.70% |
9. nRF24-Like Wireless UART Transparent Module: 12-Byte Sequential Round Trip
Serial Configuration and Frame Format
- 115200 baud
- 8 data bits
- No parity
- 1 stop bit
- 12-byte frame
A5 5A | uint32 sequence | PING | CRC16-CCITT
Test Path and Timing
The same PC was connected to two USB serial ports:
t0: PC writes to serial A
→ Module A
→ Wireless link
→ Module B
t1: PC receives the complete frame from serial B
t2: PC writes the validated frame back to serial B
→ Module B
→ Wireless link
→ Module A
t3: PC receives the returned frame from serial A
Calculations:
- A→B visible latency = t1 - t0
- PC forwarding = t2 - t1
- B→A visible latency = t3 - t2
- Full RTT = t3 - t0
Results
| Metric | A→B | PC forwarding | B→A | Full RTT |
|---|---|---|---|---|
| Mean | 8.465 ms | 0.014 ms | 8.585 ms | 17.063 ms |
| Median | 8.326 ms | 0.010 ms | 8.581 ms | 16.920 ms |
| P90 | 8.933 ms | 0.021 ms | 8.651 ms | 17.524 ms |
| P95 | 8.988 ms | 0.028 ms | 8.722 ms | 17.589 ms |
| P99 | 9.260 ms | 0.058 ms | 9.034 ms | 18.054 ms |
| Minimum | 7.910 ms | 0.005 ms | 7.980 ms | 16.494 ms |
| Maximum | 27.299 ms | 0.814 ms | 11.332 ms | 35.873 ms |
| Std | 0.510 ms | 0.025 ms | 0.127 ms | 0.529 ms |
Sample statistics:
- Planned: 3000
- Valid: 2973
- Failed: 27
- Success rate: 99.100%
For 12-byte data at 115200 8N1, the theoretical total transmission time across four UART legs is approximately 4.167 ms.
10. nRF24-Like Wireless UART Transparent Module: Simultaneous Bidirectional Transmission
Test Method
Two Python workers used threading.Barrier to start as close to simultaneously as possible:
Serial A sends frame A while serial B sends frame B
Both endpoints wait for data from the opposite side
Each frame was 12 bytes and contained a header, source, sequence number, payload, and CRC.
Results
Many communication failures occurred. No stable latency statistics or accurate failure rate were produced.
This result alone does not prove that the product is half-duplex by specification. It may also be caused by simultaneous-transmission collisions, contention, or a radio that cannot transmit and receive at the same time. However, the link is not suitable for designs that rely on simultaneous bidirectional transmission.
11. nRF24-Like Wireless UART Transparent Module: 10-Byte Python Echo RTT
Configuration
- Main port: COM9
- Echo port: COM10
- 115200 8N1
- 10-byte packet
- Warm-up: 50
- Formal measurements: 1000
- Send interval: 20 ms
- Packet timeout: 100 ms
Frame format:
AA 55 | uint32 sequence | uint16 data | CRC16-MODBUS
Test Path
Python main thread
→ COM9
→ Module A
→ Wireless link
→ Module B
→ COM10
→ Python echo thread writes the same data back to COM10
→ Module B
→ Wireless link
→ Module A
→ COM9
→ Python main thread
Results
| Metric | Result |
|---|---|
| Warm-up valid | 49/50 |
| Formal valid | 1000/1000 |
| Mean | 21.013 ms |
| Median | 21.578 ms |
| P90 | 22.024 ms |
| P95 | 22.124 ms |
| P99 | 22.391 ms |
| Minimum | 19.121 ms |
| Maximum | 24.892 ms |
| Std | 0.998 ms |
The actual test frequency was approximately 24.2 Hz.
Results Comparison
| Solution | Measurement scope | Main result | Samples / valid rate |
|---|---|---|---|
| Wi-Fi UART↔UDP | Bidirectional transparent transmission | Connected successfully, approximately 29 ms | — |
| S3↔C3 ESP-NOW | One-way host-visible | Typically about 3–7 ms, with TIMEOUT events | — |
| S3+C3 100 Hz communication | State→PC→command return | Mean 51.985 ms, P95 137.270 ms, ≤10 ms 7.70% | 961/1000 |
| Dual-S3 ESP-NOW | Board-side radio RTT | Mean 4.442 ms, median 3.602 ms, P99 12.363 ms | 2000/2000 |
| Dual-S3 single-USB run | PC→A→B→A→PC | Mean 5.697 ms, median 4.342 ms, P99 16.814 ms | 2470/3000 |
| Dual-S3 asynchronous communication | State→PC→command return | Median 11.894 ms, P99 47.351 ms, ≤10 ms 38.70% | 2482/3000 |
| Transparent modules, sequential round trip | A→B→PC forwarding→A | Mean 17.063 ms, P99 18.054 ms | 2973/3000 |
| Transparent modules, simultaneous bidirectional | Both endpoints transmit simultaneously | Many failures, no stable statistics | — |
| Test 11 transparent module echo | COM9→COM10 Python echo→COM9 | Mean 21.013 ms, P99 22.391 ms | 1000/1000 |