Files
desklock/firmware/components/esp_hosted/docs/spi_full_duplex.md
T
jpmschweitzerandClaude Fable 5 cb5826e02b
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped
Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Root cause (verified against our exact IDF tree, not the community guess):
the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout
(that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block-
mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line)
aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG
BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable
host sdio state" and reboots the whole P4. The audio TX payload is not
64-aligned, so streaming mic audio wedged on the very FIRST frame (which is
exactly what we saw: listening -> instant Failed to send -> reboot).

This also explains why buffer/queue/clock/retry tuning all did nothing: the
write never reached the bus. And why our symptom was instant, not after
~100 writes (the community block-mode-desync theory) — it is the first
misaligned buffer, every time.

Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides
the registry copy) and bounce a misaligned TX payload through one aligned
DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c).
TX is serialized by the bus lock so a single static bounce buffer is safe;
freed in hosted_sdio_deinit. Host-only change — no C6 reflash.

VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio
upstream streaming — the traffic that previously wedged on the first frame
— ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness
is kept (compiled out) for future SDIO stress testing.

Credit: root cause + patch designed via multi-agent investigation; the
precise 258=INVALID_ARG decode (correcting the upstream community timeout
assumption) came from checking our actual esp_err.h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:50:27 +02:00

26 KiB

ESP-Hosted SPI FD (Full Duplex) Operation

Sections 2 to 4 below covers the theoretical part where the SPI Full duplex design and implementation details are explained.

Section 5 to 9 covers the complete step-wise setup co-processor and host with SPI Full Duplex, using 2 or 4 data lines.

If you wish to skip the theory, you can refer the Quick Start Guide below. For quick navigation, please unfold the Table of Contents below.

Table of Contents
  1. Quick Start Guide

  2. Introduction

  3. High Level Design and Implementation || 3.1 Number of Pins Required || 3.2 SPI Full Duplex Mode Implementation || 3.3 Code Reference

  4. Hardware Considerations || 4.1 General Considerations || 4.2 Jumper Wires || 4.3 PCB Design || 4.4 Advanced Considerations

  5. Hardware Setup

  6. Set-Up ESP-IDF

  7. Flashing the Co-processor || 7.1 Create Co-processor Project || 7.2 Co-processor Config || 7.3 Co-processor Build || 7.4 Co-processor Flashing || 7.4.1 Serial Flashing (Initial Setup) || 7.4.2 Co-processor OTA Flashing (Subsequent Updates)

  8. Flashing the Host || 8.1 Select Example to Run in Hosted Mode || 8.2 Host Project Component Configuration || 8.3 Menuconfig, Build and Flash Host

  9. Testing and Troubleshooting

  10. References

1 Quick Start Guide

This section provides a brief overview of how to get started with ESP-Hosted using SPI FD mode. For detailed instructions on each step, please refer to the following sections:

These sections will guide you through the process of flashing both the co-processor and host devices, setting up the hardware connections, and verifying successful communication.

2 Introduction

The ESP32 family of chips supports the standard SPI FD (Full Duplex) Mode Protocol. In this mode, SPI uses two data lines (MISO and MOSI) to transfer data to and from the co-processor simultaneously during an SPI transaction.

3 High Level Design and Implementation

SPI Full duplex mode is the simplest mode of operation in ESP-Hosted. It can be easily tested with jumper wires. It doesn't require much complex hardware setup. For any non ESP chipsets as host also can prefer this mode for testing. This can also served as stepping stone before moving on to more complex modes of operations, like Dual SPI, Quad SPI and SDIO.

3.1 Number of Pins Required

In SPI Full Duplex mode, the following pins are required:

  1. MISO (Master In Slave Out): Data line for the co-processor to send data to the host.
  2. MOSI (Master Out Slave In): Data line for the host to send data to the co-processor.
  3. SCLK (Serial Clock): Clock signal generated by the host to synchronize data transmission.
  4. CS (Chip Select): Signal used by the host to select the co-processor for communication.
  5. Reset Pin: An output signal from the host to the co-processor. When asserted, the host resets the co-processor to synchronize the state of the host and co-processor.
  6. Handshake Pin: An output signal from the co-processor to the host. When asserted, it tells the host that the co-processor is ready for an SPI transaction.
  7. Data Ready Pin: An output signal from the co-processor to the host. When asserted, the co-processor is telling the host that it has data to send.

The SPI used is full duplex. Handshake, Data Ready and Reset are additional GPIOs used in addition to MISO, MOSI, SCLK and CS. All pins are mandatory.

3.2 SPI Full Duplex Mode Implementation

  • This solution uses SPI full duplex communication mode:

    • Read and write operations occur simultaneously in the same SPI transaction
  • Handshake and Data ready are configured as interrupts at host. On loading host, it should automatically reset the co-processor using reset pin.

  • Protocol rules:

    • Host must not start a transaction before ESP SPI peripheral is ready
    • ESP peripheral indicates readiness via Handshake pin
  • ESP peripheral preparation:

    • Always ready for data reception from host
    • Queues next SPI transaction immediately after completing previous one
  • SPI transaction structure:

    • Each transaction has a TX buffer and an RX buffer
    • TX buffer: Contains data ESP peripheral wants to send to host
    • RX buffer: Empty space to hold data received from host
  • Buffer initialization:

    • ESP peripheral sets TX and RX buffers to 1600 bytes (maximum size)
    • Host can send/receive up to 1600 bytes per transaction
  • TX buffer scenarios:

    1. No data to transfer:
      • Allocate 1600-byte dummy TX buffer
      • Set packet length field in payload header to 0
    2. Valid data to send:
      • TX buffer points to that data
  • SPI transaction setup:

    • Set transaction length to 1600 bytes regardless of TX buffer size
    • Submit transaction to SPI driver on ESP peripheral
    • Pull Handshake pin high to signal readiness
    • If TX buffer has valid data, also pull Data ready pin high
  • Host response to Handshake / Data ready interrupt:

    • Decide whether to perform SPI transaction (if Handshake is high)
    • Perform transfer if Data ready pin is high or host has data to transfer
    • If either condition is false, do not perform transfer, just ignore the interrupt
  • During SPI transaction:

    • Exchange TX and RX buffers on SPI data lines
  • Post-transaction processing:

    • Both ESP peripheral and host process received buffer based on payload header
  • Transaction completion:

    • ESP peripheral pulls Handshake pin low
    • If transaction had valid co-processor TX buffer, also pulls Data ready pin low

3.3 Code Reference

For a detailed implementation of SPI full duplex communication using the ESP-Hosted framework, refer to the following code files in the ESP-Hosted repository:

  • Master SPI Communication Code:

    • spi_drv.c: Contains the implementation for configuring and handling SPI transactions on the master side.
    • spi_wrapper.c: Provides an OS abstraction layer for SPI operations, making it easier to handle SPI communication in a platform-independent manner.
  • Co-processor SPI Communication Code:

    • spi_slave_api.c: Includes the setup and transaction handling for the SPI co-processor, detailing how the co-processor should configure its SPI interface and handle incoming and outgoing data.

4 Hardware Considerations

4.1 General Considerations

  • Ensure equal trace lengths for all SPI connections, whether using jumper wires or PCB traces.
  • Use the lower clock frequency like 5 MHz for evaluation. Once solution verified, optimise the clock frequency in increasing steps to max possible value. To find out practical maximum SPI slave frequency for your co-processor, check IDF_PERFORMANCE_MAX_SPI_CLK_FREQ in ESP-IDF SPI slave benchmark
  • Verify voltage compatibility between host and co-processor devices.
  • Provide proper power supply decoupling for both host and co-processor devices.

4.2 Jumper Wires

  • Jumper wires are suitable for initial testing and prototyping.
  • Use high-quality, low-capacitance jumper wires.
  • Keep wires as short as possible, ideally under 10 cm.
  • Arrange wires to minimize crosstalk, especially for clock and data lines.
  • Possibly, use twisted pairs for clock and data lines to reduce electromagnetic interference.
  • If possible, use a ground wire between every signal wire to improve signal integrity.
  • Connect as many grounds as possible to improve common ground reference and reduce ground noise.

4.3 PCB Design

For optimal performance and reliability in production designs:

  • Ensure equal trace lengths for all SPI signals (CLK, MOSI, MISO, CS) as much as possible. This practice, known as length matching, is crucial for maintaining signal integrity and reducing timing skew, especially at higher frequencies.
  • If perfect length matching is not possible, prioritize matching the clock (CLK) trace length with the data lines.
  • Use controlled impedance traces for high-speed signals.
  • Place bypass capacitors close to the power pins of both the host and co-processor devices.
  • Consider using series termination resistors on the clock and data lines to reduce reflections.
  • For high-speed designs, use a 4-layer PCB with dedicated power and ground planes.

4.4 Advanced Considerations

  • Calculate the maximum allowed trace length based on your clock frequency and PCB material.
  • Consider the capacitive load on the SPI bus, especially for longer traces or when using multiple co-processor devices.
  • For very high-speed designs, consider using differential signaling techniques.
  • Implement proper EMI/EMC design techniques to minimize electromagnetic interference.

Debugging Tips

  • Use an oscilloscope or logic analyzer to verify signal integrity and timing.
  • Start with a lower clock frequency and gradually increase it while monitoring performance.
  • Ensure proper grounding between the host and co-processor devices.
  • If using multiple power supplies, ensure they share a common ground.
  • Consider using level shifters if the host and co-processor operate at different voltage levels.

5 Hardware Setup

Setting up the hardware involves connecting the master and co-processor devices via the SPI pins and ensuring all extra GPIO signals are properly connected. Below is the table of connections for the SPI full duplex setup between an host ESP chipset and another ESP chipset as co-processor:

Host connections

Signal ESP32 ESP32-S2/S3 ESP32-C2/C3/C5/C6 ESP32-P4
CLK 14 12 6 9
MOSI 13 11 7 8
MISO 12 13 2 10
CS 15 10 10 7
Handshake 26 17 3 6
Data Ready 4 4 4 11
Reset Out 5 5 5 12

Note

The GPIOs pins on the ESP32-P4 are SPI IO_MUX pins and powered by the VDD_LP pin. If you use a different set of GPIOs, check that they are powered to 3.3V by the pin(s) providing power for the GPIOs based on your ESP32-P4 schematic. See the ESP32-P4 Datasheet, Table 2-1. Pin Overview, for a list of GPIO pins and the pins providing the power for the GPIOs.

If the pins providing power are connected to an internal Low Dropout Voltage Regulator (LDO), set the LDO to output 3.3V. See Low Dropout Voltage Regulator (LDO) for more information on programming the internal LDOs.

Co-processor connections

Signal ESP32 ESP32-C2/C3/C5/C6 ESP32-S2/S3 ESP32-C6 on ESP32-P4-Function-EV-Board
CLK 14 6 12 19
MOSI 13 7 11 20
MISO 12 2 13 21
CS 15 10 10 18
Handshake 26 3 17 22
Data Ready 4 4 5 23
Reset In EN EN/RST EN/RST EN/RST

Note

  • Always try to use IO_MUX pins from the datasheet for optimal performance on both sides.
  • These GPIO assignments are based on default Kconfig configurations & are configurable.
  • Once ported, any other non ESP host with standard SPI can be used.
  • All ESP chipsets support SPI Full Duplex mode. Chipsets with Wi-Fi/Bluetooth can be used as co-processor.

Important

  • Ensure proper grounding between host and co-processor devices.
  • Use short, high-quality cables for connections.
  • For production designs, consider using a properly designed PCB with controlled impedance traces.

6 Set-Up ESP-IDF

Before setting up the ESP-Hosted host & co-processor for SPI Full Duplex mode, ensure that ESP-IDF is properly installed and set up on your system.

Option 1: Installer Way

  • Windows

  • Linux or MacOS

    • For bash:
      bash docs/setup_esp_idf__latest_stable__linux_macos.sh
      
    • For fish:
      fish docs/setup_esp_idf__latest_stable__linux_macos.fish
      

Option 2: Manual Way

Please follow the ESP-IDF Get Started Guide for manual installation.

7 Flashing the Co-processor

Supported Co-processor Targets ESP32 ESP32-C2 ESP32-C3 ESP32-C5 ESP32-C6 ESP32-C61 ESP32-S2 ESP32-S3

There are two methods to flash the ESP-Hosted co-processor firmware:

7.1 Create Co-processor Project

  1. Create co-processor project possibly outside of ESP-IDF project directory using

    idf.py create-project-from-example "espressif/esp_hosted:slave"
    
  2. Navigate to the created project directory.

  3. Configure the project for your target ESP chip:

    idf.py set-target <target>
    

    Replace <target> with your specific ESP chip (e.g., esp32c3, esp32s3).

7.2 Co-processor Config

Configure the co-processor project using

idf.py menuconfig

7.2.1 Transport config

Navigate & change to following

-- "Example configuration"
    └── "Bus Config in between Host and Co-processor"
        └── "Transport layer"
            └── Select "SPI Full-duplex"

7.2.2 Any other config

Optionally, configure any additional SPI-specific settings under "SPI Full-duplex"

  • Set the GPIO pins for SPI signals (MOSI, MISO, CLK, CS), Handshake, Data Ready, Reset
  • Configure SPI mode (1, 2, or 3)
  • Set the SPI clock frequency
  • Checksum enable/disable (Checksum is recommended to be enabled as spi hardware doesn't have any error detection)
Generated files
  • Generated config files are (1) sdkconfig file and (2) internal sdkconfig.h file.
  • Please note, any manually changes done to these generated files, would not take effect.
Defaulting specific config (Optional)
  • This is advanced option, so please be careful.
  • To mark some config options as default, you can add specific config line in file, sdkconfig.defaults.<target>. So whenever next time building, you do not need to re-configure.

7.3 Co-processor Build

Build the co-processor project

idf.py build

7.4 Co-processor Flashing

It is recommended to periodically upgrade the slave firmware to leverage new features, bug fixes, and performance improvements.

Method Description Recommended Use
Direct Serial Flashing Uses UART pins for direct firmware installation First-time setup to install ESP-Hosted slave firmware
Slave OTA Update Performs slave firmware updates directly from Host All subsequent updates after initial installation
7.4.1 Serial Flashing (Initial Setup)

For the initial setup or when OTA is not available, use serial flashing.

Flash the co-processor firmware using

idf.py -p <co-processor_serial_port> flash

Note

If you are not able to flash the co-processor, there might be a chance that host is not allowing to to do so.

Put host in bootloader mode using following command and then retry flashing the co-processor

esptool.py -p **<host_serial_port>** --before default_reset --after no_reset run

Flash the co-processor and log the output:

idf.py -p <co-processor_serial_port> flash monitor

7.4.2 Co-processor OTA Flashing (Subsequent Updates)

The ESP-Hosted link comes pre-configured and ready to use on first boot. You can update the slave firmware remotely from the host MCU using OTA (Over-The-Air) updates: No ESP-Prog, serial cable, or extra GPIO connections are required.

For step-by-step instructions, see the Host Performs Slave OTA Example.

8 Flashing the Host

Supported Host Targets Any ESP chipset Any Non-ESP chipset

8.1 Select Example to Run in Hosted Mode

Select an example from the ESP-IDF examples directory that you wish to run in ESP-Hosted mode. All Wi-Fi and Bluetooth examples are supported. For simplicity and demonstration purposes, we will use the ESP-IDF iperf example.

8.2 Host Project Component Configuration

Now that ESP-IDF is set up, follow these steps to prepare the host:

1. Navigate to the iperf example in your ESP-IDF directory:
cd $IDF_PATH/examples/wifi/iperf
2. Dependency components

Add the required components to the project's idf_component.yml file:

idf.py add-dependency "espressif/esp_wifi_remote"
idf.py add-dependency "espressif/esp_hosted"
3. Remove conflicting configuration

Open the main/idf_component.yml file and remove/comment the following block if present:

# ------- Delete or comment this block ---------
espressif/esp-extconn:
  version: "~0.1.0"
  rules:
    - if: "target in [esp32p4]"
# -----------------------------------

This step is necessary because esp-extconn and esp-hosted cannot work together.

4. Disable native Wi-Fi if available

If your host ESP chip already has native Wi-Fi support, disable it by editing the components/soc/<soc>/include/soc/Kconfig.soc_caps.in file and changing all WIFI related configs to n.

If you happen to have both, host and co-processor as same ESP chipset type (for example two ESP32-C2), note an additional step

8.3 Menuconfig, Build and Flash Host

1. High performance configurations

This is optional step, suggested for high performance applications.

If using ESP32-P4 as host and the ESP32-C6 as the co-processor:

  • Remove all CONFIG_ESP_WIFI_ settings. They do not apply to ESP-Hosted.
  • Add the following settings to your sdkconfig.defaults.esp32p4 file:
    ### sdkconfig for ESP32-P4 + C6 Dev board
    CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16
    CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=64
    CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=64
    CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
    CONFIG_WIFI_RMT_TX_BA_WIN=32
    CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
    CONFIG_WIFI_RMT_RX_BA_WIN=32
    
    CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65534
    CONFIG_LWIP_TCP_WND_DEFAULT=65534
    CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
    CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
    CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
    
    CONFIG_LWIP_TCP_SACK_OUT=y
    

For other ESP32 hosts, you can merge above configs into the corresponding sdkconfig.defaults.esp32XX file.

To adjust other Wi-Fi parameters, run idf.py menuconfig and go to Component config ---> Wi-Fi Remote ---> Wi-Fi configuration.

Optimised parameters for other co-processors can be found in the Performance Optimization Guide.

2. Set environment for your host ESP chip:
idf.py set-target <host_target>
3. Flexible Menuconfig configurations
idf.py menuconfig

ESP-Hosted-MCU host configurations are available under "Component config" -> "ESP-Hosted config"

  1. Select "SPI Full-duplex" as the transport layer
  2. Change co chipset to connect to under "slave chipset to be used"
  3. Optionally, configure SPI-specific settings in the "SPI Configuration" menu, like:
  • SPI Clock Freq (MHz)
  • SPI Mode
  • SPI Pins
  • SPI Checksum Enable/Disable (Checksum is recommended to be enabled as spi hardware doesn't have any error detection)

Note

The actual clock frequency used is determined by the hardware. Use an oscilloscope or logic analyzer to check the clock frequency.

4. Build the project:
idf.py build
5. Flash the firmware:
idf.py -p <host_serial_port> flash
6. Monitor the output:
```
idf.py -p <host_serial_port> monitor
```

- If host was put into bootloader mode earlier, it may need manual reset

9 Testing and Troubleshooting

After flashing both the co-processor and host devices, follow these steps to connect and test your ESP-Hosted SPI Full Duplex setup:

  1. Connect the hardware:

    • Follow the pin assignments for SPI Full Duplex as specified in Hardware Setup.
    • Ensure all necessary connections are made, including power, ground, and the extra GPIO signals (Data_Ready and Reset).
  2. Power on both devices.

  3. Verify the connection:

    • Check the serial output of both devices for successful initialization messages.
    • Look for messages indicating that the SPI Full Duplex transport layer has been established
  4. Logs at both sides:

    • Host:

      I (522) transport: Attempt connection with slave: retry[0]
      I (525) transport: Reset slave using GPIO[54]
      I (530) os_wrapper_esp: GPIO [54] configured
      I (535) gpio: GPIO[54]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
      I (1712) transport: Received INIT event from ESP32 peripheral
      I (1712) transport: EVENT: 12
      I (1712) transport: EVENT: 11
      I (1715) transport: capabilities: 0xe8
      I (1719) transport: Features supported are:
      I (1724) transport:        - HCI over SPI
      I (1728) transport:        - BLE only
      I (1732) transport: EVENT: 13
      I (1736) transport: ESP board type is : 13
      
      I (1741) transport: Base transport is set-up
      
    • Co-processor:

      I (492) fg_mcu_slave: *********************************************************************
      I (501) fg_mcu_slave:                 ESP-Hosted-MCU Slave FW version :: X.Y.Z
      
      I (511) fg_mcu_slave:                 Transport used :: SPI
      I (520) fg_mcu_slave: *********************************************************************
      I (529) fg_mcu_slave: Supported features are:
      I (534) fg_mcu_slave: - WLAN over SPI
      I (538) h_bt: - BT/BLE
      I (541) h_bt:    - HCI Over SPI
      I (545) h_bt:    - BLE only
      
  5. Test basic functionality:

    • The iperf example automatically attempts to connect to the configured Wi-Fi network. Watch the serial output for connection status.
    • If the automatic connection fails, you can manually initiate a Wi-Fi scan and connection:
      sta_scan
      sta_connect <SSID> <password>
      
  6. Additional commands to test:

    • Get IP address: sta_ip
    • Disconnect from Wi-Fi: sta_disconnect
    • Set Wi-Fi mode: wifi_mode <mode> (where mode can be 'sta', 'ap', or 'apsta')
  7. Advanced iperf testing: Once connected, you can run iperf tests to verify performance:

    Test Case Host Command External STA Command
    UDP Host TX iperf -u -c <STA_IP> -t 60 -i 3 iperf -u -s -i 3
    UDP Host RX iperf -u -s -i 3 iperf -u -c <HOST_IP> -t 60 -i 3
    TCP Host TX iperf -c <STA_IP> -t 60 -i 3 iperf -s -i 3
    TCP Host RX iperf -s -i 3 iperf -c <HOST_IP> -t 60 -i 3

    Note: Replace <STA_IP> with the IP address of the external STA, and <HOST_IP> with the IP address of the ESP-Hosted device.

Tip

To measure the optimal performance, check out the Shield Box Test Setup.

  1. Troubleshooting:

    • If you encounter issues, refer to section 6.3 for testing the SPI connection.
    • Consider using a lower clock speed or checking your hardware connections if you experience communication problems.
    • ESP-Hosted-MCU troubleshooting guide: docs/troubleshooting.md
  2. Monitoring and debugging:

    • Use the serial monitor on both devices to observe the communication between the host and co-processor.
    • For more detailed debugging, consider using a logic analyzer to examine the SPI signals.

10 References