Card Types
Standard microSD and microSDHC cards are supported in SPI mode. SDXC cards may work if formatted as FAT32, but the SPI mode interface limits maximum throughput compared to native SDIO.
The board includes a microSD (TF) card slot connected to the ESP32-S3 via SPI. There is one important wrinkle: the chip select signal is not wired to a direct GPIO pin. Instead, it routes through the CH422G IO expander (EXIO4), which means SD card access requires initializing the IO expander first. This is a consequence of the board’s dense peripheral layout and limited available GPIO, but it works reliably once you account for the initialization order.
| Signal | GPIO / Pin | Description |
|---|---|---|
| MOSI | IO11 | SPI data out to SD card |
| SCK | IO12 | SPI clock |
| MISO | IO13 | SPI data in from SD card |
| CS | CH422G EXIO4 | Chip select (active low, via IO expander) |
The correct startup order matters here. The IO expander must be alive and its EXIO4 pin configured as an output before the SPI SD card driver can assert chip select.
Initialize I2C bus (IO8 SDA, IO9 SCL) for CH422G communication
Initialize the CH422G IO expander and configure EXIO4 as output
Assert CS low (EXIO4 LOW) to select the SD card
Initialize the SPI bus on IO11/IO12/IO13
Mount the SD card filesystem
#include <SD.h>#include <SPI.h>#include <ESP_IOExpander_Library.h>
#define SD_MOSI 11#define SD_SCK 12#define SD_MISO 13#define SD_CS -1 // Managed by IO expander, not a direct GPIO
ESP_IOExpander_CH422G *expander;
void setup() { Serial.begin(115200);
// Initialize IO expander first expander = new ESP_IOExpander_CH422G( (i2c_port_t)0, ESP_IO_EXPANDER_I2C_CH422G_ADDRESS, 8, 9); expander->init(); expander->begin();
// Set SD CS pin via expander expander->pinMode(4, OUTPUT); // EXIO4 = SD_CS expander->digitalWrite(4, LOW); // Select SD card
// Initialize SPI and SD SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
if (SD.begin(SD_CS, SPI)) { uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf("SD Card mounted: %lluMB\n", cardSize);
// List root directory File root = SD.open("/"); while (File entry = root.openNextFile()) { Serial.printf(" %s (%d bytes)\n", entry.name(), entry.size()); entry.close(); } root.close(); } else { Serial.println("SD Card mount failed"); }}
void loop() {}Once the card is mounted, standard Arduino SD library calls work as expected. Here is a minimal data logging example.
void logData(const char *filename, const char *data) { File file = SD.open(filename, FILE_APPEND); if (file) { file.println(data); file.close(); Serial.printf("Logged to %s\n", filename); } else { Serial.printf("Failed to open %s\n", filename); }}
String readFile(const char *filename) { File file = SD.open(filename, FILE_READ); if (!file) return "";
String content; while (file.available()) { content += (char)file.read(); } file.close(); return content;}In ESP-IDF, use spi_bus_initialize() with the SPI pins and mount the SD card filesystem using esp_vfs_fat_sdspi_mount(). The CS pin management through the CH422G requires initializing the IO expander driver separately before the SPI SD host configuration. The VFS mount integrates the SD card into the POSIX file system, so standard fopen(), fwrite(), and fread() calls work transparently.
Card Types
Standard microSD and microSDHC cards are supported in SPI mode. SDXC cards may work if formatted as FAT32, but the SPI mode interface limits maximum throughput compared to native SDIO.
Filesystems
FAT16 and FAT32 are natively supported by both the Arduino SD library and ESP-IDF’s FATFS component. exFAT support requires additional library configuration and is not enabled by default.
Mount Failure
The most common cause is forgetting to initialize the CH422G IO expander before calling SD.begin(). Verify the IO expander is responding on I2C (address 0x20) and that EXIO4 is configured as an output pulled LOW.
Slow Performance
SPI mode is inherently slower than native SDIO. For best throughput, use large block reads/writes rather than byte-at-a-time operations. Also ensure you are using a Class 10 or UHS-I card — older, slower cards will bottleneck the interface.