Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
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

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>
This commit is contained in:
2026-07-15 09:50:27 +02:00
co-authored by Claude Fable 5
parent ac9d24e0f9
commit cb5826e02b
666 changed files with 216925 additions and 0 deletions
@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(sd_card)
@@ -0,0 +1,164 @@
| Supported Hosts | ESP32-P4 | ESP32-S3 |
|-----------------|----------|----------|
# SD Card (SDMMC) with ESP-Hosted example
> [!IMPORTANT]
> The ESP-IDF `master` branch has an issue that prevents the SD card and ESP-Hosted from working together when both are using SDMMC. See [ESP-IDF Issue 16233](https://github.com/espressif/esp-idf/issues/16233) for more information. This code contains a workaround for this issue.
This example demonstrates how to use an SD card on an ESP32-P4 dev board when ESP-Hosted is using SDIO to communicate with the on-board ESP32-C6. It has been modified from the standard [ESP-IDF SD Card example](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card/sdmmc).
This example does the following steps:
1. Initialise ESP-Hosted and Wi-Fi
1. Use an "all-in-one" `esp_vfs_fat_sdmmc_mount` function to:
- initialize SDMMC peripheral,
- probe and initialize an SD card,
- mount FAT filesystem using FATFS library
- register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used.
1. Print information about the card, such as name, type, capacity, and maximum supported frequency.
1. Perform a Wi-Fi Scan
1. Create a file using `fopen` and write to it using `fprintf`.
1. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function.
1. Open renamed file for reading, read back the line, and print it to the terminal.
1. Perform another Wi-Fi Scan
This example supports SD (SDSC, SDHC, SDXC) cards and eMMC chips.
## Hardware
This example requires an ESP32-P4 development board with an SD card slot and an SD card. On this board, the SD card slot is assigned to SDMMC Slot 0, while the on-board ESP32-C6 is connected to the ESP32-P4 on SDMMC Slot 1.
Although it is possible to connect an SD card breakout adapter, keep in mind that connections using breakout cables are often unreliable and have poor signal integrity. You may need to use lower clock frequency when working with SD card breakout adapters.
This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot.
### Pin assignments for SDMMC Slot 0 on ESP32-P4
On ESP32-P4, SDMMC Slot 0 GPIO pins cannot be customized. The GPIO assigned in the example should not be modified.
The table below lists the default pin assignments.
ESP32-P4 pin | SD card pin | Notes
--------------|-------------|------------
GPIO43 | CLK | 10k pullup
GPIO44 | CMD | 10k pullup
GPIO39 | D0 | 10k pullup
GPIO40 | D1 | not used in 1-line SD mode; 10k pullup in 4-line mode
GPIO41 | D2 | not used in 1-line SD mode; 10k pullup in 4-line mode
GPIO42 | D3 | not used in 1-line SD mode, but card's D3 pin must have a 10k pullup
### 4-line and 1-line SD modes
By default, this example uses 4 line SD mode, utilizing 6 pins: CLK, CMD, D0 - D3. It is possible to use 1-line mode (CLK, CMD, D0) by changing "SD/MMC bus width" in the example configuration menu (see `CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_1`).
Note that even if card's D3 line is not connected to the ESP chip, it still has to be pulled up, otherwise the card will go into SPI protocol mode.
## How to use example
### Build and flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with serial port name.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example output
A Wi-Fi Scan is performed before and after accessing the SD Card. This shows that both SDMMC Slots work as expected.
```
I (2103) transport: Received INIT event from ESP32 peripheral
I (2103) transport: EVENT: 12
I (2113) transport: Identified slave [esp32c6]
I (2113) transport: EVENT: 11
I (2113) transport: capabilities: 0xd
I (2123) transport: Features supported are:
I (2123) transport: * WLAN
I (2123) transport: - HCI over SDIO
I (2133) transport: - BLE only
I (2133) transport: EVENT: 13
I (2133) transport: ESP board type is : 13
I (2143) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (2143) H_API: Transport active
I (2143) transport: Slave chip Id[12]
I (2153) transport: raw_tp_dir[-], flow_ctrl: low[60] high[80]
I (2153) transport: transport_delayed_init
I (2163) esp_cli: Remove any existing deep_sleep cmd in cli
I (2163) esp_cli: Registering command: crash
I (2173) esp_cli: Registering command: reboot
I (2173) esp_cli: Registering command: mem-dump
I (2173) esp_cli: Registering command: task-dump
I (2183) esp_cli: Registering command: cpu-dump
I (2183) esp_cli: Registering command: heap-trace
I (2193) esp_cli: Registering command: sock-dump
I (2193) esp_cli: Registering command: host-power-save
I (2203) hci_stub_drv: Host BT Support: Disabled
I (2203) H_SDIO_DRV: Received INIT event
I (2203) H_SDIO_DRV: Event type: 0x22
I (2213) H_SDIO_DRV: Write thread started
I (2643) example: Initializing SD card
I (2643) example: Using SDMMC peripheral
W (2643) ldo: The voltage value 0 is out of the recommended range [500, 2700]
I (2643) RPC_WRAP: ESP Event: wifi station started
I (2653) RPC_WRAP: ESP Event: wifi station started
I (2743) example: Mounting filesystem
I (2743) sdmmc_periph: sdmmc_host_init: SDMMC host already initialized, skipping init flow
I (2953) example: Filesystem mounted
I (2953) example: Doing Wi-Fi Scan
I (3653) rpc_req: Scan start Req
W (3653) rpc_rsp: Hosted RPC_Resp [0x21a], uid [4], resp code [12298]
I (6183) RPC_WRAP: ESP Event: StaScanDone
I (6203) example: Total APs scanned = 11, actual AP number ap_info holds = 10
Name: SD16G
Type: SDHC
Speed: 40.00 MHz (limit: 40.00 MHz)
Size: 14868MB
CSD: ver=2, sector_size=512, capacity=30449664 read_bl_len=9
SSR: bus_width=4
I (6213) example: Opening file /sdcard/hello.txt
I (6303) example: File written
I (6323) example: Renaming file /sdcard/hello.txt to /sdcard/foo.txt
I (6333) example: Reading file /sdcard/foo.txt
I (6333) example: Read from file: 'Hello SD16G!'
I (6333) example: Opening file /sdcard/nihao.txt
I (6353) example: File written
I (6353) example: Reading file /sdcard/nihao.txt
I (6363) example: Read from file: 'Nihao SD16G!'
I (6363) example: Card unmounted
I (6363) example: Doing another Wi-Fi Scan
I (6363) rpc_req: Scan start Req
I (8863) RPC_WRAP: ESP Event: StaScanDone
I (8883) example: Total APs scanned = 11, actual AP number ap_info holds = 10
I (8883) main_task: Returned from app_main()
Done
```
## Troubleshooting
### Card fails to initialize with `sdmmc_init_sd_scr: send_scr (1) returned 0x107` error
Check connections between the card and the ESP32. For example, if you have disconnected GPIO2 to work around the flashing issue, connect it back and reset the ESP32 (using a button on the development board, or by pressing Ctrl-T Ctrl-R in IDF Monitor).
### Card fails to initialize with `sdmmc_check_scr: send_scr returned 0xffffffff` error
Connections between the card and the ESP32 are too long for the frequency used. Try using shorter connections, or try reducing the clock speed of SD interface.
### Failure to mount filesystem
```
example: Failed to mount filesystem.
```
The example will be able to mount only cards formatted using FAT32 filesystem.
@@ -0,0 +1,14 @@
set(srcs "sd_card_example_main.c"
"sd_card_functions.c"
"esp_hosted_wifi.c")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
REQUIRES fatfs esp_wifi
WHOLE_ARCHIVE)
if(NOT CONFIG_SOC_SDMMC_HOST_SUPPORTED)
fail_at_build_time(sdmmc ""
"Only ESP32, ESP32-S3, ESP32-P4 targets are supported."
"Please refer README.md for more details")
endif()
@@ -0,0 +1,92 @@
menu "SD/MMC Example Configuration"
config EXAMPLE_SDMMC_SLOT_RANGE_MAX
int
default 0 if IDF_TARGET_ESP32P4
default 1 if IDF_TARGET_ESP32S3
config EXAMPLE_SDMMC_SLOT
int "SD/MMC slot to use"
default 0
range 0 EXAMPLE_SDMMC_SLOT_RANGE_MAX
help
Select the slot to use
For ESP32-P4, only Slot 0 for SD Card is available as ESP-Hosted uses Slot 1
choice EXAMPLE_SDMMC_BUS_WIDTH
prompt "SD/MMC bus width"
default EXAMPLE_SDMMC_BUS_WIDTH_4
help
Select the bus width of SD or MMC interface.
Note that even if 1 line mode is used, D3 pin of the SD card must have a pull-up resistor connected.
Otherwise the card may enter SPI mode, the only way to recover from which is to cycle power to the card.
config EXAMPLE_SDMMC_BUS_WIDTH_4
bool "4 lines (D0 - D3)"
config EXAMPLE_SDMMC_BUS_WIDTH_1
bool "1 line (D0)"
endchoice
config EXAMPLE_PIN_CARD_POWER_RESET
int
depends on SOC_SDMMC_IO_POWER_EXTERNAL
prompt "Card Power Reset"
default 45
config EXAMPLE_PIN_CMD
int
prompt "CMD GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 35 if IDF_TARGET_ESP32S3
range 44 44 if IDF_TARGET_ESP32P4
config EXAMPLE_PIN_CLK
int
prompt "CLK GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 36 if IDF_TARGET_ESP32S3
range 43 43 if IDF_TARGET_ESP32P4
config EXAMPLE_PIN_D0
int
prompt "D0 GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 37 if IDF_TARGET_ESP32S3
range 39 39 if IDF_TARGET_ESP32P4
if EXAMPLE_SDMMC_BUS_WIDTH_4
config EXAMPLE_PIN_D1
int
prompt "D1 GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 38 if IDF_TARGET_ESP32S3
range 40 40 if IDF_TARGET_ESP32P4
config EXAMPLE_PIN_D2
int
prompt "D2 GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 33 if IDF_TARGET_ESP32S3
range 41 41 if IDF_TARGET_ESP32P4
config EXAMPLE_PIN_D3
int
prompt "D3 GPIO number" if SOC_SDMMC_USE_GPIO_MATRIX
default 34 if IDF_TARGET_ESP32S3
range 42 42 if IDF_TARGET_ESP32P4
endif # EXAMPLE_SDMMC_BUS_WIDTH_4
config EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
depends on SOC_SDMMC_IO_POWER_EXTERNAL
bool "SD power supply comes from internal LDO IO (READ HELP!)"
default y
help
Only needed when the SD card is connected to specific IO pins which can be used for high-speed SDMMC.
Please read the schematic first and check if the SD VDD is connected to any internal LDO output.
Unselect this option if the SD card is powered by an external power supply.
config EXAMPLE_SD_PWR_CTRL_LDO_IO_ID
depends on SOC_SDMMC_IO_POWER_EXTERNAL && EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
int "LDO ID"
default 4 if IDF_TARGET_ESP32P4
help
Please read the schematic first and input your LDO ID.
endmenu
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/**
* ESP-Hosted Wi-Fi Functions for SD Card Example
*/
#include <string.h>
#include "esp_check.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_hosted.h"
#include "esp_hosted_wifi.h"
static const char *TAG = "sd_card_wifi";
void init_wifi(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// get co-processor app desc
esp_hosted_app_desc_t desc = { 0 };
if (ESP_OK == esp_hosted_get_coprocessor_app_desc(&desc)) {
ESP_LOGW(TAG, "Co-processor Name : %s", desc.project_name);
ESP_LOGW(TAG, "Co-processor Version: %s", desc.version);
ESP_LOGW(TAG, "Co-processor IDF Ver: %s", desc.idf_ver);
ESP_LOGW(TAG, "Co-processor Time : %s", desc.time);
ESP_LOGW(TAG, "Co-processor Date : %s", desc.date);
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
void do_wifi_scan(void)
{
uint16_t number = 10;
wifi_ap_record_t ap_info[number];
uint16_t ap_count = 0;
memset(ap_info, 0, sizeof(ap_info));
esp_wifi_scan_start(NULL, true);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
}
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef __ESP_HOSTED_WIFI_H__
#define __ESP_HOSTED_WIFI_H__
void init_wifi(void);
void do_wifi_scan(void);
#endif
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef __SD_CARD_EXAMPLE_COMMON_H__
#define __SD_CARD_EXAMPLE_COMMON_H__
#define EXAMPLE_MAX_CHAR_SIZE 64
// enable this to perform Wi-Fi functions in example
#define EXAMPLE_USE_WIFI 1
#endif
@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* SD card and FAT filesystem example.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
// This example uses SDMMC peripheral to communicate with SD card.
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "driver/sdmmc_host.h"
#include "sd_card_example_common.h"
#include "sd_card_functions.h"
#include "esp_hosted_wifi.h"
static const char *TAG = "example";
#define MOUNT_POINT "/sdcard"
#if !CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE
#error This example is to show how ESP-Hosted and SD-Card can both use SDMMC together. But ESP-Hosted is not configured to use SDIO as its interface.
#endif
void app_main(void)
{
esp_err_t ret;
#if EXAMPLE_USE_WIFI
init_wifi();
#endif
// mount the sd card at the sdmmc slot and mount point
ESP_ERROR_CHECK(sd_card_mount(CONFIG_EXAMPLE_SDMMC_SLOT, MOUNT_POINT));
#if EXAMPLE_USE_WIFI
ESP_LOGI(TAG, "Doing Wi-Fi Scan");
do_wifi_scan();
#endif
// Use POSIX and C standard library functions to work with files:
// First create a file.
const char *file_hello = MOUNT_POINT"/hello.txt";
char data[EXAMPLE_MAX_CHAR_SIZE];
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Hello", sd_card_get_card_name());
ret = sd_card_write_file(file_hello, data);
if (ret != ESP_OK) {
return;
}
const char *file_foo = MOUNT_POINT"/foo.txt";
// Check if destination file exists before renaming
struct stat st;
if (stat(file_foo, &st) == 0) {
// Delete it if it exists
unlink(file_foo);
}
// Rename original file
ESP_LOGI(TAG, "Renaming file %s to %s", file_hello, file_foo);
if (rename(file_hello, file_foo) != 0) {
ESP_LOGE(TAG, "Rename failed");
return;
}
ret = sd_card_read_file(file_foo);
if (ret != ESP_OK) {
return;
}
const char *file_nihao = MOUNT_POINT"/nihao.txt";
memset(data, 0, EXAMPLE_MAX_CHAR_SIZE);
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Nihao", sd_card_get_card_name());
// Write to file
ret = sd_card_write_file(file_nihao, data);
if (ret != ESP_OK) {
return;
}
// Read from file
ret = sd_card_read_file(file_nihao);
if (ret != ESP_OK) {
return;
}
// All done, unmount partition and disable SDMMC peripheral
sd_card_unmount(MOUNT_POINT);
ESP_LOGI(TAG, "Card unmounted");
#if EXAMPLE_USE_WIFI
ESP_LOGI(TAG, "Doing another Wi-Fi Scan");
do_wifi_scan();
#endif
}
@@ -0,0 +1,276 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/**
* SD Card Functions for SD Card Example
*/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/gpio.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_cmd.h"
#include "freertos/FreeRTOS.h"
#include "esp_idf_version.h"
#if SOC_SDMMC_IO_POWER_EXTERNAL
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
#endif
#include "sd_card_example_common.h"
#include "sd_card_functions.h"
/**
* On ESP-IDF master branch, the sdmmc host controller can only be initialised once
*
* This workaround ensures that either ESP-Hosted (if using SDIO as the transport) or
* the SD Card Example will initialise the sdmmc host control
*
* See the `README.md` for more details.
*/
#if CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0))
#define WORKAROUND_HOSTED_DOES_SDMMC_HOST_INIT 1
#else
#define WORKAROUND_HOSTED_DOES_SDMMC_HOST_INIT 0
#endif
static const char *TAG = "sd_card_fns";
static sdmmc_card_t *card = NULL;
static sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;
esp_err_t sd_card_write_file(const char *path, char *data)
{
ESP_LOGI(TAG, "Opening file %s", path);
FILE *f = fopen(path, "w");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return ESP_FAIL;
}
fprintf(f, data);
fclose(f);
ESP_LOGI(TAG, "File written");
return ESP_OK;
}
esp_err_t sd_card_read_file(const char *path)
{
ESP_LOGI(TAG, "Reading file %s", path);
FILE *f = fopen(path, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
char line[EXAMPLE_MAX_CHAR_SIZE];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
return ESP_OK;
}
#if CONFIG_EXAMPLE_PIN_CARD_POWER_RESET
esp_err_t sd_card_reset_card_power(void)
{
esp_err_t ret = ESP_FAIL;
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL<<CONFIG_EXAMPLE_PIN_CARD_POWER_RESET),
};
ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to config GPIO");
return ret;
}
ret = gpio_set_level(CONFIG_EXAMPLE_PIN_CARD_POWER_RESET, 1);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set GPIO level");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = gpio_set_level(CONFIG_EXAMPLE_PIN_CARD_POWER_RESET, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set GPIO level");
return ret;
}
return ESP_OK;
}
#endif // CONFIG_EXAMPLE_PIN_CARD_POWER_RESET
char * sd_card_get_card_name(void)
{
if (card) {
return card->cid.name;
} else {
return NULL;
}
}
#if WORKAROUND_HOSTED_DOES_SDMMC_HOST_INIT
static esp_err_t sdmmc_host_init_dummy(void)
{
return ESP_OK;
}
static esp_err_t sdmmc_host_deinit_dummy(void)
{
return ESP_OK;
}
#endif
esp_err_t sd_card_mount(int slot, const char * mount_point)
{
esp_err_t ret;
if (!mount_point) {
ESP_LOGE(TAG, "No mount point provided");
return ESP_FAIL;
}
#if CONFIG_IDF_TARGET_ESP32P4
// on ESP32-P4 Dev Board, the SDCard is on SDMMC Slot 0
// ESP-Hosted uses SDMMC Slot 1
if (slot != SDMMC_HOST_SLOT_0) {
ESP_LOGE(TAG, "For ESP32-P4, SD Card slot should be SDMMC_HOST_SLOT_0");
return ESP_FAIL;
}
#endif
// Options for mounting the filesystem.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
ESP_LOGI(TAG, "Initializing SD card");
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
// Please check its source code and implement error recovery when developing
// production applications.
ESP_LOGI(TAG, "Using SDMMC peripheral");
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 40MHz for SDMMC)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.slot = slot;
#if WORKAROUND_HOSTED_DOES_SDMMC_HOST_INIT
// The host controller is already managed by ESP-Hosted, so there is no need to add init and deinit again.
host.init = &sdmmc_host_init_dummy;
host.deinit = &sdmmc_host_deinit_dummy;
#endif
// For SoCs where the SD power can be supplied both via an internal or external (e.g. on-board LDO) power supply.
// When using specific IO pins (which can be used for ultra high-speed SDMMC) to connect to the SD card
// and the internal LDO power supply, we need to initialize the power supply first.
#if CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
sd_pwr_ctrl_ldo_config_t ldo_config = {
.ldo_chan_id = CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_IO_ID,
};
ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create a new on-chip LDO power control driver");
return ret;
}
host.pwr_ctrl_handle = pwr_ctrl_handle;
#endif
#if CONFIG_EXAMPLE_PIN_CARD_POWER_RESET
ESP_ERROR_CHECK(sd_card_reset_card_power());
#endif
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
// Set bus width to use:
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
slot_config.width = 4;
#else
slot_config.width = 1;
#endif
// On chips where the GPIOs used for SD card can be configured, set them in
// the slot_config structure:
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
slot_config.clk = CONFIG_EXAMPLE_PIN_CLK;
slot_config.cmd = CONFIG_EXAMPLE_PIN_CMD;
slot_config.d0 = CONFIG_EXAMPLE_PIN_D0;
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
slot_config.d1 = CONFIG_EXAMPLE_PIN_D1;
slot_config.d2 = CONFIG_EXAMPLE_PIN_D2;
slot_config.d3 = CONFIG_EXAMPLE_PIN_D3;
#endif // CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
#endif // CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
// Enable internal pullups on enabled pins. The internal pullups
// are insufficient however, please make sure 10k external pullups are
// connected on the bus. This is for debug / example purpose only.
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
ESP_LOGI(TAG, "Mounting filesystem");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. ");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return ret;
}
ESP_LOGI(TAG, "Filesystem mounted");
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
return ESP_OK;
}
esp_err_t sd_card_unmount(const char * mount_point)
{
esp_err_t ret;
if (!mount_point) {
ESP_LOGE(TAG, "No mount point provided");
return ESP_FAIL;
}
if (card) {
esp_vfs_fat_sdcard_unmount(mount_point, card);
// Deinitialize the power control driver if it was used
#if CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
if (pwr_ctrl_handle) {
ret = sd_pwr_ctrl_del_on_chip_ldo(pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to delete the on-chip LDO power control driver");
return ret;
}
}
#endif
return ESP_OK;
} else {
ESP_LOGE(TAG, "no SD Card mounted: cannot unmount");
return ESP_FAIL;
}
}
@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef __SD_CARD_FUNCTIONS_H__
#define __SD_CARD_FUNCTIONS_H__
esp_err_t sd_card_mount(int slot, const char * mount_point);
esp_err_t sd_card_unmount(const char * mount_point);
esp_err_t sd_card_write_file(const char *path, char *data);
esp_err_t sd_card_read_file(const char *path);
char * sd_card_get_card_name(void);
#if CONFIG_EXAMPLE_PIN_CARD_POWER_RESET
esp_err_t sd_card_reset_card_power(void);
#endif
#endif
@@ -0,0 +1,49 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import re
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.sdcard_sdmode
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_examples_sd_card_sdmmc(dut: Dut) -> None:
dut.expect('example: Initializing SD card', timeout=20)
dut.expect('example: Using SDMMC peripheral', timeout=10)
# Provide enough time for possible SD card formatting
dut.expect('Filesystem mounted', timeout=60)
# These lines are matched separately because of ASCII color codes in the output
name = dut.expect(re.compile(rb'Name: (\w+)\r'), timeout=10).group(1).decode()
_type = dut.expect(re.compile(rb'Type: (\S+)'), timeout=10).group(1).decode()
speed = dut.expect(re.compile(rb'Speed: (\S+)'), timeout=10).group(1).decode()
size = dut.expect(re.compile(rb'Size: (\S+)'), timeout=10).group(1).decode()
logging.info('Card {} {} {}MHz {} found'.format(name, _type, speed, size))
message_list1 = (
'Opening file /sdcard/hello.txt',
'File written',
'Renaming file /sdcard/hello.txt to /sdcard/foo.txt',
'Reading file /sdcard/foo.txt',
"Read from file: 'Hello {}!'".format(name),
)
sd_card_format = re.compile(str.encode('Formatting card, allocation unit size=\\S+'))
message_list2 = (
"file doesn't exist, formatting done",
'Opening file /sdcard/nihao.txt',
'File written',
'Reading file /sdcard/nihao.txt',
"Read from file: 'Nihao {}!'".format(name),
'Card unmounted',
)
for msg in message_list1:
dut.expect_exact(msg, timeout=30)
dut.expect(sd_card_format, timeout=180) # Provide enough time for SD card FATFS format operation
for msg in message_list2:
dut.expect_exact(msg, timeout=30)
@@ -0,0 +1,4 @@
CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED=y
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_EXAMPLE_FORMAT_SD_CARD=y
@@ -0,0 +1 @@
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
@@ -0,0 +1 @@
CONFIG_WIFI_RMT_NVS_ENABLED=n