Files
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

11 KiB

OpenThread and Zigbee Support

Table of Contents

1. Introduction

OpenThread is an IP stack which features mesh network and low power consumption. Zigbee is a low-power, wireless mesh networking protocol designed for IoT devices. Both operate on the 802.15.4 standard.

ESP-Hosted runs the OpenThread or Zigbee Host stack on the Host Processor and the RCP (Radio Co-Processor) on the co-processor. The RCP is common and can operate with either OpenThread or Zigbee Host stacks.

The diagram below shows how the OpenThread Host and RCP communicate.

    +------------+                                      +------------+
    |            |         ESP-Hosted Transport         |            |
    | OpenThread |<------------------------------------>|    RCP     | (Example: ESP32-C6)
    | / Zigbee   |                                      |            |
    | Host       |                                      |            |
    |            |                 UART                 |            |
    |            |<------------------------------------>|            |
    |            |                                      |            |
    +------------+                                      +------------+

Note

Currently a dedicated UART channel is required to pass OpenThread data between the Host and RCP. OpenThread over the current ESP-Hosted Transport to be supported in the future.

Zigbee only supports using a dedicated UART channel for communication.

1.1 Prerequisites

1.1.1 Basic OpenThread / Zigbee Operation

The co-processor must support OpenThread / Zigbee RCP operation.

Example co-processors that support RCP include the ESP32-H2, ESP32-C5 and ESP32-C6.

1.1.2 Operating as an OpenThread Border Router / Zigbee Gateway

The co-processor must support RCP operation and Wi-Fi to operate as part of an OpenThread Border Router or Zigbee Gateway, which passes packets between the OpenThread / Zigbee and Wi-Fi networks.

Example co-processors as RCPs that support OpenThread Border Router / Zigbee Gateway operation include the ESP32-C5 and ESP32-C6.

Coexistence between OpenThread / Zigbee and Wi-Fi

The co-processor has only one hardware radio, so Wi-Fi and OpenThread / Zigbee have to share the radio (coexistence). This may lead to performance issues or dropped packets if there is a lot of traffic on Wi-Fi and/or OpenThread. See this guide on RF Coexistence for more information.

The recommended option is to use two co-processors: one for Wi-Fi and one for OpenThread / Zigbee:

    +------------+
    |            |         ESP-Hosted Transport         +-----------+
    | OpenThread |<------------------------------------>|   Wi-Fi   | (Example: ESP32-C6)
    | Border     |                                      +-----------+
    | Router     |
    | / Zigbee   |                 UART                 +-----------+
    | Gateway    |<------------------------------------>|    RCP    | (Example: ESP32-H2)
    |            |                                      +-----------+
    +------------+

Here, OpenThread / Zigbee is operating independent of ESP-Hosted. Since there are now two hardware radios, coexistence is not required.

More information on this mode of operation for the ESP32-P4 can be found in the ESP-IDF OpenThread Border Router Example for ESP32-P4 or the Zigbee SDK Gateway Example.

2. Configuration

2.1 RCP Configuration on the Co-processor

Note

This section targets the ESP32-C6 as the RCP. The same configuration is required when using other supported co-processors as the RCP.

In the ESP-Hosted project slave directory:

Edit sdkconfig.defaults.esp32c6 to add or enable the OpenThread section. These settings are required to enable OpenThread, configure it to be an RCP, and to disable OpenThread features not required for a RCP.

CONFIG_OPENTHREAD_ENABLED=y
CONFIG_OPENTHREAD_RADIO=y
CONFIG_OPENTHREAD_DIAG=n
CONFIG_OPENTHREAD_COMMISSIONER=n
CONFIG_OPENTHREAD_JOINER=n
CONFIG_OPENTHREAD_BORDER_ROUTER=n
CONFIG_OPENTHREAD_CLI=n
CONFIG_OPENTHREAD_SRP_CLIENT=n
CONFIG_OPENTHREAD_DNS_CLIENT=n
CONFIG_OPENTHREAD_TASK_SIZE=3072
CONFIG_OPENTHREAD_CONSOLE_ENABLE=n
CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC=n

CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y

On the command-line:

idf.py set-target esp32c6
idf.py menuconfig

Enter the OpenThread section to configure the dedicated OpenThread UART:

Example Configuration
└── [*] Enable OpenThread RCP (Radio Co-Processor)         <─── ENABLE
    └── OpenThread RCP Configuration
        ├── OpenThread Transport ──> UART                  <─── SET TO THIS VALUE
        └── (configure UART parameters)

If you did not edit sdkconfig.defaults.esp32c6 to enable OpenThread (above), modify these settings instead:

Component config
├── Wireless Coexistence
│   └── [*] Software controls WiFi/Bluetooth coexistence   <─── ENABLE
└── OpenThread
    ├── [*] OpenThread                                     <─── ENABLE
    ├── Thread Task Parameters
    │   └── (3072) Size of OpenThread task                 <─── SET TO THIS VALUE
    ├── Thread Console
    │   ├── [ ] Enable OpenThread console                  <─── DISABLE
    │   └── [ ] Enable Openthread Command-Line Interface   <─── DISABLE
    ├── Thread Core Features
    │   ├── Thread device type ──> Radio Only Device       <─── SET TO THIS VALUE
    │   ├── [ ] Enable Commissioner                        <─── DISABLE
    │   ├── [ ] Enable Joiner                              <─── DISABLE
    │   ├── [ ] Enable SRP Client                          <─── DISABLE
    │   ├── [ ] Enable DNS Client                          <─── DISABLE
    │   ├── [ ] Enable diag Client                         <─── DISABLE
    └── Thread Log
        └── [ ] Enable dynamic log level control           <─── DISABLE

You can now build the co-processor as an RCP.

2.2 OpenThread Configuration on the Host

Note

This section targets the ESP32-P4 as the OpenThread Host.

OpenThread configuration on the host depends on the OpenThread features to be enabled. ESP-Hosted includes two examples:

Further OpenThread Host examples can be found in the ESP-IDF Examples directory.

Further configuration

PSRAM should be enabled to run OpenThread with ESP-Hosted due to memory requirements.

The OpenThread host must also be configured to recognise that the OpenThread RCP has coexistence enabled: otherwise OpenThread initialisation will fail due to a mismatch of OpenThread capabilities between the Host and RCP.

### enable SPIRAM, else application will not start (out of memory)
CONFIG_SPIRAM=y
CONFIG_ESP_HOSTED_DFLT_TASK_FROM_SPIRAM=y

### SW coexistence on the co-processor is on, so this option must be
### disabled on host to keep OpenThread capabilities consistent
CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE=n

For the ESP32-P4, these have been set-up in the sdkconfig.default.esp32p4 configuration files in the ESP-Hosted OpenThread examples.

2.2.1 Initialising the OpenThread Connection from the Host Application

Initialise the ESP-Hosted interface first:

esp_hosted_init();
esp_hosted_connect_to_slave();

Then initialise the OpenThread RCP using ESP-Hosted:

esp_hosted_openthread_rcp_init();
esp_hosted_openthread_rcp_start();

In ESP-IDF, the OpenThread Host interface needs to know the OpenThread radio configuration. This is provided via ESP-Hosted:

esp_hosted_openthread_radio_config_t hosted_radio_config = { 0 };
esp_hosted_openthread_get_radio_config(&hosted_radio_config);

hosted_radio_config is then used to configure the OpenThread's radio configuration.

See the ESP-Hosted OpenThread examples for more information.

2.3 Zigbee Configuration on the Host

Note

This section targets the ESP32-P4 as the Zigbee Host.

Important

Zigbee support has been tested using ESP-IDF 5.5.4.

ESP-Hosted includes the following example:

See the ESP Zigbee SDK for a Zigbee Gateway Example. Further Zigbee examples can be found in the ESP Zigbee SDK.

2.3.1 Initialising the Zigbee Connection from the Host Application

Initialising the ESP-Hosted interface and setting the Zigbee radio configuration is similar to setting up under OpenThread. See Section 2.2.1 above

3. More Information