Logging to a microSD card works with any XIAO ESP32S3: wire a module to the SPI pins D8 (SCK), D9 (MISO), D10 (MOSI) and CS on D3, and power it from 5V.
The Sense version makes it simpler — its camera board has a card slot on the same SPI lines. The sketch appends a line to /boots.txt at every reset and prints the file, a quick test that writes really persist.
Wiring
| XIAO ESP32S3 | MicroSD card module (SPI) | Wire | Notes |
|---|---|---|---|
| 5V | VCC | red | The common module needs 5 V for its regulator — USB power only. |
| GND | GND | black | |
| D8 | SCK | yellow | |
| D9 | MISO | blue | |
| D10 | MOSI | green | |
| D3 | CS | purple |
4.5–5.5 V on the common module with regulator (3.3 V-only boards also sold) part, 3.3 V board. The common blue module has an AMS1117 3.3 V regulator and a 74LVC125 level shifter, so it needs 5 V on VCC — 3.3 V leaves the card under-powered. Its signal pins accept 3.3 V or 5 V logic, so ESP32, Pico and Uno all connect directly.
Board quirks
- Have a XIAO ESP32S3 Sense? Its expansion board already has a microSD slot on the same D8/D9/D10 lines, with CS on GPIO21. Set
SD_CSto 21 and skip the module. - GPIO21 is also the orange user LED, so on the Sense the LED flickers with card access — expected.
- The blue module’s regulator needs 5 V, and the XIAO’s 5V pin is dead on battery. For a battery logger use the Sense slot or a 3.3 V-only card module on 3V3.
- Cards must be FAT32 (32 GB or smaller works best).
Code
// microSD card module on a Seeed XIAO ESP32S3.
// VCC → 5V (module with regulator), GND → GND, SCK → D8 (GPIO7), MISO → D9 (GPIO8),
// MOSI → D10 (GPIO9), CS → D3 (GPIO4).
// On the XIAO ESP32S3 Sense, the built-in card slot uses the same D8/D9/D10 lines with CS on
// GPIO21: set SD_CS to 21 and skip the external module.
#include <SPI.h>
#include <SD.h>
constexpr uint8_t SD_CS = D3; // 21 for the Sense board's own slot
void setup() {
Serial.begin(115200);
uint32_t t0 = millis();
while (!Serial && millis() - t0 < 3000) {}
SPI.begin(D8, D9, D10, SD_CS); // SCK, MISO, MOSI, SS
if (!SD.begin(SD_CS, SPI, 4000000)) {
Serial.println("Card mount failed - check wiring, 5V on VCC and that the card is FAT32");
while (true) delay(1000);
}
Serial.printf("Card size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));
File f = SD.open("/boots.txt", FILE_APPEND);
if (f) {
f.printf("Boot at %lu ms\n", millis());
f.close();
}
f = SD.open("/boots.txt");
if (f) {
Serial.println("--- /boots.txt ---");
while (f.available()) Serial.write(f.read());
f.close();
}
}
void loop() {}// microSD card module on a Seeed XIAO ESP32S3.
// VCC → 5V (module with regulator), GND → GND, SCK → D8 (GPIO7), MISO → D9 (GPIO8),
// MOSI → D10 (GPIO9), CS → D3 (GPIO4).
// On the XIAO ESP32S3 Sense, the built-in card slot uses the same D8/D9/D10 lines with CS on
// GPIO21: set SD_CS to 21 and skip the external module.
#include <SPI.h>
#include <SD.h>
constexpr uint8_t SD_CS = D3; // 21 for the Sense board's own slot
void setup() {
Serial.begin(115200);
uint32_t t0 = millis();
while (!Serial && millis() - t0 < 3000) {}
SPI.begin(D8, D9, D10, SD_CS); // SCK, MISO, MOSI, SS
if (!SD.begin(SD_CS, SPI, 4000000)) {
Serial.println("Card mount failed - check wiring, 5V on VCC and that the card is FAT32");
while (true) delay(1000);
}
Serial.printf("Card size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));
File f = SD.open("/boots.txt", FILE_APPEND);
if (f) {
f.printf("Boot at %lu ms\n", millis());
f.close();
}
f = SD.open("/boots.txt");
if (f) {
Serial.println("--- /boots.txt ---");
while (f.available()) Serial.write(f.read());
f.close();
}
}
void loop() {}Library: SD (Arduino, SparkFun (ESP32 and Pico cores ship their own SD library)).
Expected serial output
Card size: 15193 MB
--- /boots.txt ---
Boot at 512 ms
Boot at 498 msCard size: 15193 MB --- /boots.txt --- Boot at 512 ms Boot at 498 ms
Common errors
“Card mount failed”
Card not FAT32, CS on the wrong pin, or the module on 3V3. Check with the Sense slot (CS 21) if you have one.
Works on USB, fails on battery
The module lost its 5 V. Use a 3.3 V card module or the Sense slot.