From e4f26403ccdc4619b0586046fba7eba6a3a46839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:26:24 +0200 Subject: [PATCH 1/2] fix(sd): align SPI card init with IDF (ACMD41 args, timeout, CMD0, CRC) --- libraries/SD/src/sd_diskio.cpp | 52 ++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/libraries/SD/src/sd_diskio.cpp b/libraries/SD/src/sd_diskio.cpp index 5b51db4138f..a1c9285ae0f 100644 --- a/libraries/SD/src/sd_diskio.cpp +++ b/libraries/SD/src/sd_diskio.cpp @@ -56,6 +56,10 @@ typedef enum { CRC_ON_OFF = 59 } ardu_sdcard_command_t; +// Align with ESP-IDF sdmmc SPI init (ACMD41 timeout must be >1s per SD spec) +static constexpr unsigned sd_go_idle_delay_ms = 20; +static constexpr unsigned sd_op_cond_timeout_ms = 3000; + typedef struct { uint8_t ssPin; SPIClass *spi; @@ -470,12 +474,12 @@ struct AcquireSPI { * the SD card specification. It performs card detection, type identification, * and configuration for SPI mode operation. * - * The initialization sequence follows the SD card protocol: + * The initialization sequence follows the SD card protocol (SPI mode, aligned with IDF): * 1. Power-up sequence with 74+ clock cycles - * 2. GO_IDLE_STATE command to reset the card - * 3. CRC_ON_OFF to enable/disable CRC checking + * 2. GO_IDLE_STATE twice to enter SPI mode + * 3. CRC_ON_OFF to enable CRC checking (with retry) * 4. SEND_IF_COND to identify SDHC/SDXC cards - * 5. APP_OP_COND to set operating conditions + * 5. APP_OP_COND / SEND_OP_COND (SPI args; timeout >1s) * 6. Card type detection (SD/SDHC/MMC) * 7. Final configuration and sector count retrieval * @@ -507,23 +511,34 @@ DSTATUS ff_sd_initialize(uint8_t pdrv) { card->spi->transfer(0XFF); } - // Step 2: Select the card and send GO_IDLE_STATE command - // This command resets the card to idle state and enables SPI mode + // Step 2: Select the card and send GO_IDLE_STATE (CMD0) twice in SPI mode. + // Per SD Simplified Spec (figure 4-1) / IDF: some cards enter SD mode on the + // first CMD0, so the first response may fail; the second must succeed. // Fix mount issue - sdWait fail ignored before command GO_IDLE_STATE digitalWrite(card->ssPin, LOW); if (!sdWait(pdrv, 500)) { log_w("sdWait fail ignored, card initialize continues"); } - if (sdCommand(pdrv, GO_IDLE_STATE, 0, NULL) != 1) { + (void)sdCommand(pdrv, GO_IDLE_STATE, 0, NULL); + sdDeselectCard(pdrv); + delay(sd_go_idle_delay_ms); + + if (!sdSelectCard(pdrv) || sdCommand(pdrv, GO_IDLE_STATE, 0, NULL) != 1) { sdDeselectCard(pdrv); log_w("GO_IDLE_STATE failed"); goto unknown_card; } sdDeselectCard(pdrv); + delay(sd_go_idle_delay_ms); // Step 3: Configure CRC checking - // Enable CRC for data transfers in SPI mode (required for reliable communication) + // Enable CRC for data transfers in SPI mode (required for reliable communication). + // Some cards reject the first CRC_ON_OFF; retry once (same as IDF). token = sdTransaction(pdrv, CRC_ON_OFF, 1, NULL); + if (token != 1 && token != 0x5) { + delay(10); + token = sdTransaction(pdrv, CRC_ON_OFF, 1, NULL); + } if (token == 0x5) { // Old card that doesn't support CRC - disable CRC checking card->supports_crc = false; @@ -547,12 +562,13 @@ DSTATUS ff_sd_initialize(uint8_t pdrv) { goto unknown_card; } - // Send APP_OP_COND to set operating conditions for SDHC/SDXC - // Wait up to 1 second for the card to become ready + // Send APP_OP_COND to set operating conditions for SDHC/SDXC. + // In SPI mode only HCS (bit 30) is valid; voltage bits must be 0 (same as IDF). + // Timeout must be >1s per SD spec (IDF uses ~3s). start = millis(); do { - token = sdTransaction(pdrv, APP_OP_COND, 0x40100000, NULL); - } while (token == 1 && (millis() - start) < 1000); + token = sdTransaction(pdrv, APP_OP_COND, 0x40000000, NULL); + } while (token == 1 && (millis() - start) < sd_op_cond_timeout_ms); if (token) { log_w("APP_OP_COND failed: %u", token); @@ -577,20 +593,20 @@ DSTATUS ff_sd_initialize(uint8_t pdrv) { goto unknown_card; } - // Try SD card initialization first + // Try SD card initialization first (SPI mode: ACMD41 arg must be 0) start = millis(); do { - token = sdTransaction(pdrv, APP_OP_COND, 0x100000, NULL); - } while (token == 0x01 && (millis() - start) < 1000); + token = sdTransaction(pdrv, APP_OP_COND, 0, NULL); + } while (token == 0x01 && (millis() - start) < sd_op_cond_timeout_ms); if (!token) { card->type = CARD_SD; // Standard SD card } else { - // Try MMC card initialization + // Try MMC card initialization (SPI mode: CMD1 arg must be 0) start = millis(); do { - token = sdTransaction(pdrv, SEND_OP_COND, 0x100000, NULL); - } while (token != 0x00 && (millis() - start) < 1000); + token = sdTransaction(pdrv, SEND_OP_COND, 0, NULL); + } while (token != 0x00 && (millis() - start) < sd_op_cond_timeout_ms); if (token == 0x00) { card->type = CARD_MMC; // MMC card From 1623e349e833a94587ed9772caa9d8c413bf7db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:39:13 +0200 Subject: [PATCH 2/2] fix(sd): address review feedback on SPI CMD0 init path --- libraries/SD/src/sd_diskio.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/SD/src/sd_diskio.cpp b/libraries/SD/src/sd_diskio.cpp index a1c9285ae0f..538f4536e43 100644 --- a/libraries/SD/src/sd_diskio.cpp +++ b/libraries/SD/src/sd_diskio.cpp @@ -57,8 +57,8 @@ typedef enum { } ardu_sdcard_command_t; // Align with ESP-IDF sdmmc SPI init (ACMD41 timeout must be >1s per SD spec) -static constexpr unsigned sd_go_idle_delay_ms = 20; -static constexpr unsigned sd_op_cond_timeout_ms = 3000; +static constexpr uint32_t sd_go_idle_delay_ms = 20; +static constexpr uint32_t sd_op_cond_timeout_ms = 3000; typedef struct { uint8_t ssPin; @@ -476,7 +476,7 @@ struct AcquireSPI { * * The initialization sequence follows the SD card protocol (SPI mode, aligned with IDF): * 1. Power-up sequence with 74+ clock cycles - * 2. GO_IDLE_STATE twice to enter SPI mode + * 2. Two GO_IDLE_STATE attempts to enter SPI mode * 3. CRC_ON_OFF to enable CRC checking (with retry) * 4. SEND_IF_COND to identify SDHC/SDXC cards * 5. APP_OP_COND / SEND_OP_COND (SPI args; timeout >1s) @@ -511,10 +511,11 @@ DSTATUS ff_sd_initialize(uint8_t pdrv) { card->spi->transfer(0XFF); } - // Step 2: Select the card and send GO_IDLE_STATE (CMD0) twice in SPI mode. + // Step 2: Perform two GO_IDLE_STATE (CMD0) attempts in SPI mode. // Per SD Simplified Spec (figure 4-1) / IDF: some cards enter SD mode on the - // first CMD0, so the first response may fail; the second must succeed. - // Fix mount issue - sdWait fail ignored before command GO_IDLE_STATE + // first attempt, so the first response may fail; the second must succeed. + // (sdCommand may also retry internally on no-token/CRC errors.) + // Fix mount issue - sdWait fail ignored before each CMD0 attempt digitalWrite(card->ssPin, LOW); if (!sdWait(pdrv, 500)) { log_w("sdWait fail ignored, card initialize continues"); @@ -523,7 +524,11 @@ DSTATUS ff_sd_initialize(uint8_t pdrv) { sdDeselectCard(pdrv); delay(sd_go_idle_delay_ms); - if (!sdSelectCard(pdrv) || sdCommand(pdrv, GO_IDLE_STATE, 0, NULL) != 1) { + digitalWrite(card->ssPin, LOW); + if (!sdWait(pdrv, 500)) { + log_w("sdWait fail ignored, card initialize continues"); + } + if (sdCommand(pdrv, GO_IDLE_STATE, 0, NULL) != 1) { sdDeselectCard(pdrv); log_w("GO_IDLE_STATE failed"); goto unknown_card;