In questo tutorial imparerai come collegare un modulo Micro SD all’ESP32 ed eseguire operazioni di base sui file. Tratteremo l’elenco delle directory, la creazione e la cancellazione di file e cartelle, la lettura e la scrittura di dati e altro ancora. Alla fine, avrai una solida base per integrare lo storage su scheda SD nei tuoi progetti.
Componenti necessari
Di seguito trovi i componenti necessari per questo tutorial. Ho usato una vecchia scheda ESP32, ormai obsoleta ma ancora reperibile a basso costo. Comunque qualsiasi altro ESP32 va bene.
Inoltre, serve un modulo Micro SD. Qualsiasi modulo con interfaccia SPI e logica a 3.3V funzionerà. Se vuoi collegare il modulo SD a un Arduino UNO, ti serve un modulo con logica a 5V o un convertitore di livello. Ho scelto un modulo molto semplice e piccolo senza convertitore, dato che lo collegheremo a un ESP32 con logica a 3.3V.

Modulo Micro SD

Micro SD Card 8GB

ESP32 lite

Cavo dati USB

Set di fili Dupont

Breadboard
Nozioni di base sui moduli lettore SD
I moduli MicroSD fungono da interfaccia tra un microcontrollore, come l’ESP32, e una scheda MicroSD. Permettono al microcontrollore di leggere e scrivere dati sulla scheda, espandendo efficacemente la capacità di archiviazione per log, media o file di configurazione.
Come funzionano i moduli MicroSD
Alla base, le schede MicroSD comunicano usando il protocollo SPI (Serial Peripheral Interface). Il modulo contiene un alloggiamento per la MicroSD e un circuito level shifter se necessario, per adattare i livelli di tensione tra la scheda e il microcontrollore. Collegato, l’ESP32 agisce da master SPI, inviando comandi e ricevendo dati dalla MicroSD, che agisce da slave SPI.
Il bus SPI usa linee separate per input dati (MOSI), output dati (MISO), clock (SCK) e chip select (CS). Questa comunicazione full-duplex permette trasferimenti dati veloci e affidabili, essenziali per le operazioni sul file system.
Perché SPI e non I2C?
SPI è preferito a I2C per la comunicazione con MicroSD per le sue velocità di trasferimento dati più elevate e protocollo più semplice. I2C è un bus a due fili progettato per comunicazioni a breve distanza e bassa velocità, tipicamente fino a 400 kHz o 1 MHz in modalità veloce. Invece, SPI può facilmente operare a diversi MHz, fondamentale per leggere e scrivere file grandi in modo efficiente.
Inoltre, SPI supporta comunicazione full-duplex, permettendo invio e ricezione simultanei di dati, mentre I2C è half-duplex. La specifica MicroSD supporta esplicitamente la modalità SPI, rendendo SPI la scelta naturale.
Pin SPI
La tabella seguente mostra i pin necessari per SPI:
| Pin SPI | Funzione | Descrizione |
|---|---|---|
| MOSI | Master Out Slave In | Trasporta dati dall’ESP32 alla MicroSD |
| MISO | Master In Slave Out | Trasporta dati dalla MicroSD all’ESP32 |
| SCK | Serial Clock | Sincronizza la trasmissione dati |
| CS | Chip Select (Slave Select) | Seleziona la MicroSD per la comunicazione |
L’ESP32 permette tipicamente l’assegnazione flessibile di questi pin tramite la sua matrice GPIO, ma i default comuni sono GPIO 23 per MOSI, GPIO 19 per MISO, GPIO 18 per SCK e GPIO 5 per CS.
Tipi di moduli MicroSD: 5V vs 3.3V
Esistono due tipi principali di moduli MicroSD basati sulla compatibilità di tensione. La maggior parte delle schede Arduino opera a livelli logici 5V, quindi i moduli progettati per Arduino includono level shifter e regolatori di tensione onboard per interfacciarsi in sicurezza con le MicroSD a 3.3V.
L’immagine seguente mostra il fronte e il retro di un modulo MicroSD a 5V. Si vedono il regolatore di tensione e il circuito level shifter:

Tuttavia, se il tuo microcontrollore opera già a 3.3V (ESP32), non servono regolatori o level shifter. Puoi usare un modulo lettore SD più semplice e economico. L’immagine seguente mostra il fronte e il retro di un tipico modulo MicroSD a 3.3V:

Se osservi bene la scheda a 3.3V, non ci sono IC per regolazione o conversione di livello. Ma dovrebbero esserci resistori di pull-up da 10K per l’interfaccia SPI:

Evita schede senza questi resistori di pull-up e non collegare un modulo MicroSD a 3.3V a un Arduino a 5V!
Dimensioni e tipi di MicroSD
Le schede MicroSD sono disponibili in varie capacità e classi di velocità. Le dimensioni più comuni vanno da 2GB fino a 1TB, con le seguenti classificazioni:
| Tipo | Nome | Dimensione |
|---|---|---|
| SDSC | Capacità Standard | Fino a 2GB |
| SDHC | Alta Capacità | 4GB a 32GB |
| SDXC | Capacità Estesa | 64GB a 2TB |
La libreria SD dell’ESP32 supporta schede formattate con file system FAT32.
Classi di velocità delle MicroSD
Le classi di velocità definiscono la velocità minima di scrittura sostenuta della scheda, fondamentale per applicazioni come registrazione video o logging dati.
| Classe di velocità | Velocità minima di scrittura | Descrizione |
|---|---|---|
| Classe 2 | 2 MB/s | Adatta per video a definizione standard |
| Classe 4 | 4 MB/s | Adatta per video ad alta definizione |
| Classe 6 | 6 MB/s | Adatta per registrazioni video HD |
| Classe 10 | 10 MB/s | Adatta per video Full HD |
| UHS-I U1 | 10 MB/s | Ultra High Speed, adatta per video HD |
| UHS-I U3 | 30 MB/s | Ultra High Speed, adatta per video 4K |
Per la maggior parte dei progetti ESP32, una scheda Classe 10 o UHS-I U1 offre un buon equilibrio tra velocità e costo. Schede più veloci potrebbero non garantire prestazioni migliori a causa dei limiti del microcontrollore e del bus SPI.
Collegare il modulo SD all’ESP32
Collegare il modulo SD all’ESP32 via SPI è semplice. L’immagine seguente mostra il cablaggio tra un ESP32 Lite e un modulo MicroSD a 3.3V:

Per comodità, ecco la mappatura dei pin tra il modulo SD e l’ESP32:
| Pin modulo SD | Pin ESP32 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| MISO | GPIO 19 |
| MOSI | GPIO 23 |
| SCK/CLK | GPIO 18 |
| CS (Chip Select) | GPIO 5 |
Assicurati di usare il pin 3.3V dell’ESP32 per alimentare il modulo SD. La scheda SD funziona a 3.3V, applicare 5V può danneggiarla.
Formattare la scheda SD
Prima di usare la scheda SD con l’ESP32, è importante assicurarsi che sia formattata correttamente. La maggior parte delle schede SD arriva preformattata con file system FAT32, compatibile con la libreria SD dell’ESP32.
Tuttavia, se la tua scheda è nuova o è stata usata con altri dispositivi, potrebbe essere necessario formattarla. Puoi formattare la scheda SD usando il computer:
Windows
- Inserisci la scheda SD nel PC.
- Apri Esplora File e clicca col tasto destro sull’unità della scheda SD.
- Seleziona Formatta.
- Scegli FAT32 come file system.
- Clicca Avvia per formattare.

macOS
- Inserisci la scheda SD.
- Apri Utility Disco.
- Seleziona la scheda SD dalla barra laterale.
- Clicca su Cancella.
- Scegli MS-DOS (FAT) come formato.
- Clicca su Cancella per formattare.
Linux
Usa il comando mkfs nel terminale. Sostituisci /dev/sdX1 con l’identificatore del dispositivo della tua scheda SD:
sudo mkfs.vfat -F 32 /dev/sdX1
Consigli importanti
- Fai il backup di dati importanti prima di formattare.
- Non usare exFAT o NTFS, non supportati dalla libreria SD dell’ESP32.
- Dopo la formattazione, espelli la scheda in modo sicuro prima di inserirla nel modulo SD.
Dimensione massima della scheda SD
Su Windows la dimensione massima formattabile tramite Esplora File è 32GB. È possibile formattare schede di capacità superiore in FAT32 e usarle con ESP32, ma serve software diverso. Ad esempio, ci sono FAT32Format o guiformat-x64.
Classe SD
La maggior parte delle funzioni relative alle schede SD si trova nella libreria SD e specificamente nella SDClass. Il codice seguente mostra la SDClass con le sue dichiarazioni di funzione. Dagli un’occhiata veloce e nelle sezioni successive imparerai a usare le diverse funzioni della SDClass.
class SDClass {
private:
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile getParentDir(const char *filepath, int *indx);
public:
// This needs to be called to set up the connection to the SD card
// before other methods are used.
bool begin(uint8_t csPin = SD_CHIP_SELECT_PIN);
bool begin(uint32_t clock, uint8_t csPin);
//call this when a card is removed. It will allow you to insert and initialise a new card.
void end();
// Note that currently only one file can be open at a time.
File open(const char *filename, uint8_t mode = FILE_READ);
File open(const String &filename, uint8_t mode = FILE_READ) {
return open(filename.c_str(), mode);
}
bool exists(const char *filepath);
bool exists(const String &filepath) {
return exists(filepath.c_str());
}
bool mkdir(const char *filepath);
bool mkdir(const String &filepath) {
return mkdir(filepath.c_str());
}
bool remove(const char *filepath);
bool remove(const String &filepath) {
return remove(filepath.c_str());
}
bool rmdir(const char *filepath);
bool rmdir(const String &filepath) {
return rmdir(filepath.c_str());
}
private:
int fileOpenMode;
friend class File;
friend bool callback_openPath(SdFile&, const char *, bool, void *);
};
extern SDClass SD;
};
Inizializzare il modulo SD
Prima di lavorare con i file sulla scheda SD, devi inizializzare il modulo SD con l’ESP32. L’inizializzazione stabilisce la comunicazione tra ESP32 e scheda SD, assicurando che la scheda sia pronta per le operazioni sui file. Il codice seguente mostra come fare:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void setup() {
Serial.begin(115200);
if(!SD.begin()) {
Serial.println("Card Mount Failed");
return;
}
Serial.println("SD Card Initialized");
}
void loop() { }
La funzione SD.begin() restituisce true se la scheda è inizializzata con successo. Se restituisce false, la scheda potrebbe non essere collegata correttamente o formattata in modo errato. Controlla anche il cablaggio del modulo SD con l’ESP32.
Nota che SD.begin() ha parametri che puoi impostare se il default non funziona:
SD.begin(uint8_t ssPin=SS,
SPIClass &spi=SPI,
uint32_t frequency=4000000,
const char * mountpoint="/sd",
uint8_t max_files=5,
bool format_if_empty=false);
Soprattutto, puoi cambiare i pin per l’interfaccia SPI qui. La libreria SD usa di default i pin VSPI (23, 19, 18, 5).
Per cambiare i pin, ridefinisci le costanti dei pin, crei un’istanza di SPIClass e la passi al metodo SD.begin(). Ecco un esempio:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#define SCK 17
#define MISO 19
#define MOSI 23
#define CS 5
SPIClass spi = SPIClass(VSPI);
void setup() {
spi.begin(SCK, MISO, MOSI, CS);
if (!SD.begin(CS, spi)) {
Serial.println("Card Mount Failed");
}
}
void loop() {}
Una volta inizializzata, la scheda SD è pronta per operazioni come lettura, scrittura e gestione delle directory.
Lettura del tipo di scheda SD
Dopo aver inizializzato il modulo SD, puoi identificare programmaticamente il tipo di scheda collegata. La funzione sd.cardType() restituisce un intero che rappresenta il tipo di scheda. Ecco un esempio di codice che stampa il tipo di scheda sul Monitor Seriale:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void setup() {
Serial.begin(115200);
if (!SD.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
} else if (cardType == CARD_MMC) {
Serial.println("MMC Card");
} else if (cardType == CARD_SD) {
Serial.println("Standard SD Card");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC Card");
} else {
Serial.println("Unknown Card Type");
}
}
void loop() { }
Iniziamo inizializzando la scheda SD con SD.begin(). Poi chiamiamo SD.cardType() per ottenere il tipo di scheda e infine stampiamo il tipo sul Monitor Seriale.
Ottenere informazioni sulla scheda SD
La libreria SD ha anche funzioni per recuperare la dimensione della scheda, lo spazio totale disponibile e lo spazio usato. Vedi il codice seguente:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void setup() {
Serial.begin(115200);
SD.begin();
uint64_t MB = 1024 * 1024;
uint64_t cardSize = SD.cardSize();
Serial.printf("SD Card Size: %lluMB\n", cardSize / MB);
uint64_t totalBytes = SD.cardSize();
Serial.printf("Total space: %lluMB\n", totalBytes / MB);
uint64_t usedBytes = SD.usedBytes();
Serial.printf("Used space: %lluMB\n", usedBytes / MB);
}
void loop() {}
Elencare la directory
Purtroppo la libreria SD non ha una funzione per elencare ricorsivamente tutti i file e le cartelle su una scheda SD. Devi implementare questa funzione da solo. Ecco il codice per una funzione printDir() che stampa la struttura di cartelle e file sul Monitor Seriale:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void printDir(const char *dirname, uint8_t levels = 0) {
File root = SD.open(dirname);
if (!root || !root.isDirectory()) return;
File file = root.openNextFile();
while (file) {
for (int i = 0; i < levels; i++) Serial.print(" ");
if (file.isDirectory()) {
Serial.printf("[%s]\n", file.name());
printDir(file.path(), levels + 1);
} else {
Serial.printf("%s (%d)\n", file.name(), file.size());
}
file = root.openNextFile();
}
}
void setup() {
Serial.begin(115200);
SD.begin();
printDir("/");
}
void loop() {}
Se ti servono solo i file in una cartella specifica, puoi usare questo codice:
void printDir(const char *dirname) {
File dir = SD.open(dirname);
File file = dir.openNextFile();
while (file) {
if (!file.isDirectory()) {
Serial.printf("%s (%d)\n", file.name(), file.size());
}
file = dir.openNextFile();
}
}
Creare o cancellare directory
Per creare o rimuovere directory la libreria SD offre le funzioni mkdir() e rmdir(). Ecco un esempio che prima crea una directory e poi la cancella:
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void setup() {
Serial.begin(115200);
SD.begin();
const char *path = "/data";
if (!SD.mkdir(path)) {
Serial.println("mkdir failed");
}
if (!SD.rmdir(path)) {
Serial.println("rmdir failed");
}
}
void loop() {}
Nota che devi creare e cancellare cartelle annidate singolarmente e che le cartelle devono essere vuote prima di poterle cancellare. Ecco un esempio per creare e cancellare una struttura di cartelle annidate:
void setup() {
SD.begin();
SD.mkdir("/data");
SD.mkdir("/data/temp");
SD.mkdir("/data/temp/kitchen");
SD.rmdir("/data/temp/kitchen");
SD.rmdir("/data/temp");
SD.rmdir("/data");
}
Funzioni sui file
Tutte le funzioni relative ai file si trovano nel file File.cpp della libreria SD. Qui una panoramica rapida delle funzioni più importanti:
File file = SD.open(path, FILE_WRITE); File file = SD.open(path, FILE_APPEND); file.write(buf, size); file.print(text); file.read(buf, nbyte); file.read(); file.available(); uint32_t pos = file.position(); file.seek(pos); file.size(); file.flush(); file.close(); SD.exists(path); SD.remove(path); SD.rename(path1, path2);
Nelle sezioni successive discuteremo queste funzioni in dettaglio.
Scrivere dati su file
Quando scrivi dati su un file, devi prima aprirlo, scrivere i dati e poi chiuderlo. Ecco un esempio senza controllo errori:
File file = SD.open("/readme.txt", FILE_WRITE);
file.print("This is the readme");
file.close();
Ho notato che il nome del file deve iniziare con una slash (‘/’) o il percorso dalla root, es. “/text/readme.txt”. Altrimenti non riuscivo a creare un file.
Nota che esiste anche un metodo printf() che permette di scrivere testo formattato su un file, per esempio:
File file = SD.open("/log.txt", FILE_WRITE);
file.printf("Temperature = %.2f\n", 21.3);
file.close();
Per semplificare, di solito conviene racchiudere il codice per scrivere testo in una funzione:
void writeFile(const char *path, const char *text) {
File file = SD.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
file.print(text);
file.close();
}
Nota anche che puoi scrivere dati binari invece di testo su un file:
File file = SD.open("/data.bin", FILE_WRITE);
uint8_t buffer[4] = { 0xDE, 0xAD, 0xBE, 0xEF };
size_t bytesWritten = file.write(buffer, sizeof(buffer));
if (bytesWritten != sizeof(buffer)) {
Serial.println("Write error!");
}
file.close();
Un caso d’uso comune è la scrittura di dati da sensori. Puoi creare una struct con i dati del sensore e poi scrivere o aggiungere questa struct come blocco dati a un file binario:
struct SensorData {
uint32_t timestamp;
int16_t temperature;
int16_t humidity;
};
SensorData data = { millis(), 234, 81 };
File file = SD.open("/log.bin", FILE_WRITE);
file.write((uint8_t *)&data, sizeof(data));
file.close();
Una volta scritto, puoi leggere i dati binari del sensore così:
SensorData data;
File file = SD.open("/log.bin");
file.read((uint8_t *)&data, sizeof(data));
file.close();
Serial.printf("timestamp: %d\n", data.timestamp);
Serial.printf("temperature: %d\n", data.temperature);
Serial.printf("humidity: %d\n", data.humidity);
Se hai aggiunto più blocchi di dati sensore a un file e vuoi leggere un blocco specifico, puoi usare la funzione seek():
SensorData data;
int n = 4; // Read 5ths sample (0...4)
File file = SD.open("/log.bin");
file.seek(n * sizeof(data));
file.read((uint8_t *)&data, sizeof(data));
file.close();
Se ti serve conoscere la posizione corrente del puntatore del file mentre scrivi, usa la funzione position().
Aggiungere dati a un file
Se vuoi aggiungere dati a un file senza sovrascriverne il contenuto, usa il flag FILE_APPEND:
File file = SD.open("/readme.txt", FILE_APPEND);
file.print("This is more to read");
file.close();
Ecco di nuovo una funzione di comodità con controllo errori per aggiungere testo a un file:
void appendFile(const char* path, const char* text) {
File file = SD.open(path, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (!file.print(text)) {
Serial.println("Append failed");
}
file.close();
}
Leggere il contenuto di un file
Puoi leggere il contenuto di un file usando il metodo read():
File file = SD.open("/readme.txt");
while (file.available()) {
Serial.write(file.read());
}
file.close();
Il metodo read() restituisce un intero e devi chiamarlo ripetutamente per leggere tutto il file. Tipicamente lo fai in un ciclo while e usi la funzione available() per sapere quando fermarti. Ma potresti anche usare un ciclo for per leggere i primi n caratteri di un file, per esempio.
Qui sotto una funzione di comodità che stampa il contenuto di un file sul Monitor Seriale:
void printFile(const char* path) {
File file = SD.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
Flush del file
Se tieni un file aperto a lungo, per esempio per aggiungere periodicamente dati di sensori a un log, assicurati di svuotare frequentemente il buffer del file. In caso di interruzione di corrente riduce il rischio di perdita o corruzione dati.
File file = SD.open("/log.txt", FILE_WRITE);
for(int i=0; i<10; i++) {
file.print("temp = %d", i);
file.flush();
delay(1000);
}
file.close();
Cancellare un file
Per cancellare un file, chiama la funzione remove() con il percorso del file. Puoi anche verificare prima se il file esiste:
if (SD.exists("/log.txt")) {
SD.remove("/log.txt");
}
Rinominare un file
Rinominare un file è semplice. Basta chiamare la funzione rename():
if (!SD.rename("/logs/old_log.txt", "/logs/new_log.txt")) {
Serial.println("Could not rename file");
}
Ecco fatto. Ora conosci tutte le funzioni necessarie per leggere e scrivere dati su una scheda SD.
Conclusione
In questa guida hai imparato come collegare un modulo Micro SD all’ESP32, formattare la scheda e inizializzare la comunicazione. Hai anche visto come elencare directory, creare e cancellare cartelle, e gestire file leggendo, scrivendo, aggiungendo, rinominando e cancellando. Per altre applicazioni, vedi anche la cartella examples della libreria SD.
Ricorda di chiudere e svuotare i file per evitare corruzione dati. Questo è particolarmente importante per i data logger, che possono tenere un file aperto a lungo e sono soggetti a interruzioni di corrente.
Se hai domande, sentiti libero di lasciarle nei commenti.
Buon divertimento con il tinkering!

