Summary
On the Waveshare ESP32-P4-WIFI6-Touch-LCD-4B, machine.SDCard() — the form board_peripherals.sdcard() uses — never brings up the card. The peripheral initialises, the card never answers: info() raises ESP_ERR_TIMEOUT, ioctl(4/5/6) return -1, and readblocks() returns False.
The card itself is fine. When it did come up, it read as a 32 GB card (62,685,184 x 512 blocks) with a valid MBR (55aa).
Three things the board config does not do
From the Waveshare GPIO allocation table: D0-D3 = GPIO39-42, CLK = GPIO43, CMD = GPIO44, power control = GPIO45.
- GPIO45 is a power-control pin and nothing drives it. Observed working with it driven low.
board_peripherals.sdcard() drives nothing — the same shape as the S3's CH422G/EXIO5 trap, where an unset enable pin made a whole peripheral look absent.
- 4-bit width is required.
width=1 never answered at any frequency.
- 25 MHz and above fail. 4-20 MHz worked; the default is 20 MHz.
The explicit-pin arguments are not available here — SDCard(clk=..., cmd=..., data=...) raises TypeError: extra keyword arguments given, so SOC_SDMMC_USE_GPIO_MATRIX is off for this target and 39-44 are the fixed pins.
What is still unexplained, and why this is not just a config fix
Getting the parameters right is not sufficient. With Pin(45, OUT, value=0), slot=0, width=4, freq=20000000, a fresh boot still times out.
The two runs where the card came up were both preceded by many failed attempts in the same session, and — critically — the following run also worked. An ESP32 reset does not reset the card, so those successes are a card left initialised by an earlier accident, not a sequence that can be reproduced on demand. Everything since is ESP_ERR_TIMEOUT.
Sequences tried, all failing from a clean boot: retry loops (5x with settles), a deliberate power cycle (GPIO45 high 400 ms, then low), a slow identification pass at 400 kHz then a fast open, and an ascending frequency walk with deinit() between attempts.
Also note the first SDCard() after boot constructs; subsequent ones raise ESP_ERR_TIMEOUT at construction, so the peripheral is left held and a naive retry loop cannot work without a deinit() on every path.
Impact
board_peripherals.sdcard() on this board cannot work as written, whatever else is fixed.
- Blocks the host-side SD format test across the USB MSC link — the last open item on the "SD card as a device drive" row in the roadmap, which needs a working card behind
sd_drive.py.
Method note
An earlier report in this session claimed the card was unformatted with "sector 0 signature 0000". That was wrong: readblocks() had returned False and never written the buffer, and the zeros were the buffer's own. Anything checking this should pre-fill the buffer with a poison value (0xA5) and assert it was overwritten — reading zeros from an untouched buffer is indistinguishable from reading an empty sector.
Separately: sd_drive.py takes ioctl at face value
examples/sd_drive.py reads card.ioctl(4, 0) straight into its block count. A card returning -1 produces no output and no error — the example simply does nothing, which is how this was first mistaken for the example being broken. It should refuse a non-positive block count with a real message.
Summary
On the Waveshare ESP32-P4-WIFI6-Touch-LCD-4B,
machine.SDCard()— the formboard_peripherals.sdcard()uses — never brings up the card. The peripheral initialises, the card never answers:info()raisesESP_ERR_TIMEOUT,ioctl(4/5/6)return-1, andreadblocks()returnsFalse.The card itself is fine. When it did come up, it read as a 32 GB card (62,685,184 x 512 blocks) with a valid MBR (
55aa).Three things the board config does not do
From the Waveshare GPIO allocation table:
D0-D3 = GPIO39-42, CLK = GPIO43, CMD = GPIO44, power control = GPIO45.board_peripherals.sdcard()drives nothing — the same shape as the S3's CH422G/EXIO5 trap, where an unset enable pin made a whole peripheral look absent.width=1never answered at any frequency.The explicit-pin arguments are not available here —
SDCard(clk=..., cmd=..., data=...)raisesTypeError: extra keyword arguments given, soSOC_SDMMC_USE_GPIO_MATRIXis off for this target and 39-44 are the fixed pins.What is still unexplained, and why this is not just a config fix
Getting the parameters right is not sufficient. With
Pin(45, OUT, value=0),slot=0, width=4, freq=20000000, a fresh boot still times out.The two runs where the card came up were both preceded by many failed attempts in the same session, and — critically — the following run also worked. An ESP32 reset does not reset the card, so those successes are a card left initialised by an earlier accident, not a sequence that can be reproduced on demand. Everything since is
ESP_ERR_TIMEOUT.Sequences tried, all failing from a clean boot: retry loops (5x with settles), a deliberate power cycle (GPIO45 high 400 ms, then low), a slow identification pass at 400 kHz then a fast open, and an ascending frequency walk with
deinit()between attempts.Also note the first
SDCard()after boot constructs; subsequent ones raiseESP_ERR_TIMEOUTat construction, so the peripheral is left held and a naive retry loop cannot work without adeinit()on every path.Impact
board_peripherals.sdcard()on this board cannot work as written, whatever else is fixed.sd_drive.py.Method note
An earlier report in this session claimed the card was unformatted with "sector 0 signature 0000". That was wrong:
readblocks()had returnedFalseand never written the buffer, and the zeros were the buffer's own. Anything checking this should pre-fill the buffer with a poison value (0xA5) and assert it was overwritten — reading zeros from an untouched buffer is indistinguishable from reading an empty sector.Separately: sd_drive.py takes ioctl at face value
examples/sd_drive.pyreadscard.ioctl(4, 0)straight into its block count. A card returning-1produces no output and no error — the example simply does nothing, which is how this was first mistaken for the example being broken. It should refuse a non-positive block count with a real message.