Merge branch 'sdio-example-4bit-bringup' into 'master'

example(sdio_slave): add docs and examples for SI testing

See merge request espressif/esp-idf!49371
This commit is contained in:
Michael (XIAO Xufeng)
2026-08-05 16:48:38 +08:00
34 changed files with 1492 additions and 36 deletions

View File

@@ -585,7 +585,7 @@ examples/peripherals/rmt/stepper_motor:
- esp_driver_rmt
- soc
examples/peripherals/sdio/host:
examples/peripherals/sdio/basic/host:
disable:
- if: IDF_TARGET == "esp32p4"
temporary: true
@@ -604,7 +604,7 @@ examples/peripherals/sdio/host:
- esp_driver_sdio
- soc
examples/peripherals/sdio/slave:
examples/peripherals/sdio/basic/slave:
disable:
- if: SOC_SDIO_SLAVE_SUPPORTED != 1
disable_test:
@@ -616,6 +616,30 @@ examples/peripherals/sdio/slave:
- esp_driver_sdio
- soc
examples/peripherals/sdio/hw_test/host:
disable:
- if: SOC_SDMMC_HOST_SUPPORTED != 1
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32p4"]
temporary: true
reason: lack of runners
depends_components:
- esp_hal_sd
- sdmmc
- esp_driver_sdmmc
- soc
examples/peripherals/sdio/hw_test/slave:
disable:
- if: SOC_SDIO_SLAVE_SUPPORTED != 1
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32c5", "esp32c6", "esp32c61"]
temporary: true
reason: lack of runners
depends_components:
- esp_driver_sdio
- soc
examples/peripherals/sigma_delta:
disable:
- if: SOC_SDM_SUPPORTED != 1

View File

@@ -59,7 +59,7 @@ disconnected, the config options are still valid, and the host example will stil
slave meet the "all pins should be pulled up" requirement.
For the SDIO Slave, CMD and DAT0-3 lines require to be pulled up (suggested resistor value: 10 KOhm) even in 1-bit mode
or SPI mode, which is required by the SD specification. See *Board Compability* below for details.
or SPI mode, which is required by the SD specification. See *Board Compatibility* below for details.
In 1-bit mode, the host can make use of DAT2 and DAT3, however the slave should
leave them alone but pulled up.
@@ -128,12 +128,12 @@ modules and devkits.
## About `esp_serial_slave_link` component used in the host example
The host example is based on [esp_serial_slave_link component](https://components.espressif.com/components/espressif/esp_serial_slave_link), which is used to communicate to a ESP slave device.
The host example is based on the [esp_serial_slave_link component](https://components.espressif.com/components/espressif/esp_serial_slave_link), which is used to communicate with an ESP slave device.
The component can be installed by esp component manager. Since this example already installed it, no need to re-installed it again, but if you want to install this component in your own project, you can input the following command:
The component can be installed by the ESP component manager. Since this example already installs it, there is no need to install it again. If you want to use this component in your own project, run the following command:
```
idf.py add-dependency espressif/esp_serial_slave_link
```
If the dependency is added, you can check `idf_component.yml` for more detail. When building this example or other projects with managed components, the component manager will search for the required components online and download them into the `managed_componets` folder.
If the dependency is added, you can check `idf_component.yml` for more details. When building this example or other projects with managed components, the component manager will search for the required components online and download them into the `managed_components` folder.

View File

@@ -1,11 +1,8 @@
/* SDIO example, host (uses sdmmc_host/sdspi_host driver)
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.
*/
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

View File

@@ -1,11 +1,8 @@
/* SDIO example, slave (uses sdio slave driver)
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.
*/
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "driver/sdio_slave.h"
#include "esp_log.h"
#include "sys/queue.h"

View File

@@ -0,0 +1,176 @@
### SDIO Channel Test Example
## Introduction
This example pair turns the SDIO host/slave examples into a channel test app
for lab work.
Use it when you want to:
- check whether a board pair can communicate over SDIO,
- compare 1-line and 4-line transfer modes,
- compare default-speed and high-speed clock settings,
- generate repeated traffic that is easy to capture with a scope or logic
analyzer,
- validate that the received data still matches a known fixed pattern.
The `host` app provides an interactive serial menu:
1. Select 1-line or 4-line mode and select default-speed or high-speed clock.
2. Start link initialization.
3. Enter transmit mode or receive mode.
4. Run one transfer per second until `z` is pressed to return to the menu.
The `slave` app is a passive endpoint. Once initialized, it keeps enough
buffers ready so the host can repeatedly send or receive a fixed 16-byte test
frame without an extra handshake before each round.
The fixed test frame is:
`FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00`
Both sides validate received data against this exact sequence and print a
success or failure log for each completed transfer.
## Wiring
Connect the host and slave boards exactly as described in the SDIO programming
guide and the basic SDIO example for the selected targets.
For this signal-integrity test, connect all SDIO signal lines between the two
boards and always provide a solid common ground. Keep the wires short and avoid
loose return paths.
If link initialization fails, the host deinitializes the bus and returns to the
setup menu so the user can choose a different width/speed combination.
## Signal Integrity Notes
This example can exercise either a single active data lane or four active data
lanes. The waveforms below are conceptual. They show the fixed `FF 00`
payload pattern used by this example and intentionally omit the exact CRC bits.
### 1-line mode
```text
CLK : _|-|_|-|_|-|_|-|_|-|_|-|_|-|_
CMD : -----------------------------
DAT0 : _|--------________--------________...
DAT1 : -----------------------------
DAT2 : -----------------------------
DAT3 : -----------------------------
^start 0xFF 0x00 0xFF 0x00
```
### 4-line mode
```text
CLK : _|-|_|-|_|-|_|-|_|-|_|-|_|-|_
CMD : -----------------------------
DAT0 : _|--__--__--__--__--__--__...
DAT1 : _|--__--__--__--__--__--__...
DAT2 : _|--__--__--__--__--__--__...
DAT3 : _|--__--__--__--__--__--__...
^start FF 00 FF 00 ...
```
In 4-line mode, the repeated `FF 00` frame makes all four data lines switch
at the same time during the `0xF -> 0x0` and `0x0 -> 0xF` nibble
transitions. This is useful when checking simultaneous switching noise and
grounding quality.
To confirm the wiring before analyzing signal quality:
1. If CMD and CLK are correct, the host should complete the command-only
initialization path and print `SDIO link initialized successfully`. In this
example, both `sdmmc_card_init()` and `enable_slave_function()` still use
only CMD-based transactions before the first data transfer starts.
2. If DAT0 is correct, 1-line mode should work. This example does have a real
1-line-only data phase when you choose `1-line`: the first data-line
transfer is the first CMD53 transaction after initialization succeeds.
3. If DAT1-DAT3 are connected but 4-line mode still fails, use a logic analyzer
or oscilloscope to check whether the slave is responding on DAT1-DAT3 during
4-line reads. The 4-line flow does not first send payload in 1-line mode, so
a 4-line failure after a passing 1-line test points to DAT1-DAT3. No
response usually indicates wiring or pull-up issues;
response with corrupted data usually indicates signal-quality issues.
Use a logic analyzer or oscilloscope when checking these nodes, and make sure
the pull-ups and board-specific SDIO requirements are correct. For more
signal-quality analysis and mitigation guidance, see the SDIO programming guide.
## How To Use
## 1. Build And Flash
Flash the `host` app to the host-capable board and the `slave` app to the
slave-capable board.
For example, in a manual setup with ESP32 host and ESP32-C6 slave:
- flash `examples/peripherals/sdio/hw_test/host` to the ESP32 host board
- flash `examples/peripherals/sdio/hw_test/slave` to the ESP32-C6 slave board
## 2. Open The Host Console
After reset, the host prints a setup menu. Use it to select:
- line width: 1-line or 4-line
- speed: default-speed or high-speed
Then start SDIO initialization.
If initialization fails, the host prints the error and returns to the setup
menu.
## 3. Choose The Traffic Direction
Once the link is up, use the second menu:
- transmit mode: host sends the fixed 16-byte pattern once per second
- receive mode: host reads the fixed 16-byte pattern once per second
- `z`: stop the current loop and return to the second menu
No extra synchronization is performed before each round. The slave is expected
to be ready already.
## 4. Adjust Drive Strength (Optional)
If you want to compare how drive strength affects the observed waveforms or
error rate, change it before starting link initialization:
- on the host, use the setup menu keys `k`, `m`, and `g` to configure CLK,
CMD, and DATA drive strength
- on the slave, edit `SLAVE_CLK_DRIVE_CAP`, `SLAVE_CMD_DRIVE_CAP`, and
`SLAVE_DATA_DRIVE_CAP` in `slave/main/app_main.c`, then rebuild and flash
Both sides print the active drive-strength settings during startup.
## 5. Check The Logs
Watch for:
- initialization success or failure,
- transfer success or driver error codes,
- pattern validation success or failure on both boards.
The transfer interval is intentionally slow so repeated waveforms are easy to
capture.
## Pytest Coverage
`pytest_sdio_hw_test.py` drives the same host serial menu that a user sees.
The automated pytest cases exercise these width/speed combinations on the
available SDIO multi-device runners:
- 1-line + default-speed
- 1-line + high-speed
- 4-line + default-speed
- 4-line + high-speed
For each combination, the test enters both host transmit mode and host receive
mode, waits for 5 successful transfers, and fails immediately if any transfer
or pattern-validation error log appears. Other SDIO-capable board pairs can
still use this example manually if their wiring and pin assignments follow the
SDIO programming guide.

View File

@@ -0,0 +1,8 @@
# 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(sdio_si_test)

View File

@@ -0,0 +1,5 @@
| Supported Targets | ESP32 | ESP32-P4 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | --------- |
For wiring, setup flow, and signal-integrity guidance, see `README.md` in the
parent folder.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "app_main.c"
PRIV_REQUIRES esp_driver_gpio esp_driver_sdmmc esp_driver_uart
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,51 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_ADJUSTABLE_PIN
bool
default SOC_SDMMC_USE_GPIO_MATRIX
config EXAMPLE_PIN_CMD
int "CMD (MOSI) GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 4 if IDF_TARGET_ESP32P4
default 15
config EXAMPLE_PIN_CLK
int "CLK GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 33 if IDF_TARGET_ESP32P4
default 14
config EXAMPLE_PIN_D0
int "D0 (MISO) GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 32 if IDF_TARGET_ESP32P4
default 2
config EXAMPLE_PIN_D1
int "D1 (INTR) GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 23 if IDF_TARGET_ESP32P4
default 4
config EXAMPLE_PIN_D2
int "D2 GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 53 if IDF_TARGET_ESP32P4
default 12
config EXAMPLE_PIN_D3
int "D3 (CS) GPIO number"
depends on EXAMPLE_ADJUSTABLE_PIN
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 5 if IDF_TARGET_ESP32P4
default 13
endmenu

View File

@@ -0,0 +1,674 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ctype.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "driver/uart.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_log_buffer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "sdmmc_cmd.h"
#include "soc/sdmmc_pins.h"
static const char *TAG = "example_host";
#define TEST_FRAME_LEN 16
#define SDIO_BUFFER_SIZE 128
#define LOOP_INTERVAL_MS 1000
#define INIT_RETRY_COUNT 5
#define INIT_RETRY_DELAY_MS 1000
#define IO_TIMEOUT_MS 1000
#define POLL_SLICE_MS 50
#define DEBUG_FRAME_LOG_LIMIT 8
#define SDIO_FUNC_NUM 1
#define SDIO_FUNC1_EN_MASK BIT(1)
#define SDIO_DATA_END_ADDR 0x1f800
#define DRIVE_CAP_KEEP_DEFAULT (-1)
#if CONFIG_EXAMPLE_ADJUSTABLE_PIN
#define PIN_CLK CONFIG_EXAMPLE_PIN_CLK
#define PIN_CMD CONFIG_EXAMPLE_PIN_CMD
#define PIN_D0 CONFIG_EXAMPLE_PIN_D0
#define PIN_D1 CONFIG_EXAMPLE_PIN_D1
#define PIN_D2 CONFIG_EXAMPLE_PIN_D2
#define PIN_D3 CONFIG_EXAMPLE_PIN_D3
#else
#define PIN_CLK SDMMC_SLOT1_IOMUX_PIN_NUM_CLK
#define PIN_CMD SDMMC_SLOT1_IOMUX_PIN_NUM_CMD
#define PIN_D0 SDMMC_SLOT1_IOMUX_PIN_NUM_D0
#define PIN_D1 SDMMC_SLOT1_IOMUX_PIN_NUM_D1
#define PIN_D2 SDMMC_SLOT1_IOMUX_PIN_NUM_D2
#define PIN_D3 SDMMC_SLOT1_IOMUX_PIN_NUM_D3
#endif
typedef enum {
LINK_WIDTH_1BIT = 1,
LINK_WIDTH_4BIT = 4,
} link_width_t;
typedef enum {
LINK_SPEED_PROBING = 0,
LINK_SPEED_DEFAULT,
LINK_SPEED_HIGH,
} link_speed_t;
typedef struct {
int clk_drive;
int cmd_drive;
int data_drive;
} drive_strength_config_t;
typedef struct {
link_width_t width;
link_speed_t speed;
drive_strength_config_t drive_strength;
} link_config_t;
typedef struct {
sdmmc_card_t *card;
bool host_initialized;
bool link_ready;
} host_link_t;
static const uint8_t s_pattern[TEST_FRAME_LEN] = {
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
};
static DRAM_DMA_ALIGNED_ATTR uint8_t s_tx_frame[TEST_FRAME_LEN];
static DRAM_DMA_ALIGNED_ATTR uint8_t s_rx_frame[TEST_FRAME_LEN];
static int read_console_key_blocking(void);
static const char *link_width_name(link_width_t width)
{
return (width == LINK_WIDTH_4BIT) ? "4-line" : "1-line";
}
static const char *link_speed_name(link_speed_t speed)
{
switch (speed) {
case LINK_SPEED_PROBING:
return "400K";
case LINK_SPEED_HIGH:
return "high-speed";
case LINK_SPEED_DEFAULT:
default:
return "default-speed";
}
}
static const char *drive_strength_name(int value)
{
switch (value) {
case DRIVE_CAP_KEEP_DEFAULT:
return "default";
case GPIO_DRIVE_CAP_0:
return "cap0";
case GPIO_DRIVE_CAP_1:
return "cap1";
case GPIO_DRIVE_CAP_2:
return "cap2";
case GPIO_DRIVE_CAP_3:
return "cap3";
default:
return "unknown";
}
}
static int prompt_drive_strength_selection(const char *label, int current_value)
{
printf("\nSelect %s drive strength:\n", label);
printf(" n - keep default\n");
printf(" 0 - GPIO_DRIVE_CAP_0\n");
printf(" 1 - GPIO_DRIVE_CAP_1\n");
printf(" 2 - GPIO_DRIVE_CAP_2\n");
printf(" 3 - GPIO_DRIVE_CAP_3\n");
printf("Current: %s\n", drive_strength_name(current_value));
fflush(stdout);
switch (read_console_key_blocking()) {
case 'n':
return DRIVE_CAP_KEEP_DEFAULT;
case '0':
return GPIO_DRIVE_CAP_0;
case '1':
return GPIO_DRIVE_CAP_1;
case '2':
return GPIO_DRIVE_CAP_2;
case '3':
return GPIO_DRIVE_CAP_3;
default:
printf("Unknown selection, keeping previous value\n");
fflush(stdout);
return current_value;
}
}
static void fill_test_pattern(uint8_t *buffer, size_t len)
{
for (size_t i = 0; i < len; ++i) {
buffer[i] = s_pattern[i % TEST_FRAME_LEN];
}
}
static bool validate_test_pattern(const uint8_t *buffer, size_t len)
{
if (len != TEST_FRAME_LEN) {
return false;
}
return memcmp(buffer, s_pattern, TEST_FRAME_LEN) == 0;
}
static int find_pattern_mismatch(const uint8_t *buffer, size_t len)
{
size_t compare_len = (len < TEST_FRAME_LEN) ? len : TEST_FRAME_LEN;
for (size_t i = 0; i < compare_len; ++i) {
if (buffer[i] != s_pattern[i]) {
return (int)i;
}
}
if (len != TEST_FRAME_LEN) {
return (int)compare_len;
}
return -1;
}
static void log_frame_debug(const char *label, uint32_t frame_index, const uint8_t *buffer, size_t len)
{
ESP_LOGI(TAG, "%s frame=%" PRIu32 " len=%u", label, frame_index, (unsigned)len);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_INFO);
}
static void log_gpio_drive_capability(const char *label, gpio_num_t pin)
{
gpio_drive_cap_t capability = GPIO_DRIVE_CAP_DEFAULT;
esp_err_t err = gpio_get_drive_capability(pin, &capability);
if (err == ESP_OK) {
ESP_LOGI(TAG, "%s GPIO%d drive capability: %s (%d)", label, pin, drive_strength_name(capability), capability);
} else {
ESP_LOGW(TAG, "Failed to read drive capability for GPIO%d: %s", pin, esp_err_to_name(err));
}
}
static void log_host_drive_strengths(link_width_t width, const char *label)
{
log_gpio_drive_capability(label, PIN_CLK);
log_gpio_drive_capability(label, PIN_CMD);
log_gpio_drive_capability(label, PIN_D0);
if (width == LINK_WIDTH_4BIT) {
log_gpio_drive_capability(label, PIN_D1);
log_gpio_drive_capability(label, PIN_D2);
log_gpio_drive_capability(label, PIN_D3);
}
}
static esp_err_t apply_gpio_drive_strength(gpio_num_t pin, int drive_strength)
{
if (drive_strength == DRIVE_CAP_KEEP_DEFAULT) {
return ESP_OK;
}
return gpio_set_drive_capability(pin, (gpio_drive_cap_t)drive_strength);
}
static esp_err_t apply_host_drive_strengths(const link_config_t *config)
{
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_CLK, config->drive_strength.clk_drive), TAG, "set CLK drive failed");
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_CMD, config->drive_strength.cmd_drive), TAG, "set CMD drive failed");
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D0, config->drive_strength.data_drive), TAG, "set D0 drive failed");
if (config->width == LINK_WIDTH_4BIT) {
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D1, config->drive_strength.data_drive), TAG, "set D1 drive failed");
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D2, config->drive_strength.data_drive), TAG, "set D2 drive failed");
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D3, config->drive_strength.data_drive), TAG, "set D3 drive failed");
}
return ESP_OK;
}
static esp_err_t enable_slave_function(sdmmc_card_t *card)
{
uint8_t fn_enable = 0;
ESP_RETURN_ON_ERROR(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &fn_enable), TAG, "read FN_ENABLE failed");
fn_enable |= SDIO_FUNC1_EN_MASK;
ESP_RETURN_ON_ERROR(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, fn_enable, NULL), TAG, "write FN_ENABLE failed");
uint8_t fn_ready = 0;
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(IO_TIMEOUT_MS);
do {
esp_err_t err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &fn_ready);
if (err != ESP_OK) {
return err;
}
if ((fn_ready & SDIO_FUNC1_EN_MASK) != 0) {
return ESP_OK;
}
vTaskDelay(pdMS_TO_TICKS(POLL_SLICE_MS));
} while (xTaskGetTickCount() < deadline);
return ESP_ERR_TIMEOUT;
}
static esp_err_t send_one_frame(host_link_t *link, const uint8_t *buffer, size_t len)
{
uint32_t addr = SDIO_DATA_END_ADDR - len;
return sdmmc_io_write_bytes(link->card, SDIO_FUNC_NUM, addr, buffer, len);
}
static esp_err_t receive_one_frame(host_link_t *link, uint8_t *buffer, size_t *out_len)
{
esp_err_t err = sdmmc_io_read_bytes(link->card, SDIO_FUNC_NUM, SDIO_DATA_END_ADDR - TEST_FRAME_LEN, buffer, TEST_FRAME_LEN);
if (err != ESP_OK) {
return err;
}
*out_len = TEST_FRAME_LEN;
return ESP_OK;
}
static void slave_power_on(void)
{
gpio_config_t gpio_cfg = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << GPIO_NUM_18,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
ESP_LOGI(TAG, "power on slave");
gpio_set_level(GPIO_NUM_18, 1);
}
static void configure_console(void)
{
if (uart_is_driver_installed((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM)) {
return;
}
setvbuf(stdin, NULL, _IONBF, 0);
ESP_ERROR_CHECK(uart_driver_install((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
}
static int read_console_key_with_timeout(int timeout_ms)
{
uint8_t byte = 0;
int elapsed_ms = 0;
while (elapsed_ms <= timeout_ms) {
int wait_ms = (timeout_ms == 0) ? 0 : POLL_SLICE_MS;
if (timeout_ms > 0 && (timeout_ms - elapsed_ms) < wait_ms) {
wait_ms = timeout_ms - elapsed_ms;
}
int read_len = uart_read_bytes((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
&byte,
1,
pdMS_TO_TICKS(wait_ms));
if (read_len > 0) {
if (byte == '\r' || byte == '\n') {
continue;
}
return tolower(byte);
}
if (timeout_ms == 0) {
break;
}
elapsed_ms += wait_ms;
}
return -1;
}
static int read_console_key_blocking(void)
{
while (true) {
uint8_t byte = 0;
int read_len = uart_read_bytes((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
&byte,
1,
portMAX_DELAY);
if (read_len > 0) {
if (byte == '\r' || byte == '\n') {
continue;
}
return tolower(byte);
}
}
}
static bool wait_for_stop_key(void)
{
for (int elapsed = 0; elapsed < LOOP_INTERVAL_MS; elapsed += POLL_SLICE_MS) {
int key = read_console_key_with_timeout(0);
if (key == 'z') {
return true;
}
if (key >= 0) {
ESP_LOGW(TAG, "Ignoring key '%c' while transfer loop is running. Press z to stop.", key);
}
vTaskDelay(pdMS_TO_TICKS(POLL_SLICE_MS));
}
return false;
}
static void print_setup_menu(const link_config_t *config)
{
printf("\n=== SDIO Link Setup ===\n");
printf("Current width: %s\n", link_width_name(config->width));
printf("Current speed: %s\n", link_speed_name(config->speed));
printf("CLK drive: %s\n", drive_strength_name(config->drive_strength.clk_drive));
printf("CMD drive: %s\n", drive_strength_name(config->drive_strength.cmd_drive));
printf("DATA drive: %s\n", drive_strength_name(config->drive_strength.data_drive));
printf("Select bus width:\n");
printf(" 1 - 1-line\n");
printf(" 4 - 4-line\n");
printf("Select clock mode:\n");
printf(" p - 400K\n");
printf(" d - default-speed\n");
printf(" h - high-speed\n");
printf("Drive strength:\n");
printf(" k - configure CLK drive\n");
printf(" m - configure CMD drive\n");
printf(" g - configure DATA drive\n");
printf("Press s to start link initialization\n");
fflush(stdout);
}
static void print_traffic_menu(const link_config_t *config)
{
printf("\n=== SDIO Traffic Menu ===\n");
printf("Current link: %s, %s\n", link_width_name(config->width), link_speed_name(config->speed));
printf("Press t for transmit mode\n");
printf("Press r for receive mode\n");
printf("Press x to close the link\n");
fflush(stdout);
}
static void enable_bus_pullups(link_width_t width)
{
const gpio_num_t pins_1bit[] = {PIN_CMD, PIN_CLK, PIN_D0};
const gpio_num_t pins_4bit[] = {PIN_CMD, PIN_CLK, PIN_D0, PIN_D1, PIN_D2, PIN_D3};
const gpio_num_t *pins = (width == LINK_WIDTH_4BIT) ? pins_4bit : pins_1bit;
size_t pin_count = (width == LINK_WIDTH_4BIT)
? sizeof(pins_4bit) / sizeof(pins_4bit[0])
: sizeof(pins_1bit) / sizeof(pins_1bit[0]);
for (size_t i = 0; i < pin_count; ++i) {
gpio_pullup_en(pins[i]);
gpio_pulldown_dis(pins[i]);
}
}
static void host_link_deinit(host_link_t *link)
{
if (link == NULL) {
return;
}
if (link->card != NULL && link->card->host.dma_aligned_buffer != NULL) {
free(link->card->host.dma_aligned_buffer);
link->card->host.dma_aligned_buffer = NULL;
}
if (link->host_initialized) {
esp_err_t err = sdmmc_host_deinit();
if (err != ESP_OK) {
ESP_LOGW(TAG, "sdmmc_host_deinit failed: %s", esp_err_to_name(err));
}
}
free(link->card);
memset(link, 0, sizeof(*link));
}
static esp_err_t host_link_init(const link_config_t *config, host_link_t *link)
{
esp_err_t err;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
host.flags = ((config->width == LINK_WIDTH_4BIT) ? SDMMC_HOST_FLAG_4BIT : SDMMC_HOST_FLAG_1BIT)
| SDMMC_HOST_FLAG_ALLOC_ALIGNED_BUF;
switch (config->speed) {
case LINK_SPEED_PROBING:
host.max_freq_khz = SDMMC_FREQ_PROBING;
break;
case LINK_SPEED_HIGH:
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
break;
case LINK_SPEED_DEFAULT:
default:
host.max_freq_khz = SDMMC_FREQ_DEFAULT;
break;
}
slot_config.width = config->width;
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
slot_config.clk = PIN_CLK;
slot_config.cmd = PIN_CMD;
slot_config.d0 = PIN_D0;
slot_config.d1 = PIN_D1;
slot_config.d2 = PIN_D2;
slot_config.d3 = PIN_D3;
#endif
link->card = calloc(1, sizeof(sdmmc_card_t));
if (link->card == NULL) {
return ESP_ERR_NO_MEM;
}
err = sdmmc_host_init();
if (err != ESP_OK) {
host_link_deinit(link);
return err;
}
link->host_initialized = true;
err = sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
if (err != ESP_OK) {
host_link_deinit(link);
return err;
}
for (int attempt = 0; attempt < INIT_RETRY_COUNT; ++attempt) {
err = sdmmc_card_init(&host, link->card);
if (err == ESP_OK) {
break;
}
ESP_LOGW(TAG, "sdmmc_card_init failed (%s), retrying...", esp_err_to_name(err));
vTaskDelay(pdMS_TO_TICKS(INIT_RETRY_DELAY_MS));
}
if (err != ESP_OK) {
host_link_deinit(link);
return err;
}
enable_bus_pullups(config->width);
log_host_drive_strengths(config->width, "Host default");
err = apply_host_drive_strengths(config);
if (err != ESP_OK) {
host_link_deinit(link);
return err;
}
log_host_drive_strengths(config->width, "Host active");
err = enable_slave_function(link->card);
if (err != ESP_OK) {
host_link_deinit(link);
return err;
}
link->link_ready = true;
return ESP_OK;
}
static void run_transmit_mode(host_link_t *link)
{
fill_test_pattern(s_tx_frame, sizeof(s_tx_frame));
printf("Entering transmit mode\n");
fflush(stdout);
while (true) {
esp_err_t err = send_one_frame(link, s_tx_frame, sizeof(s_tx_frame));
if (err == ESP_OK) {
printf("TX transfer success\n");
} else {
printf("TX transfer error: %s\n", esp_err_to_name(err));
}
fflush(stdout);
if (wait_for_stop_key()) {
break;
}
}
}
static void run_receive_mode(host_link_t *link)
{
uint32_t frame_index = 0;
printf("Entering receive mode\n");
fflush(stdout);
while (true) {
size_t received_len = 0;
esp_err_t err = receive_one_frame(link, s_rx_frame, &received_len);
++frame_index;
if (err == ESP_OK) {
bool valid = validate_test_pattern(s_rx_frame, received_len);
if (frame_index <= DEBUG_FRAME_LOG_LIMIT || !valid) {
log_frame_debug("RX", frame_index, s_rx_frame, received_len);
}
if (valid) {
printf("RX pattern validation: success\n");
} else {
int mismatch_index = find_pattern_mismatch(s_rx_frame, received_len);
if (mismatch_index >= 0 && mismatch_index < TEST_FRAME_LEN && received_len > (size_t)mismatch_index) {
ESP_LOGE(TAG,
"RX mismatch at byte %d: expected 0x%02X got 0x%02X",
mismatch_index,
s_pattern[mismatch_index],
s_rx_frame[mismatch_index]);
} else {
ESP_LOGE(TAG,
"RX length mismatch: expected %u got %u",
TEST_FRAME_LEN,
(unsigned)received_len);
}
printf("RX pattern validation: failure\n");
}
} else {
printf("RX transfer error: %s\n", esp_err_to_name(err));
}
fflush(stdout);
if (wait_for_stop_key()) {
break;
}
}
}
void app_main(void)
{
link_config_t config = {
.width = LINK_WIDTH_1BIT,
.speed = LINK_SPEED_DEFAULT,
.drive_strength = {
.clk_drive = DRIVE_CAP_KEEP_DEFAULT,
.cmd_drive = DRIVE_CAP_KEEP_DEFAULT,
.data_drive = DRIVE_CAP_KEEP_DEFAULT,
},
};
slave_power_on();
configure_console();
while (true) {
host_link_t link = {0};
while (!link.link_ready) {
print_setup_menu(&config);
switch (read_console_key_blocking()) {
case '1':
config.width = LINK_WIDTH_1BIT;
break;
case '4':
config.width = LINK_WIDTH_4BIT;
break;
case 'd':
config.speed = LINK_SPEED_DEFAULT;
break;
case 'p':
config.speed = LINK_SPEED_PROBING;
break;
case 'h':
config.speed = LINK_SPEED_HIGH;
break;
case 'k':
config.drive_strength.clk_drive = prompt_drive_strength_selection("CLK", config.drive_strength.clk_drive);
break;
case 'm':
config.drive_strength.cmd_drive = prompt_drive_strength_selection("CMD", config.drive_strength.cmd_drive);
break;
case 'g':
config.drive_strength.data_drive = prompt_drive_strength_selection("DATA", config.drive_strength.data_drive);
break;
case 's': {
esp_err_t err = host_link_init(&config, &link);
if (err == ESP_OK) {
printf("SDIO link initialized successfully\n");
} else {
printf("SDIO link initialization failed: %s\n", esp_err_to_name(err));
host_link_deinit(&link);
}
fflush(stdout);
break;
}
default:
printf("Unknown selection\n");
fflush(stdout);
break;
}
}
while (link.link_ready) {
print_traffic_menu(&config);
switch (read_console_key_blocking()) {
case 't':
run_transmit_mode(&link);
break;
case 'r':
run_receive_mode(&link);
break;
case 'x':
host_link_deinit(&link);
printf("SDIO link closed\n");
fflush(stdout);
break;
default:
printf("Unknown selection\n");
fflush(stdout);
break;
}
}
}
}

View File

@@ -0,0 +1 @@
CONFIG_FREERTOS_HZ=1000

View File

@@ -0,0 +1,162 @@
# SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
import pytest
from pytest_embedded_idf import IdfDut
HOST_PATH = os.path.join(os.path.dirname(__file__), 'host')
SLAVE_PATH = os.path.join(os.path.dirname(__file__), 'slave')
TRANSFER_REPEAT_COUNT = 5
HOST_FAILURE_PATTERNS = [
'TX transfer error:',
'RX transfer error:',
'RX pattern validation: failure',
]
SLAVE_FAILURE_PATTERNS = [
'RX pattern validation: failure',
]
_WIDTH_SPEED_PARAMS = [
pytest.param('1', 'd', id='1line_ds'),
pytest.param('1', 'h', id='1line_hs'),
pytest.param('4', 'd', id='4line_ds'),
pytest.param('4', 'h', id='4line_hs'),
]
def parameter_expand(existing_parameters: list[list[str]], value_list: list[str]) -> list[list[str]]:
ret = []
for param in existing_parameters:
ret.extend([param + [value] for value in value_list])
return ret
esp32_32_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32']]
esp32_c6_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32c6']]
esp32p4_c5_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32p4|esp32c5']]
esp32_c61_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32c61']]
esp32_param_default = [pytest.param(*param) for param in parameter_expand(esp32_32_param, ['default|default'])]
c6_param_default = [pytest.param(*param) for param in parameter_expand(esp32_c6_param, ['default|default'])]
c5_param_default = [pytest.param(*param) for param in parameter_expand(esp32p4_c5_param, ['default|default'])]
c61_param_default = [pytest.param(*param) for param in parameter_expand(esp32_c61_param, ['default|default'])]
def _send_choice(dut: IdfDut, choice: str) -> None:
dut.write(f'\n{choice}\n')
def _expect_success_or_fail(dut: IdfDut, success_pattern: str, failure_patterns: list[str], timeout: int = 10) -> None:
matched = dut.expect_exact([success_pattern, *failure_patterns], timeout=timeout)
matched_text = matched.decode() if isinstance(matched, bytes) else str(matched)
assert matched_text == success_pattern, f'Expected "{success_pattern}", got "{matched_text}"'
def _expect_setup_menu(host: IdfDut) -> None:
host.expect_exact('=== SDIO Link Setup ===', timeout=10)
host.expect_exact('Select bus width:', timeout=5)
host.expect_exact('Select clock mode:', timeout=5)
host.expect_exact('Press s to start link initialization', timeout=5)
def _expect_transfer_menu(host: IdfDut) -> None:
host.expect_exact('=== SDIO Traffic Menu ===', timeout=10)
host.expect_exact('Press t for transmit mode', timeout=5)
host.expect_exact('Press r for receive mode', timeout=5)
host.expect_exact('Press x to close the link', timeout=5)
def _select_link_mode(host: IdfDut, width: str, speed: str) -> None:
_expect_setup_menu(host)
_send_choice(host, width)
_send_choice(host, speed)
_send_choice(host, 's')
host.expect_exact('SDIO link initialized successfully', timeout=15)
_expect_transfer_menu(host)
def _run_transmit_check(host: IdfDut, slave: IdfDut) -> None:
_send_choice(host, 't')
host.expect_exact('Entering transmit mode', timeout=5)
for _ in range(TRANSFER_REPEAT_COUNT):
_expect_success_or_fail(host, 'TX transfer success', HOST_FAILURE_PATTERNS)
_expect_success_or_fail(slave, 'RX pattern validation: success', SLAVE_FAILURE_PATTERNS)
_send_choice(host, 'z')
_expect_transfer_menu(host)
def _run_receive_check(host: IdfDut, slave: IdfDut) -> None:
_send_choice(host, 'r')
host.expect_exact('Entering receive mode', timeout=5)
for _ in range(TRANSFER_REPEAT_COUNT):
_expect_success_or_fail(host, 'RX pattern validation: success', HOST_FAILURE_PATTERNS)
_expect_success_or_fail(slave, 'TX buffer recycled', SLAVE_FAILURE_PATTERNS)
_send_choice(host, 'z')
_expect_transfer_menu(host)
def _run_channel_quality_test(
dut: tuple[IdfDut, IdfDut],
width_choice: str,
speed_choice: str,
) -> None:
host = dut[0]
slave = dut[1]
host.pexpect_proc.timeout = 10
slave.pexpect_proc.timeout = 10
slave.expect_exact('SDIO slave ready for channel test', timeout=15)
_select_link_mode(host, width_choice, speed_choice)
_run_transmit_check(host, slave)
_run_receive_check(host, slave)
@pytest.mark.sdio_multidev_32_c6
@pytest.mark.parametrize('count', [2], indirect=True)
@pytest.mark.parametrize('app_path, target, config', c6_param_default, indirect=True)
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
def test_sdio_hw_esp32_esp32c6(
dut: tuple[IdfDut, IdfDut],
width_choice: str,
speed_choice: str,
) -> None:
_run_channel_quality_test(dut, width_choice, speed_choice)
@pytest.mark.sdio_master_slave
@pytest.mark.parametrize('count', [2], indirect=True)
@pytest.mark.parametrize('app_path, target, config', esp32_param_default, indirect=True)
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
def test_sdio_hw_esp32_esp32(
dut: tuple[IdfDut, IdfDut],
width_choice: str,
speed_choice: str,
) -> None:
_run_channel_quality_test(dut, width_choice, speed_choice)
@pytest.mark.sdio_multidev_p4_c5
@pytest.mark.parametrize('count', [2], indirect=True)
@pytest.mark.parametrize('app_path, target, config', c5_param_default, indirect=True)
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
def test_sdio_hw_esp32p4_esp32c5(
dut: tuple[IdfDut, IdfDut],
width_choice: str,
speed_choice: str,
) -> None:
_run_channel_quality_test(dut, width_choice, speed_choice)
@pytest.mark.sdio_multidev_32_c61
@pytest.mark.parametrize('count', [2], indirect=True)
@pytest.mark.parametrize('app_path, target, config', c61_param_default, indirect=True)
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
def test_sdio_hw_esp32_esp32c61(
dut: tuple[IdfDut, IdfDut],
width_choice: str,
speed_choice: str,
) -> None:
_run_channel_quality_test(dut, width_choice, speed_choice)

View File

@@ -0,0 +1,8 @@
# 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(sdio_slave)

View File

@@ -0,0 +1,4 @@
| Supported Targets | ESP32 | ESP32-C5 | ESP32-C6 | ESP32-C61 |
| ----------------- | ----- | -------- | -------- | --------- |
See README.md in the parent folder

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "app_main.c"
PRIV_REQUIRES esp_driver_gpio esp_driver_sdio esp_ringbuf
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,272 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/sdio_slave.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_log_buffer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/sdio_slave_pins.h"
#define TAG "example_slave"
#define TEST_FRAME_LEN 16
#define SDIO_BUFFER_SIZE 128
#define RX_BUFFER_COUNT 16
#define TX_BUFFER_COUNT 8
#define LOOP_IDLE_DELAY_MS 20
#define DEBUG_FRAME_LOG_LIMIT 8
#define DRIVE_CAP_KEEP_DEFAULT (-1)
#define SLAVE_CLK_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
#define SLAVE_CMD_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
#define SLAVE_DATA_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
static const uint8_t s_pattern[TEST_FRAME_LEN] = {
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00,
};
static DMA_ATTR uint8_t s_rx_buffers[RX_BUFFER_COUNT][SDIO_BUFFER_SIZE];
static DMA_ATTR uint8_t s_tx_buffers[TX_BUFFER_COUNT][TEST_FRAME_LEN];
static uint32_t s_tx_frame_counter;
static const gpio_num_t s_clk_pin = (gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK;
static const gpio_num_t s_cmd_pin = (gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD;
static const gpio_num_t s_data_pins[] = {
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0,
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1,
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2,
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3,
};
static void fill_test_pattern(uint8_t *buffer, size_t len)
{
for (size_t i = 0; i < len; ++i) {
buffer[i] = s_pattern[i % TEST_FRAME_LEN];
}
}
static bool validate_test_pattern(const uint8_t *buffer, size_t len)
{
if (len != TEST_FRAME_LEN) {
return false;
}
return memcmp(buffer, s_pattern, TEST_FRAME_LEN) == 0;
}
static int find_pattern_mismatch(const uint8_t *buffer, size_t len)
{
size_t compare_len = (len < TEST_FRAME_LEN) ? len : TEST_FRAME_LEN;
for (size_t i = 0; i < compare_len; ++i) {
if (buffer[i] != s_pattern[i]) {
return (int)i;
}
}
if (len != TEST_FRAME_LEN) {
return (int)compare_len;
}
return -1;
}
static void log_frame_debug(const char *label, uint32_t frame_index, const uint8_t *buffer, size_t len)
{
ESP_LOGI(TAG, "%s frame=%" PRIu32 " len=%u", label, frame_index, (unsigned)len);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_INFO);
}
static const char *drive_strength_name(int value)
{
switch (value) {
case DRIVE_CAP_KEEP_DEFAULT:
return "default";
case GPIO_DRIVE_CAP_0:
return "cap0";
case GPIO_DRIVE_CAP_1:
return "cap1";
case GPIO_DRIVE_CAP_2:
return "cap2";
case GPIO_DRIVE_CAP_3:
return "cap3";
default:
return "unknown";
}
}
static void log_gpio_drive_capability(const char *label, gpio_num_t pin)
{
gpio_drive_cap_t capability = GPIO_DRIVE_CAP_DEFAULT;
esp_err_t err = gpio_get_drive_capability(pin, &capability);
if (err == ESP_OK) {
ESP_LOGI(TAG, "%s GPIO%d drive capability: %s (%d)", label, pin, drive_strength_name(capability), capability);
} else {
ESP_LOGW(TAG, "Failed to read drive capability for GPIO%d: %s", pin, esp_err_to_name(err));
}
}
static esp_err_t apply_gpio_drive_strength(gpio_num_t pin, int drive_strength)
{
if (drive_strength == DRIVE_CAP_KEEP_DEFAULT) {
return ESP_OK;
}
return gpio_set_drive_capability(pin, (gpio_drive_cap_t)drive_strength);
}
static esp_err_t apply_slave_drive_strengths(void)
{
esp_err_t err = apply_gpio_drive_strength(s_clk_pin, SLAVE_CLK_DRIVE_CAP);
if (err != ESP_OK) {
return err;
}
err = apply_gpio_drive_strength(s_cmd_pin, SLAVE_CMD_DRIVE_CAP);
if (err != ESP_OK) {
return err;
}
for (size_t i = 0; i < sizeof(s_data_pins) / sizeof(s_data_pins[0]); ++i) {
err = apply_gpio_drive_strength(s_data_pins[i], SLAVE_DATA_DRIVE_CAP);
if (err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
static void log_slave_drive_strengths(const char *label)
{
log_gpio_drive_capability(label, s_clk_pin);
log_gpio_drive_capability(label, s_cmd_pin);
for (size_t i = 0; i < sizeof(s_data_pins) / sizeof(s_data_pins[0]); ++i) {
log_gpio_drive_capability(label, s_data_pins[i]);
}
}
static void init_rx_buffers(void)
{
for (int i = 0; i < RX_BUFFER_COUNT; ++i) {
sdio_slave_buf_handle_t handle = sdio_slave_recv_register_buf(s_rx_buffers[i]);
assert(handle != NULL);
ESP_ERROR_CHECK(sdio_slave_recv_load_buf(handle));
}
}
static void queue_tx_buffer(uint8_t *buffer)
{
fill_test_pattern(buffer, TEST_FRAME_LEN);
++s_tx_frame_counter;
if (s_tx_frame_counter <= DEBUG_FRAME_LOG_LIMIT) {
log_frame_debug("TX queue", s_tx_frame_counter, buffer, TEST_FRAME_LEN);
}
ESP_ERROR_CHECK(sdio_slave_send_queue(buffer, TEST_FRAME_LEN, buffer, portMAX_DELAY));
}
static void init_tx_buffers(void)
{
for (int i = 0; i < TX_BUFFER_COUNT; ++i) {
queue_tx_buffer(s_tx_buffers[i]);
}
}
static void recycle_finished_tx_buffers(void)
{
while (true) {
void *arg = NULL;
esp_err_t err = sdio_slave_send_get_finished(&arg, 0);
if (err == ESP_ERR_TIMEOUT) {
return;
}
ESP_ERROR_CHECK(err);
uint8_t *buffer = (uint8_t *)arg;
queue_tx_buffer(buffer);
printf("TX buffer recycled\n");
fflush(stdout);
}
}
static bool process_rx_packet(void)
{
sdio_slave_buf_handle_t handle = NULL;
uint8_t *buffer = NULL;
size_t length = 0;
esp_err_t err = sdio_slave_recv(&handle, &buffer, &length, 0);
if (err == ESP_ERR_TIMEOUT) {
return false;
}
ESP_ERROR_CHECK(err);
if (validate_test_pattern(buffer, length)) {
static uint32_t rx_frame_counter;
++rx_frame_counter;
if (rx_frame_counter <= DEBUG_FRAME_LOG_LIMIT) {
log_frame_debug("RX", rx_frame_counter, buffer, length);
}
printf("RX pattern validation: success\n");
} else {
int mismatch_index = find_pattern_mismatch(buffer, length);
log_frame_debug("RX invalid", 0, buffer, length);
if (mismatch_index >= 0 && mismatch_index < TEST_FRAME_LEN && length > (size_t)mismatch_index) {
ESP_LOGE(TAG,
"RX mismatch at byte %d: expected 0x%02X got 0x%02X",
mismatch_index,
s_pattern[mismatch_index],
buffer[mismatch_index]);
} else {
ESP_LOGE(TAG,
"RX length mismatch: expected %u got %u",
TEST_FRAME_LEN,
(unsigned)length);
}
printf("RX pattern validation: failure\n");
}
fflush(stdout);
ESP_ERROR_CHECK(sdio_slave_recv_load_buf(handle));
return true;
}
void app_main(void)
{
sdio_slave_config_t config = {
.sending_mode = SDIO_SLAVE_SEND_PACKET,
.send_queue_size = TX_BUFFER_COUNT,
.recv_buffer_size = SDIO_BUFFER_SIZE,
.event_cb = NULL,
};
ESP_ERROR_CHECK(sdio_slave_initialize(&config));
log_slave_drive_strengths("Slave default");
ESP_ERROR_CHECK(apply_slave_drive_strengths());
log_slave_drive_strengths("Slave active");
init_rx_buffers();
init_tx_buffers();
ESP_ERROR_CHECK(sdio_slave_start());
printf("SDIO slave ready for channel test\n");
fflush(stdout);
while (true) {
bool did_work = false;
recycle_finished_tx_buffers();
did_work |= process_rx_packet();
if (!did_work) {
vTaskDelay(pdMS_TO_TICKS(LOOP_IDLE_DELAY_MS));
}
}
}

View File

@@ -1,7 +1,6 @@
# SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
from typing import Tuple
import pytest
from pytest_embedded_idf import IdfDut
@@ -12,12 +11,16 @@ from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.parametrize(
'count, app_path',
[
(2, f'{os.path.join(os.path.dirname(__file__), "host")}|{os.path.join(os.path.dirname(__file__), "slave")}'),
(
2,
f'{os.path.join(os.path.dirname(__file__), "basic", "host")}|'
f'{os.path.join(os.path.dirname(__file__), "basic", "slave")}',
),
],
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_example_sdio_communication(dut: Tuple[IdfDut, IdfDut]) -> None:
def test_example_sdio_communication(dut: tuple[IdfDut, IdfDut]) -> None:
"""
Configurations
host = host -> slave = slave