SD Card on SparkFun Thing Plus ESP32-C6 — Lessons Learned
Date: 2026-06-12
Project: CoffeeKeeper
The Short Version
The ESP32-C6 does NOT auto-assign SPI pins like older ESP32 boards.
You must explicitly declare all 4 SPI pins AND pass the SPI object to SD.begin().
Cards >32GB must be formatted FAT32 using the SD Association Formatter tool or diskpart.
Lesson 1 — Pin Mapping (CRITICAL)
The ESP32-C6 chip does not automatically assign standard SPI pins.
Always declare all 4 pins explicitly. The SparkFun Thing Plus ESP32-C6
routes the microSD slot to these internal pins:
#define SD_CS 18 // Chip Select
#define SD_SCK 19 // Clock
#define SD_MOSI 20 // PICO on schematic (Controller Out, Peripheral In)
#define SD_MISO 21 // POCI on schematic (Controller In, Peripheral Out)
#define SD_DET 22 // Card Detect — LOW = card inserted
Warning on schematic naming: The SparkFun schematic labels MOSI as “PICO”
and MISO as “POCI” — this is the PERIPHERAL perspective, opposite of what
you might expect from the controller perspective. Don’t get confused:
- PICO (IO20) = MOSI from ESP32’s perspective
- POCI (IO21) = MISO from ESP32’s perspective
Lesson 2 — Include Order and Style (CRITICAL)
Use quotes not angle brackets, and put FS.h first:
// CORRECT — use quotes, FS first
#include "FS.h"
#include "SD.h"
#include "SPI.h"
// WRONG — angle brackets cause issues on C6
#include <SD.h>
#include <SPI.h>
#include <FS.h>
Lesson 3 — Pass SPI Object to SD.begin() (CRITICAL)
The default SD library on C6 does not auto-configure the SPI bus.
You MUST pass the SPI object explicitly to SD.begin():
// CORRECT
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, SPI)) {
Serial.println("Card Mount Failed!");
}
// WRONG — fails silently on C6
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS)) { // missing SPI object!
Serial.println("Card Mount Failed!");
}
Lesson 4 — Power Rail Settling Delay
The 3.3V rail on the ESP32-C6 sometimes needs time to stabilize
after boot before the SD card will respond. Add a delay before SPI.begin():
delay(2000); // wait for 3.3V rail to settle
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
Lesson 5 — Retry Loop (IMPORTANT)
SD.begin() can fail intermittently on the C6 even with correct pins.
Always use a retry loop with SD.end() between attempts:
bool sdOnline = false;
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
for (int attempt = 1; attempt <= 3 && !sdOnline; attempt++) {
if (attempt > 1) {
Serial.printf("[RETRY] SD attempt %d/3\n", attempt);
SD.end();
delay(500);
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
}
if (SD.begin(SD_CS, SPI)) {
uint8_t cardType = SD.cardType();
if (cardType != CARD_NONE) {
sdOnline = true;
Serial.println("[OK] SD card ready");
}
}
}
if (!sdOnline) {
Serial.println("[ERROR] SD failed after 3 attempts");
}
Lesson 6 — FAT32 Formatting for Large Cards (CRITICAL)
Cards larger than 32GB ship formatted as exFAT by default.
The ESP32 SD library does NOT support exFAT — it will fail silently.
Always format SD cards as FAT32 before use.
Option A — SD Association Formatter (recommended for ≤32GB)
Download: SD Memory Card Formatter for Windows/Mac - SD Association
Note: For cards >32GB this tool also formats as exFAT — use diskpart instead.
Option B — Windows diskpart (works for any size)
Run Command Prompt as Administrator:
diskpart
list disk ← find your SD card disk number
select disk X ← replace X with SD card number (BE CAREFUL)
list partition
select partition 1
format fs=fat32 quick
exit
How to verify
After formatting, check Windows Explorer — right-click the card → Properties.
File system should show FAT32, not exFAT.
Lesson 7 — Card Detect Pin
The Thing Plus C6 has a card detect pin on IO22.
LOW = card inserted, HIGH = no card.
Check this first to confirm physical seating before debugging software:
pinMode(SD_DET, INPUT_PULLUP);
if (digitalRead(SD_DET) == HIGH) {
Serial.println("[WARN] SD card not detected — check seating");
}
Lesson 8 — Filename Length (FAT32 Limit)
FAT32 supports long filenames (up to 255 characters) but keep filenames
short and simple to avoid edge cases. Always prefix with / for root directory.
Avoid special characters.
// GOOD — short, simple, starts with /
String name = "/BME_data_12345.json";
// RISKY — long names can cause open() to fail
String name = "BME_Log_empty_baseline_Board_683422375_P0_4046_F0.json";
Working Minimal Example
Verified working on SparkFun Thing Plus ESP32-C6:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#define SD_CS 18
#define SD_SCK 19
#define SD_MOSI 20
#define SD_MISO 21
#define SD_DET 22
void setup() {
Serial.begin(115200);
delay(2000); // power rail settling
// Check card detect
pinMode(SD_DET, INPUT_PULLUP);
if (digitalRead(SD_DET) == HIGH) {
Serial.println("No card detected!");
return;
}
// Init SPI and SD
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, SPI)) {
Serial.println("SD mount failed!");
return;
}
Serial.print("Card type: ");
Serial.println(SD.cardType()); // 2 = SDHC
// Write a test file
File f = SD.open("/test.txt", FILE_WRITE);
if (f) {
f.println("Hello from ESP32-C6!");
f.close();
Serial.println("Write OK");
}
}
void loop() {}
Quick Diagnostic Checklist
When SD fails, go through in order:
- Is card physically inserted? Check SD_DET pin (should be LOW)
- Is card formatted FAT32? (not exFAT — check with diskpart or Properties)
- Are all 4 SPI pins explicitly declared? (CS=18, SCK=19, MOSI=20, MISO=21)
- Is SPI object passed to SD.begin(SD_CS, SPI)?
- Is include order correct? (“FS.h” first, then “SD.h”, then “SPI.h”)
- Is there a delay(2000) before SPI.begin() for power rail settling?
- Is there a retry loop with SD.end() between attempts?
- Is the filename short and starting with “/”?
Key References
- SparkFun Thing Plus ESP32-C6 Schematic: IO18=CS, IO19=SCK, IO20=MOSI, IO21=MISO, IO22=SD_DET
- SD Association Formatter: SD Memory Card Formatter for Windows/Mac - SD Association
- Verified working ESP32 core version: 3.3.10
- FAT32 max card size without special tools: 32GB (use diskpart for larger)