microSD cards
Gigabytes for a couple of pounds, and the one thing nobody budgets for - the card occasionally stops for tens of milliseconds to reorganise itself, and a logger sampling on a timer loses every sample that lands in the gap.
The pause you have to buffer around
SPI or SDMMC
SPI — four wires on any pins, about 400 kB/s, works on every chip. Right for almost every project.
SDMMC — one or four data lines on fixed pins, two to five times faster, and on the classic ESP32 those pins include GPIO 2 and 15, which are strapping pins. Worth it for audio or camera work, not for a data logger.
Habits that keep a logger alive
- Open the file once, not per row.
- Buffer a few kilobytes and write blocks.
- Flush on a timer, so a power cut costs seconds rather than everything.
- Start a new file per hour or per day; a corrupt tail then costs one file.
- Write CSV. It survives being read by anything, including a person.
The code
Buffer in RAM, write in blocks, keep the file open. Opening and closing a file per reading is what makes SD logging slow and what wears the card out fastest.
#include <SD.h>
#include <SPI.h>
const int CS = 5;
File log;
char buf[2048];
size_t used = 0;
void setup() {
Serial.begin(115200);
if (!SD.begin(CS)) { Serial.println("no card"); return; }
log = SD.open("/data.csv", FILE_APPEND);
}
void addRow(unsigned long t, float v) {
used += snprintf(buf + used, sizeof(buf) - used, "%lu,%.2f\n", t, v);
if (used > sizeof(buf) - 64) { // write a block at a time
log.write((uint8_t *)buf, used);
used = 0;
}
}
void loop() {
static unsigned long lastFlush = 0;
addRow(millis(), analogRead(34) * 3.3 / 4095);
if (millis() - lastFlush > 10000) { // survive a power cut
log.flush();
lastFlush = millis();
}
delay(10);
}Flush on a timer rather than on every write. An unflushed file loses its tail on a power cut, and flushing every row costs most of the throughput.
Mount the card as a directory and it becomes ordinary file access. The buffering is yours to add, same as above.
from machine import SPI, Pin
import os, sdcard, time
spi = SPI(2, baudrate=20_000_000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
sd = sdcard.SDCard(spi, Pin(5))
os.mount(sd, '/sd')
rows = []
with open('/sd/data.csv', 'a') as f:
for i in range(1000):
rows.append('{},{}\n'.format(time.ticks_ms(), i))
if len(rows) >= 64: # write in blocks
f.write(''.join(rows))
rows.clear()
time.sleep_ms(10)Format the card as FAT32 with a single partition. exFAT and 64 GB cards are supported unevenly, and a card that mounts on your laptop is not proof.
When it does not work
Formatting or wiring. FAT32, one partition, 32 GB or less is the safe combination. Then check that the breakout's level shifter works in both directions - many cheap ones do not.
The card pausing for internal housekeeping. Nothing is wrong with your code. Buffer a few kilobytes in RAM so the pause is covered.
Current. A card draws 100 mA in bursts, and on the classic ESP32 the SDMMC pins include strapping pins - a card present at boot can also stop the board starting.
The file was never flushed. Flush on a timer, and accept losing that window - or write to a new file each hour so a corrupt tail costs less.
All three stores live in the same chip, divided by a table you have never looked at. This is that table.
Partition tables and flash size →Edit this page — content/esp32/microsd-cards.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.