SdProfile describes an SPI-attached card: cs / mosi / miso / sclk / spiHz, mounted through SD.begin() on a SPIClass. The Freenove FNK0104B's slot is wired for SDMMC in 4-bit mode, which has no chip select and is a different peripheral entirely. The board profile therefore sets every field to PIN_NONE.
This is a supported configuration, not a fault: sdReady() returns false, optional SD content is not loaded, and everything that reads it already has defaults. See docs/SD_CONTENT_SPEC.md for what is actually optional.
What a user notices
Very little today — SD content is optional across the board. The gap matters because the FNK0104B has 16 MB of flash and 8 MB of PSRAM, so it is the first board where large optional content is genuinely practical.
The hardware
SDMMC 4-bit, from Tutorial_With_Touch/Sketches/Sketch_06.1_SDMMC_Test:
| Signal |
GPIO |
| CLK |
38 |
| CMD |
40 |
| D0 |
39 |
| D1 |
41 |
| D2 |
48 |
| D3 |
47 |
Vendor's comment on every one of them is "Please do not modify it" — these are fixed-function pins for the SDMMC host on this layout.
The 3.5-inch FNK0104N variant uses CLK 5 / CMD 4 / D0 6 / D1 7 / D2 2 / D3 3. Do not assume one map across the family.
Arduino core 2.0.17 (pinned via espressif32@7.0.1) exposes this as SD_MMC.setPins(clk, cmd, d0, d1, d2, d3) followed by SD_MMC.begin().
What needs doing
- Extend
SdProfile to describe which kind of slot the board has — the discriminated-record pattern TouchProfile now uses. Flat and /* fieldName */-labelled, because tools/check_boards.py reads those labels; unused lines PIN_NONE.
- Fill the new fields in for all four boards in the same commit, or the checker fails and a board boots with a silent zero.
- Implement the SDMMC mount in
Board::mountSd() (src/hal/Board.cpp) behind the existing sdReady() / mountSd() API, so nothing above the HAL changes.
- Guard with
#if, not if constexpr — the GUME_TOUCH_CAPACITIVE precedent in include/boards/*.h. Both arms of an if constexpr compile in a non-template function; the touch equivalent linked Wire into every resistive board for +4,476 bytes of dead code. SD_MMC and SD are both non-trivial and flash is at ~75%.
- Note that
Board::Board() constructs sdSpi_(VSPI) unconditionally. On a board with no SPI card that member is pure waste and should be conditional too.
- Update
README.md (the FNK0104B limitations table), src/hal/CLAUDE.md, docs/PORTING.md's optional-peripheral table, and the flash/RAM figures in README.md and CLAUDE.md from your own pio run.
Worth deciding at the same time
The board has 16 MB of flash and currently uses huge_app.csv, a 4 MB table — about 12 MB is stranded. If SD content is the motivation for this work, a larger partition table with a data partition may serve better than a card. That is a separate decision but the same conversation; raise it before building an SDMMC backend nobody needs.
Verifying
Add a mount-and-list step to pio run -e s3diag first: it is standalone, so it confirms the six pins and the 4-bit mode before any HAL work depends on them.
SdProfiledescribes an SPI-attached card:cs / mosi / miso / sclk / spiHz, mounted throughSD.begin()on aSPIClass. The Freenove FNK0104B's slot is wired for SDMMC in 4-bit mode, which has no chip select and is a different peripheral entirely. The board profile therefore sets every field toPIN_NONE.This is a supported configuration, not a fault:
sdReady()returns false, optional SD content is not loaded, and everything that reads it already has defaults. Seedocs/SD_CONTENT_SPEC.mdfor what is actually optional.What a user notices
Very little today — SD content is optional across the board. The gap matters because the FNK0104B has 16 MB of flash and 8 MB of PSRAM, so it is the first board where large optional content is genuinely practical.
The hardware
SDMMC 4-bit, from
Tutorial_With_Touch/Sketches/Sketch_06.1_SDMMC_Test:Vendor's comment on every one of them is "Please do not modify it" — these are fixed-function pins for the SDMMC host on this layout.
The 3.5-inch FNK0104N variant uses CLK 5 / CMD 4 / D0 6 / D1 7 / D2 2 / D3 3. Do not assume one map across the family.
Arduino core 2.0.17 (pinned via
espressif32@7.0.1) exposes this asSD_MMC.setPins(clk, cmd, d0, d1, d2, d3)followed bySD_MMC.begin().What needs doing
SdProfileto describe which kind of slot the board has — the discriminated-record patternTouchProfilenow uses. Flat and/* fieldName */-labelled, becausetools/check_boards.pyreads those labels; unused linesPIN_NONE.Board::mountSd()(src/hal/Board.cpp) behind the existingsdReady()/mountSd()API, so nothing above the HAL changes.#if, notif constexpr— theGUME_TOUCH_CAPACITIVEprecedent ininclude/boards/*.h. Both arms of anif constexprcompile in a non-template function; the touch equivalent linked Wire into every resistive board for +4,476 bytes of dead code.SD_MMCandSDare both non-trivial and flash is at ~75%.Board::Board()constructssdSpi_(VSPI)unconditionally. On a board with no SPI card that member is pure waste and should be conditional too.README.md(the FNK0104B limitations table),src/hal/CLAUDE.md,docs/PORTING.md's optional-peripheral table, and the flash/RAM figures inREADME.mdandCLAUDE.mdfrom your ownpio run.Worth deciding at the same time
The board has 16 MB of flash and currently uses
huge_app.csv, a 4 MB table — about 12 MB is stranded. If SD content is the motivation for this work, a larger partition table with a data partition may serve better than a card. That is a separate decision but the same conversation; raise it before building an SDMMC backend nobody needs.Verifying
Add a mount-and-list step to
pio run -e s3diagfirst: it is standalone, so it confirms the six pins and the 4-bit mode before any HAL work depends on them.