feat(sdspi): Add an option to modify wait time for MISO before sending next command

Closes https://github.com/espressif/esp-idf/issues/16909
This commit is contained in:
Adam Múdry
2025-12-16 15:43:52 +01:00
parent bf7fc0b0fb
commit 4a0db18ff1
8 changed files with 121 additions and 21 deletions
@@ -75,6 +75,10 @@ typedef struct {
0 means "active low", i.e. card is protected when the GPIO is low;
1 means "active high", i.e. card is protected when GPIO is high. */
uint16_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
int8_t wait_for_miso; /*!< Timeout value in the driver will be waiting for MISO to be high before sending commands. Possible values are the following:
0: default value (40ms); -1: no waiting (0ms); 1-127: timeout in ms; else: invalid value, default will be used.
This can be used to speed up transactions in certain scenarios but should not be needed if correct pull-up resistors are used.
Use with care on devices where multiple SPI slaves use the same SPI bus.*/
} sdspi_device_config_t;
#define SDSPI_SLOT_NO_CS GPIO_NUM_NC ///< indicates that card select line is not used
@@ -86,14 +90,15 @@ typedef struct {
/**
* Macro defining default configuration of SD SPI device.
*/
#define SDSPI_DEVICE_CONFIG_DEFAULT() {\
#define SDSPI_DEVICE_CONFIG_DEFAULT() { \
.host_id = SDSPI_DEFAULT_HOST, \
.gpio_cs = GPIO_NUM_13, \
.gpio_cd = SDSPI_SLOT_NO_CD, \
.gpio_wp = SDSPI_SLOT_NO_WP, \
.gpio_int = GPIO_NUM_NC, \
.gpio_wp_polarity = SDSPI_IO_ACTIVE_LOW, \
.duty_cycle_pos = 0,\
.duty_cycle_pos = 0, \
.wait_for_miso = 0, \
}
/**
+49 -8
View File
@@ -42,10 +42,15 @@
typedef struct {
spi_host_device_t host_id; //!< SPI host id.
spi_device_handle_t spi_handle; //!< SPI device handle, used for transactions
uint8_t* block_buf;
/// semaphore of gpio interrupt
SemaphoreHandle_t semphr_int;
uint16_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
uint8_t gpio_cs; //!< CS GPIO, or GPIO_UNUSED
uint8_t gpio_cd; //!< Card detect GPIO, or GPIO_UNUSED
uint8_t gpio_wp; //!< Write protect GPIO, or GPIO_UNUSED
uint8_t gpio_int; //!< Write protect GPIO, or GPIO_UNUSED
uint8_t poll_busy_start_command_timeout; ///< Timeout value in milliseconds the driver will be waiting for MISO to be high before sending commands.
/// GPIO write protect polarity.
/// 0 means "active low", i.e. card is protected when the GPIO is low;
/// 1 means "active high", i.e. card is protected when GPIO is high.
@@ -54,10 +59,6 @@ typedef struct {
uint8_t data_crc_enabled : 1;
/// Intermediate buffer used when application buffer is not in DMA memory;
/// allocated on demand, SDSPI_BLOCK_BUF_SIZE bytes long. May be zero.
uint8_t* block_buf;
/// semaphore of gpio interrupt
SemaphoreHandle_t semphr_int;
uint16_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
} slot_info_t;
// Reserved for old API to be back-compatible
@@ -326,6 +327,28 @@ static void gpio_intr(void* arg)
}
}
static inline int wait_for_miso_to_poll_busy_timeout_ms(int8_t wait_for_miso)
{
static int default_timeout = 40; // default timeout in ms
int ret;
switch (wait_for_miso) {
case -1:
ret = 0; // no waiting
break;
case 0:
ret = default_timeout;
break;
case 1 ... 127:
ret = (int) wait_for_miso; // timeout in ms
break;
default:
ret = default_timeout; // unsupported values (from -128 to -2), use default
break;
}
assert(ret >= 0);
return ret;
}
esp_err_t sdspi_host_init_device(const sdspi_device_config_t* slot_config, sdspi_dev_handle_t* out_handle)
{
ESP_LOGD(TAG, "%s: SPI%d cs=%d cd=%d wp=%d wp_polarity:%d",
@@ -340,6 +363,7 @@ esp_err_t sdspi_host_init_device(const sdspi_device_config_t* slot_config, sdspi
.host_id = slot_config->host_id,
.gpio_cs = slot_config->gpio_cs,
.duty_cycle_pos = slot_config->duty_cycle_pos,
.poll_busy_start_command_timeout = wait_for_miso_to_poll_busy_timeout_ms(slot_config->wait_for_miso),
};
// Attach the SD card to the SPI bus
@@ -451,7 +475,6 @@ cleanup:
}
free(slot);
return ret;
}
esp_err_t sdspi_host_start_command(sdspi_dev_handle_t handle, sdspi_hw_cmd_t *cmd, void *data,
@@ -472,8 +495,21 @@ esp_err_t sdspi_host_start_command(sdspi_dev_handle_t handle, sdspi_hw_cmd_t *cm
ESP_LOGV(TAG, "%s: slot=%i, CMD%d, arg=0x%08"PRIx32" flags=0x%x, data=%p, data_size=%"PRIu32" crc=0x%02x",
__func__, handle, cmd_index, cmd_arg, flags, data, data_size, cmd->crc7);
spi_device_acquire_bus(slot->spi_handle, portMAX_DELAY);
poll_busy(slot, 40, true);
esp_err_t ret;
ret = spi_device_acquire_bus(slot->spi_handle, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGD(TAG, "%s: spi_device_acquire_bus returned 0x%x", __func__, ret);
return ret;
}
ret = poll_busy(slot, slot->poll_busy_start_command_timeout, true);
if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) {
ESP_LOGD(TAG, "%s: poll_busy error=0x%x", __func__, ret);
cs_high(slot);
release_bus(slot);
spi_device_release_bus(slot->spi_handle);
return ret;
}
// For CMD0, clock out 80 cycles to help the card enter idle state,
// *before* CS is asserted.
@@ -481,7 +517,7 @@ esp_err_t sdspi_host_start_command(sdspi_dev_handle_t handle, sdspi_hw_cmd_t *cm
go_idle_clockout(slot);
}
// actual transaction
esp_err_t ret = ESP_OK;
ret = ESP_OK;
cs_low(slot);
if (flags & SDSPI_CMD_FLAG_DATA) {
@@ -573,6 +609,11 @@ static esp_err_t start_command_default(slot_info_t *slot, int flags, sdspi_hw_cm
// Wait until MISO goes high
static esp_err_t poll_busy(slot_info_t *slot, int timeout_ms, bool polling)
{
if (timeout_ms < 0) {
return ESP_ERR_INVALID_ARG;
} else if (timeout_ms == 0) {
return ESP_OK;
}
uint8_t t_rx;
spi_transaction_t t = {
.tx_buffer = &t_rx,
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -37,13 +37,24 @@ void sdmmc_test_spi_skip_if_board_incompatible(int slot, int freq_khz)
}
void sdmmc_test_spi_begin(int slot, int freq_khz, sdmmc_card_t *out_card)
void sdmmc_test_spi_begin(int slot, int freq_khz, sdmmc_card_t *out_card,
sdmmc_host_t *_config, sdspi_device_config_t *_dev_config, spi_bus_config_t *_bus_config)
{
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
spi_bus_config_t bus_config = {};
sdspi_dev_handle_t handle;
if (_config != NULL) {
config = *_config;
}
if (_dev_config != NULL) {
dev_config = *_dev_config;
}
if (_bus_config != NULL) {
bus_config = *_bus_config;
}
sdspi_dev_handle_t handle;
/* Similar to the checks in sdmmc_test_spi_skip_if_board_incompatible, but
* we fail the test if we somehow got to this point with an incompatible board.
*/
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sd_protocol_types.h"
#include "driver/sdspi_host.h"
#ifdef __cplusplus
extern "C" {
@@ -29,7 +30,8 @@ void sdmmc_test_spi_skip_if_board_incompatible(int slot, int freq_khz);
* @brief Helper function to initialize the SDMMC host and slot for the test using the given settings, for SPI mode
* @see sdmmc_test_sd_begin
*/
void sdmmc_test_spi_begin(int slot, int freq_khz, sdmmc_card_t *out_card);
void sdmmc_test_spi_begin(int slot, int freq_khz, sdmmc_card_t *out_card,
sdmmc_host_t *_config, sdspi_device_config_t *_dev_config, spi_bus_config_t *_bus_config);
/**
* @brief Helper function to deinitialize the SDMMC host and slot after the test, for SPI mode
@@ -16,7 +16,7 @@ static void do_one_sdspi_erase(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card);
sdmmc_test_spi_begin(slot, freq_khz, &card, NULL, NULL, NULL);
sdmmc_card_print_info(stdout, &card);
sdmmc_test_sd_erase_blocks(&card);
sdmmc_test_spi_end(slot, &card);
@@ -14,7 +14,7 @@ static void do_one_sdspi_probe(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card);
sdmmc_test_spi_begin(slot, freq_khz, &card, NULL, NULL, NULL);
sdmmc_card_print_info(stdout, &card);
uint8_t* buffer = heap_caps_calloc(512, 1, MALLOC_CAP_DMA);
TEST_ESP_OK(sdmmc_read_sectors(&card, buffer, 0, 1));
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include "unity.h"
#include "sdmmc_cmd.h"
#include "driver/sdspi_host.h"
#include "sdmmc_test_begin_end_spi.h"
#include "sdmmc_test_rw_common.h"
@@ -15,7 +16,7 @@ static void do_one_sdspi_perf_test(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card);
sdmmc_test_spi_begin(slot, freq_khz, &card, NULL, NULL, NULL);
sdmmc_card_print_info(stdout, &card);
sdmmc_test_rw_performance(&card, NULL);
sdmmc_test_spi_end(slot, &card);
@@ -39,7 +40,7 @@ static void do_one_sdspi_rw_test_with_offset(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card);
sdmmc_test_spi_begin(slot, freq_khz, &card, NULL, NULL, NULL);
sdmmc_card_print_info(stdout, &card);
sdmmc_test_rw_with_offset(&card);
sdmmc_test_spi_end(slot, &card);
@@ -63,7 +64,7 @@ static void do_one_sdspi_rw_test_unaligned_buffer(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card);
sdmmc_test_spi_begin(slot, freq_khz, &card, NULL, NULL, NULL);
sdmmc_card_print_info(stdout, &card);
sdmmc_test_rw_unaligned_buffer(&card);
sdmmc_test_spi_end(slot, &card);
@@ -78,3 +79,31 @@ TEST_CASE("sdspi read/write using unaligned buffer, slot 1", "[sdspi]")
{
do_one_sdspi_rw_test_unaligned_buffer(SLOT_1, SDMMC_FREQ_DEFAULT);
}
/* ========== Read/write performance tests with wait_for_miso == -1, SPI ========== */
static void do_one_sdspi_perf_test_dont_wait_for_miso(int slot, int freq_khz)
{
sdmmc_card_t card;
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
dev_config.wait_for_miso = -1; // no waiting for MISO
spi_bus_config_t bus_config = {};
sdmmc_test_spi_skip_if_board_incompatible(slot, freq_khz);
sdmmc_test_spi_begin(slot, freq_khz, &card, &config, &dev_config, &bus_config);
sdmmc_card_print_info(stdout, &card);
sdmmc_test_rw_performance(&card, NULL);
sdmmc_test_spi_end(slot, &card);
}
TEST_CASE("sdspi read/write performance - wait_for_miso == -1, slot 0", "[sdspi]")
{
do_one_sdspi_perf_test_dont_wait_for_miso(SLOT_0, SDMMC_FREQ_HIGHSPEED);
}
TEST_CASE("sdspi read/write performance - wait_for_miso == -1, slot 1", "[sdspi]")
{
//TODO: IDF-8749
//here freq should be changed to SDMMC_FREQ_HIGHSPEED after fixing IDF-8749
do_one_sdspi_perf_test_dont_wait_for_miso(SLOT_1, SDMMC_FREQ_DEFAULT);
}