Skip to main content
Visitor
September 16, 2026
Question

STM32F427 SDIO write reports success at every layer but data never persists (DMA and polling both)

  • September 16, 2026
  • 2 replies
  • 11 views

Part Number: STM32F427VIT6

Environment: Custom PCB (not an eval/dev board), native SDIO/SDMMC1 peripheral, 4-bit bus, FatFs (ChaN's ff.c) on top of ST's HAL (stm32f4xx_hal_sd.c). STM32CubeIDE 2.2.0, GNU Arm Embedded toolchain 14.3.rel1 (arm-none-eabi-gcc), STM32CubeProgrammer for flashing/SWD debug, bare-metal HAL (no RTOS). SDIO clock tested from 400kHz up to ~24MHz; same result throughout.

Schematics / hardware note: This is a custom PCB, so a board-level cause can't be fully excluded, a scope check on the 3.3V rail during a write burst is still pending, and I'm happy to attach the relevant SDMMC1/power schematic on request. That said, a pure PCB signal-integrity/trace defect looks unlikely specifically: DAT0-3 are bidirectional and reads over those same physical traces are 100% reliable, every write's CRC check passes cleanly, the failure is fully deterministic rather than marginal/intermittent, and it's unchanged across the full 400kHz-24MHz clock range tested, none of that fits a timing-margin/trace-length explanation. A power-delivery issue during the card's internal write-programming phase specifically (as opposed to the low-current handshake phase, which always succeeds) remains the leading untested hardware hypothesis.

Details / symptom: A write reports success at every single layer, FatFs FRESULT clean, BSP_SD_WriteBlocks()/_DMA() return MSD_OK, hsd.ErrorCode clean, SDIO->STA shows clean DBCKEND/DATAEND with no error flags, and a raw CMD13/SEND_STATUS immediately afterward decodes to 0x900 (READY_FOR_DATA=1, CURRENT_STATE=4/"tran", every error bit zero). But the data never lands: reading the same sector back, at the raw block level, bypassing FatFs entirely, returns the pre-existing stale content, not what was just written, not corrupted garbage.

Expected behavior: Reading back a sector immediately after writing it should return the data just written. Actual: it returns whatever was there before.

How to reproduce: BSP_SD_WriteBlocks() (or _DMA()) a known pattern to a sector, then immediately BSP_SD_ReadBlocks()/_DMA() the same sector and compare. Fails every time, regardless of sector location, write pattern, clock speed, or bus width.

Occurrence: Systematic, 100% reproducible, every single attempt, not intermittent.

Sanity checks already performed (with hardware evidence, not just config review):

  • Not FatFs-specific: raw BSP_SD_WriteBlocks()/_DMA() block writes fail identically.
  • Not sector-location-specific: tested at multiple sectors across the card.
  • Not card-specific: reproduces identically across 3 different physical SD cards.
  • Not a write-protect flag: HAL_SD_GetCardCSD() shows PermWrProtect=0TempWrProtect=0WrProtectGrEnable=0.
  • Not bus-width-dependent: fails identically in 1-bit and 4-bit mode.
  • Not clock-speed-dependent: fails identically from 400kHz to ~24MHz.
  • Not DMA-specific: switching the write path to polling (BSP_SD_WriteBlocks(), no DMA at all) produces the exact same symptom.
  • Not a stuck/stale software state: hsd.State reads back clean/READY immediately after a fresh failed write.
  • Not a card-side volatile buffer: power-cycling the card (GPIO power switch) and re-initializing before the read-back doesn't recover the data.
  • Not a busy-line/trailing-clock desync: direct GPIO read of the physical DAT0 line (bypassing the SDIO peripheral's own busy interpretation entirely) matches the software's ready/busy state exactly.
  • Not interference from other on-board peripherals: reproduces identically in a stripped-down boot with every other peripheral (UART/I2C/SPI/USB/other DMA/timers) left uninitialized, SDIO+DMA+FatFs running alone.
  • Hardware flow control (SDIO_HARDWARE_FLOW_CONTROL_ENABLE): no change either way.

One separate, real bug found and fixed along the way (not the cause of the above, but worth mentioning for anyone else on this thread): a DMA write's NDTR register doesn't reach zero in sync with SDIO->DCOUNT/FIFOCNT, same symptom class as this earlier thread on F427 SDIO+DMA writes. Worked around by moving writes to polling mode instead of DMA, but as noted above, the actual write-persistence bug reproduces identically either way, so that wasn't the root cause of this specific issue.

One more data point: the card's own busy/programming time after a write is consistently ~1ms, much shorter than a typical NAND program cycle, which suggests the card may not be attempting a real internal write at all rather than attempting one and failing partway through.

Question: Has anyone seen a write that the SDIO peripheral, the HAL, and the card's own CMD13 status all agree completed cleanly, but that silently doesn't persist? Is there any register-level state (beyond STA/DCOUNT/FIFOCNT/CMD13) that could show a write being accepted by the peripheral/card protocol layer without actually reaching flash? Any errata for the F427 SDMMC1 block worth checking that isn't in the standard STM32F4 errata sheet?

2 replies

TDK
September 16, 2026

The DMA NDTR register isn’t exactly in sync with the peripheral exactly because DMA either leads (for transmitting) or lags (for receiving). This is not a bug. When using the SDIO peripheral, you generally shouldn’t be using NDTR for anything.

Showing code from your minimal working example may help. There are many working examples out there for STM32F4 + SD cards. There’s no massive hardware bug here--the functionality is there. If you’re not seeing it work, the issue is likely specific to your code (or hardware, but code seems more likely here if command/response is working).

"If you feel a post has answered your question, please click ""Accept as Solution""."
Visitor
September 16, 2026

Fair points on both counts.

On NDTR, agreed it's expected divergence rather than evidence of a defect on its own; I called it a separate finding precisely because it doesn't explain the actual symptom (it reproduces identically in polling mode, with DMA out of the picture entirely). Should have made that clearer up front rather than leaving it to a footnote.

On code, happy to share the actual minimal repro rather than just describing it. Here's the raw block-level test (bypasses FatFs) plus the SDIO init it depends on:

/* MX_SDIO_SD_Init() */
hsd.Instance = SDIO;
hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
hsd.Init.BusWide = SDIO_BUS_WIDE_1B; /* also tested at 4B, same result */
hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_ENABLE; /* also tested DISABLE, same result */
hsd.Init.ClockDiv = 118; /* also tested 0 (~24MHz), same result */
/* HAL_SD_MspInit() GPIO portion */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_2;
/* same Mode/Pull/Speed/Alternate */
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

 

uint32_t sector = 50000;

static uint32_t wbuf[128]; /* 512 bytes, 32-bit aligned (required for SDIO DMA) */
static uint32_t rbuf[128];
for (int i = 0; i < 128; i++) {
wbuf[i] = 0x11000000u | (uint32_t)i; /* known, non-trivial pattern */
}

uint8_t st = BSP_SD_WriteBlocks(wbuf, sector, 1, 3000); /* -> MSD_OK */

/* explicit busy-wait for the card's own programming state */
uint32_t start = HAL_GetTick();
while (BSP_SD_GetCardState() != MSD_OK) {
if (HAL_GetTick() - start > 3000) break;
}

st = BSP_SD_ReadBlocks(rbuf, sector, 1, 3000); /* -> MSD_OK */

int match = (memcmp(wbuf, rbuf, sizeof(wbuf)) == 0); /* 0 every time -- rbuf holds stale content */

Genuinely open to "you forgot X", that's exactly what I'm hoping someone spots. GPIO config (pull-up, very-high speed, AF12) matches the standard CubeMX-generated pattern for SDIO, and I've verified this specific repro survives with every other peripheral on the board uninitialized (isolated boot, nothing else running), across 3 physical cards, both bus widths, and clock speeds from 400kHz to 24MHz, so if it is code, it's something pretty deeply hidden, not a surface-level GPIO/clock mistake.