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>
14 KiB
Host Power Save (ESP-Hosted MCU)
Overview
The Host Power Save feature allows the host MCU to enter low-power states while the ESP slave maintains network connectivity. This enables efficient power usage in battery-operated devices.
Host Power Save Modes:
- Deep Sleep at Host: Ultra-low power consumption with GPIO wake-up
- Light Sleep at Host: (Coming Soon) Faster wake-up with reduced power savings
Key features include:
- Host enters power save mode, network stays online
- Slave intelligently wakes host when needed
- Power state synchronization between host and slave
- Seamless network handover during power save
- Integration with Network Split
Configuration
Host Configuration
In host example project, run idf.py menuconfig and Enable Allow host to power save
# Host side kconfig
Component config
└── ESP-Hosted config
└── [*] Allow host to power save <== Enable This
└── [*] Allow host to enter deep sleep. ─┐
├── (<gpio_num>) Host in: Host Wakeup GPIO │
└── Host Wakeup GPIO Level ├── Default config (No change)
└── (X) High ─┘
Note
- The wakeup GPIO number must be an RTC-capable GPIO.
- Ensure the wake up GPIO is unused
Slave Configuration
In slave project, run idf.py menuconfig and Enable Allow host to power save
# Slave side kconfig
Example Configuration
└── [*] Allow host to power save <== Enable This
└── [*] Allow host to enter deep sleep. ─┐
├── (<gpio_num>) Slave out: Host wakeup GPIO │
└── Host Wakeup GPIO Level ├── Default config (No change)
└── (X) High ─┘
High Level Overview
The power management system coordinates between the host MCU and ESP slave to ensure smooth transitions between active and power save states.
sequenceDiagram
participant Host as Host MCU
participant Slave as ESP Slave
participant Network as Network/WiFi
Host->>Slave: Power Save Start Signal
Note over Slave: Slave takes over network operations
Host->>Host: Enter Power Save Mode
Network->>Slave: Incoming packets
Slave->>Slave: Packet needs host
Slave->>Host: Trigger Wake-up GPIO
Host->>Host: Exit Power Save Mode
Host->>Slave: Power Save Stop Signal
Slave->>Slave: Resume packet delivery
Wake-up Mechanisms
The slave initiates a wake-up by toggling a dedicated GPIO line to the host when one of the following triggers occurs:
- Periodic Timer-based Wake-up
- CLI-based Wake-up
- Network-triggered Wake-up
flowchart TD
A[💤 Host in Power Save Mode] --> B{⏱️ Trigger on Slave?}
B -->|🕒 Timer Expired| C[🔔 Send Wake-up GPIO]
B -->|🧑💻 CLI Command| C
B -->|🌐 Network Packet| C
C --> D[⚡ Host Wakes Up]
D --> E[▶️ Resume Operations]
classDef sleep fill:#f5f5f5,stroke:#888,font-size:14px;
classDef event fill:#e3f2fd,stroke:#2196f3,font-size:14px;
classDef action fill:#e0f7e9,stroke:#4caf50,font-size:14px;
class A sleep
class B,C event
class D,E action
State transitions
- Active → Preparing: Host prepares (notifies slave, configures GPIO)
- Preparing → PowerSave: Host enters power save mode; slave handles network
- PowerSave → Waking: Slave triggers wakeup on event (timer/packet/CLI)
- Waking → Active: Host re-inits system, syncs with slave
stateDiagram
[*] --> Active
Active --> Preparing: 🥱 start_host_power_save()
Preparing --> PowerSave: 😴 Power Save Entry
PowerSave --> Waking: 😳 Wake Event
Waking --> Active: 😊 Slave sync-up complete
Deep Dive
💤 Host Enters Power Save Mode
When the host application initiates power save mode, the following function sequence occurs to safely coordinate with the slave.
sequenceDiagram
participant App as Host Application (or CLI)
participant PowerSaveDrv as power_save_drv.c
participant TransportDrv as transport_drv.c
participant Slave as Slave MCU
App->>PowerSaveDrv: start_host_power_save()
PowerSaveDrv->>TransportDrv: notify_slave_host_power_save_start()
TransportDrv->>Slave: bus_inform_slave_host_power_save_start()
Slave-->>TransportDrv: Ack
TransportDrv-->>PowerSaveDrv: Ack
Note right of TransportDrv: Slave is now aware<br/>host is entering power save.
PowerSaveDrv->>PowerSaveDrv: hold_slave_reset_gpio_pre_power_save()
Note right of PowerSaveDrv: Prevents slave from resetting.
PowerSaveDrv->>PowerSaveDrv: _h_config_host_power_save_hal_impl()
Note right of PowerSaveDrv: Configures wakeup GPIO.
PowerSaveDrv->>PowerSaveDrv: _h_start_host_power_save_hal_impl()
Note right of PowerSaveDrv: Host enters power save mode.
Host Wake-up and Synchronization Sequence
The slave wakes the host using a GPIO signal. Upon rebooting, the host detects the wake-up reason and synchronizes with the slave through a handshake mechanism, after which normal operation resumes.
sequenceDiagram
participant SlaveApp as Slave<br/>(App/CLI)
participant SlavePS as Slave<br/>(host_power_save.c)
participant HostHW as Host MCU<br/>(Hardware)
participant HostTransport as Host<br/>(transport_drv.c)
participant HostPS as Host<br/>(power_save_drv.c)
SlaveApp->>SlavePS: wakeup_host()
SlavePS->>SlavePS: wakeup_host_mandate()
SlavePS->>HostHW: Toggles Wake-up GPIO
Note over SlavePS: Now blocks, waiting<br/>for wakeup_sem.
HostHW-->>HostTransport: Host wakes from power save
Note over HostTransport: esp_hosted_init() is called,<br/>which calls transport_drv_reconfigure().
HostTransport->>HostPS: esp_hosted_woke_from_power_save()
HostPS-->>HostTransport: Returns true
HostTransport->>HostPS: stop_host_power_save()
HostPS->>HostTransport: notify_slave_host_power_save_stop()
HostTransport-->>SlavePS: Sends "Power Save Off" event<br/>via transport layer.
Note over SlavePS: host_power_save_alert() handles the event.
SlavePS->>SlavePS: Gives wakeup_sem.
Note over SlavePS: Handshake complete.
SlavePS-->>SlaveApp: wakeup_host_mandate() returns,<br/> unblocking the application.
Note over HostTransport, SlavePS: System is now fully active.
SlavePS->>HostTransport: Resume packet delivery for Network Packet Wake-up case
APIs
Sources
Host Files
host/api/include/esp_hosted_power_save.h: enums and APIshost/drivers/power_save/power_save_drv.c: power save logichost/drivers/transport/transport_drv.c: slave communicationhost/drivers/transport/{sdio,spi}/...: bus specific power save hooks
Slave Files
slave/main/host_power_save.h: slave enums and APIsslave/main/host_power_save.c: wake-up managementslave/main/nw_split_router.c: packet inspection (for wake-up)
Functions
Host APIs
/* Power save driver APIs (host side) */
int esp_hosted_power_save_enabled(void);
int esp_hosted_power_save_init(void);
int esp_hosted_woke_from_power_save(void);
int esp_hosted_power_saving(void);
int esp_hosted_power_save_start(esp_hosted_power_save_type_t power_save_type);
int esp_hosted_power_save_timer_start(uint32_t time_ms, int timer_type);
int esp_hosted_power_save_timer_stop(void);
/* Retain GPIO during power save */
int hold_slave_reset_gpio_pre_power_save(void);
int release_slave_reset_gpio_post_wakeup(void);
Slave APIs
/* Slave-side power save APIs */
int host_power_save_init(void (*host_wakeup_callback)(void));
int is_host_power_saving(void);
int is_host_wakeup_needed(interface_buffer_handle_t *buf_handle);
int wakeup_host_mandate(uint32_t timeout_ms);
int wakeup_host(uint32_t timeout_ms);
int host_power_save_alert(uint32_t ps_evt);
CLI Demo
Enter Power Save Mode
- Enable CLI: Ensure
ESP_HOSTED_CLI_ENABLEDis enabled in the host'smenuconfig. - Start the CLI: The CLI starts automatically when
esp_hosted_init()is called. - Enter the command: At the
host>prompt, type thepower_savecommand.
host> host-power-save
I (11147) esp_cli: Putting ESP32-P4 into power save mode...
I (11147) H_power_save: Inform slave: Host PS start
I (11148) H_SDIO_DRV: Inform slave, host power save is started
ESP-ROM:esp32p4...
...
This command calls esp_hosted_power_save_start(), which signals the slave and puts the host into the configured power save mode. If it is deep sleep power save, The host would not be able to execute any further code until it is woken up.
Wake-up from Slave
The host can be woken up by the slave under several conditions, such as receiving a specific network packet. On the slave, a CLI command wake-up is available for demonstration.
- Network Packet Wake-up: By default, the slave is configured to wake the host upon receiving certain network traffic (e.g., on priority ports like SSH, or MQTT messages containing a "wakeup-host" payload).
- Manual Wake-up (Demo): Use the
wake-upcommand in the slave's terminal.
Slave Log:
coprocessor> wake-up-host
I (13730) esp_cli: Asking P4 to wake-up...
I (13730) host_ps: WAKE UP Host!!!!!
I (13741) host_ps: Cleared wakeup gpio, IO2
...
I (15543) host_ps: Wakeup semaphore given
I (15543) host_ps: host woke up
Host Log (After Wake-up):
...
I (430) H_power_save: Wakeup from power save
I (432) transport: Waiting for power save to be off
I (1137) H_SDIO_DRV: Host woke up from power save
...
When the host wakes up, checks wake-up reason, and re-establishes slave connectivity automatically.
Coding Example
While the CLI provides an easy demo, most applications would trigger power save modes using the power save APIs.
Host example
The example below shows how to check wake-up reason and enter power save mode programmatically:
#include "esp_hosted.h"
#include "esp_hosted_power_save.h"
static const char* TAG = "HOST_APP";
void app_main(void)
{
/* First, check the reason of host boot-up. is it deep sleep? */
if (esp_hosted_woke_from_power_save()) {
ESP_LOGI(TAG, "Host woke up from power save mode.");
}
esp_hosted_init();
esp_hosted_power_save_init();
/*
* The call to esp_hosted_init() automatically handles informing the
* slave that the host has woken up. No explicit call to
* stop_host_power_save() is needed here.
*/
ESP_LOGI(TAG, "Application running. Entering power save in 15 seconds.");
vTaskDelay(pdMS_TO_TICKS(15000));
ESP_LOGI(TAG, "Initiating power save now.");
/* This function does not return. The host will enter power save mode
* and wake up from app_main upon wake-up event. */
esp_hosted_power_save_start(HOSTED_POWER_SAVE_TYPE_DEEP_SLEEP);
}
Slave Example
The slave wakes the host automatically for host destined network packets. The example below demonstrates manual wake-up using timer
#include "host_power_save.h"
static const char* TAG = "SLAVE_APP";
void wake_host_task(void *pvParameters)
{
/* Wait for a while before attempting to wake the host */
vTaskDelay(pdMS_TO_TICKS(30000));
ESP_LOGI(TAG, "Checking host power state.");
/* Check if the host is actually sleeping */
if (is_host_power_saving()) {
ESP_LOGI(TAG, "Host is in power save mode. Sending wake-up signal.");
/*
* Attempt to wake the host. The timeout (in ms) is for the
* handshake to complete after the host wakes up.
*/
int ret = wakeup_host(5000);
if (ret == 0) {
ESP_LOGI(TAG, "Host wake-up process initiated successfully.");
} else {
ESP_LOGE(TAG, "Failed to wake up host. Error: %d", ret);
}
} else {
ESP_LOGI(TAG, "Host is already awake.");
}
vTaskDelete(NULL);
}
void app_main(void)
{
/*
* Initialize slave-side components...
* host_power_save_init() is to be called by slave application.
* For example, https://github.com/espressif/esp-hosted-mcu/blob/31bdfb417fbdba4119d8544277bfbdad4e62e40c/slave/main/example_light_sleep.c#L111
*
* If you do not wish to use `host_wakeup_callback` callback functions, you can call
* ret = host_power_save_init(NULL);
* if (ret) {
* ESP_LOGW(TAG, "host_power_save_init failed with ret: %d", ret);
* }
*
* *** It is suggested to use light sleep integration with deep sleep for highest power saving ***
* See: https://github.com/espressif/esp-hosted-mcu/blob/31bdfb417fbdba4119d8544277bfbdad4e62e40c/slave/main/Kconfig.projbuild#L1360 for sample slave example config
*/
/* Create a task to wake up the host */
xTaskCreate(wake_host_task, "wake_host_task", 2048, NULL, 5, NULL);
}
Troubleshooting
-
Wake-up Failures
- Check GPIO configuration and physical connections.
- Verify
CONFIG_ESP_HOSTED_HOST_WAKEUP_GPIOis set correctly on both host and slave. - Check that the GPIO level configuration (High/Low) matches on both devices.
-
Conflicting GPIOs
- Ensure you use Host Wakeup GPIO different from the GPIOs used in communication bus underlying
-
Network Connectivity Loss
- Verify the slave maintains its WiFi connection while the host is asleep.
- Check the Network Split configuration if enabled.
-
Transport Synchronization Issues
- Ensure the correct power state signals are being sent. After wake-up, the host should log that it is informing the slave it has woken up.