En este tutorial, aprenderás cómo conectar un módulo de tarjeta Micro SD al ESP32 y realizar operaciones básicas con archivos. Cubriremos cómo listar directorios, crear y eliminar archivos y carpetas, leer y escribir datos, y más. Al final, tendrás una base sólida para integrar almacenamiento en tarjeta SD en tus propios proyectos.
Partes necesarias
A continuación encontrarás los componentes necesarios para este tutorial. Usé una placa ESP32 antigua, que ya está descontinuada pero aún puedes conseguirla a muy bajo precio. Sin embargo, cualquier otro ESP32 también sirve.
Además, necesitamos un módulo de tarjeta Micro SD. Cualquier módulo con interfaz SPI y lógica de 3.3V funcionará. Si quieres conectar el módulo de tarjeta SD a un Arduino UNO, necesitas un módulo con lógica de 5V o un convertidor de nivel lógico. Elegí un módulo muy simple y pequeño sin convertidor de nivel, ya que lo conectaremos a un ESP32 con lógica de 3.3V.

Módulo de tarjeta Micro SD

Tarjeta Micro SD 8GB

ESP32 lite

Cable de datos USB

Juego de cables Dupont

Protoboard
Conceptos básicos de los módulos lectores de tarjetas SD
Los módulos de tarjetas MicroSD sirven como interfaz entre un microcontrolador, como el ESP32, y una tarjeta MicroSD. Permiten que el microcontrolador lea y escriba datos en la tarjeta, ampliando efectivamente la capacidad de almacenamiento para registros, medios o archivos de configuración.
Cómo funcionan los módulos de tarjetas MicroSD
En esencia, las tarjetas MicroSD se comunican usando el protocolo SPI (Serial Peripheral Interface). El módulo contiene un socket para la tarjeta MicroSD y un circuito convertidor de nivel si es necesario, para igualar los niveles de voltaje entre la tarjeta y el microcontrolador. Cuando está conectado, el ESP32 actúa como maestro SPI, enviando comandos y recibiendo datos de la tarjeta MicroSD, que actúa como esclavo SPI.
El bus SPI usa líneas separadas para entrada de datos (MOSI), salida de datos (MISO), reloj (SCK) y selección de chip (CS). Esta comunicación full-duplex permite una transferencia de datos rápida y fiable, esencial para operaciones del sistema de archivos.
¿Por qué SPI y no I2C?
SPI se prefiere sobre I2C para la comunicación con tarjetas MicroSD debido a sus mayores velocidades de transferencia y protocolo más simple. I2C es un bus de dos cables diseñado para comunicación a corta distancia y baja velocidad, típicamente hasta 400 kHz o 1 MHz en modos rápidos. En cambio, SPI puede operar fácilmente a varios MHz, lo cual es crítico para leer y escribir archivos grandes eficientemente.
Además, SPI soporta comunicación full-duplex, permitiendo enviar y recibir datos simultáneamente, mientras que I2C es half-duplex. La especificación de la tarjeta MicroSD soporta explícitamente el modo SPI, haciendo de SPI la opción natural.
Pines SPI
La siguiente tabla muestra los pines necesarios para SPI:
| Pin SPI | Función | Descripción |
|---|---|---|
| MOSI | Master Out Slave In | Transporta datos del ESP32 a la tarjeta MicroSD |
| MISO | Master In Slave Out | Transporta datos de la tarjeta MicroSD al ESP32 |
| SCK | Reloj serial | Sincroniza la transmisión de datos |
| CS | Chip Select (Selección de esclavo) | Selecciona la tarjeta MicroSD para la comunicación |
El ESP32 típicamente permite asignar estos pines de forma flexible mediante su matriz GPIO, pero los valores comunes por defecto son GPIO 23 para MOSI, GPIO 19 para MISO, GPIO 18 para SCK y GPIO 5 para CS.
Tipos de módulos de tarjetas MicroSD: 5V vs 3.3V
Existen dos tipos principales de módulos de tarjetas MicroSD según la compatibilidad de voltaje. La mayoría de las placas Arduino operan con niveles lógicos de 5V, por lo que los módulos diseñados para Arduino incluyen reguladores de voltaje y convertidores de nivel para conectar de forma segura las tarjetas MicroSD de 3.3V con señales y alimentación de 5V.
La imagen siguiente muestra el frente y reverso de un módulo de tarjeta MicroSD de 5V. Se pueden ver el regulador de voltaje y el circuito integrado convertidor de nivel lógico:

Sin embargo, si tu microcontrolador ya opera con niveles lógicos de 3.3V (ESP32), no se requiere regulador de voltaje ni convertidor de nivel. Entonces puedes usar un módulo lector de tarjeta SD más simple y económico. La siguiente imagen muestra el frente y reverso de un módulo típico de 3.3V:

Si observas de cerca la placa de 3.3V verás que no hay circuitos integrados para regulación de voltaje ni conversión de nivel. Pero sí debe haber resistencias pull-up de 10K para la interfaz SPI:

Evita placas que no tengan estas resistencias pull-up y no conectes un módulo de tarjeta MicroSD de 3.3V a un Arduino de 5V.
Tamaños y tipos de tarjetas MicroSD
Las tarjetas MicroSD vienen en varias capacidades de almacenamiento y clases de velocidad. Los tamaños más comunes van desde 2GB hasta 1TB, con las siguientes clasificaciones:
| Tipo | Nombre | Tamaño |
|---|---|---|
| SDSC | Capacidad estándar | Hasta 2GB |
| SDHC | Alta capacidad | 4GB a 32GB |
| SDXC | Capacidad extendida | 64GB a 2TB |
La biblioteca SD del ESP32 soporta tarjetas formateadas con sistemas de archivos FAT32.
Clases de velocidad de tarjetas MicroSD
Las clases de velocidad definen la velocidad mínima sostenida de escritura de la tarjeta, lo cual es crítico para aplicaciones como grabación de video o registro de datos.
| Clase de velocidad | Velocidad mínima de escritura | Descripción |
|---|---|---|
| Clase 2 | 2 MB/s | Adecuada para video en definición estándar |
| Clase 4 | 4 MB/s | Adecuada para video en alta definición |
| Clase 6 | 6 MB/s | Adecuada para grabación de video HD |
| Clase 10 | 10 MB/s | Adecuada para video Full HD |
| UHS-I U1 | 10 MB/s | Ultra alta velocidad, adecuada para video HD |
| UHS-I U3 | 30 MB/s | Ultra alta velocidad, adecuada para video 4K |
Para la mayoría de proyectos con ESP32, una tarjeta Clase 10 o UHS-I U1 ofrece un buen equilibrio entre velocidad y coste. Las tarjetas de mayor velocidad no siempre proporcionan mejor rendimiento debido a limitaciones del microcontrolador y del bus SPI.
Conectar el módulo de tarjeta SD al ESP32
Conectar tu módulo de tarjeta SD al ESP32 vía SPI es sencillo. La siguiente imagen muestra el cableado entre un ESP32 Lite y un módulo de tarjeta MicroSD de 3.3V:

Y para mayor comodidad, aquí está el mapeo de pines entre el módulo de tarjeta SD y el ESP32:
| Pin del módulo de tarjeta SD | Pin del ESP32 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| MISO | GPIO 19 |
| MOSI | GPIO 23 |
| SCK/CLK | GPIO 18 |
| CS (Chip Select) | GPIO 5 |
Asegúrate de usar el pin de 3.3V del ESP32 para alimentar el módulo de tarjeta SD. La tarjeta SD opera a 3.3V, y aplicar 5V puede dañarla.
Formatear la tarjeta SD
Antes de usar tu tarjeta SD con el ESP32, es importante asegurarte de que esté correctamente formateada. La mayoría de las tarjetas SD vienen preformateadas con el sistema de archivos FAT32, que es compatible con la biblioteca SD del ESP32.
Sin embargo, si tu tarjeta es nueva o ha sido usada con otros dispositivos, puede ser necesario formatearla. Puedes formatear la tarjeta SD usando tu ordenador:
Windows
- Inserta la tarjeta SD en tu PC.
- Abre el Explorador de archivos y haz clic derecho en la unidad de la tarjeta SD.
- Selecciona Formatear.
- Elige FAT32 como sistema de archivos.
- Haz clic en Iniciar para formatear.

macOS
- Inserta la tarjeta SD.
- Abre Utilidad de Discos.
- Selecciona la tarjeta SD en la barra lateral.
- Haz clic en Borrar.
- Elige MS-DOS (FAT) como formato.
- Haz clic en Borrar para formatear.
Linux
Usa el mkfs comando en la terminal. Reemplaza /dev/sdX1 con el identificador de dispositivo de tu tarjeta SD:
sudo mkfs.vfat -F 32 /dev/sdX1
Consejos importantes
- Haz copia de seguridad de cualquier dato importante antes de formatear.
- No uses exFAT ni NTFS, ya que no son compatibles con la biblioteca SD del ESP32.
- Después de formatear, expulsa la tarjeta de forma segura antes de insertarla en el módulo de tarjeta SD.
Tamaño máximo de tarjeta SD
En Windows, el tamaño máximo de tarjeta que puedes formatear desde el explorador de archivos es 32GB. Es posible formatear tarjetas SD de mayor capacidad como FAT32 y usarlas con un ESP32. Sin embargo, necesitarás usar herramientas de software diferentes. Por ejemplo, existen FAT32Format o guiformat-x64.
Clase SD
La mayoría de las funciones relacionadas con tarjetas SD se encuentran en la biblioteca SD y específicamente en la SDClass. El código a continuación muestra la clase SDClass con sus declaraciones de funciones. Échale un vistazo rápido y en las siguientes secciones aprenderás a usar las diferentes funciones de 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;
};
Inicializar el módulo de tarjeta SD
Antes de poder trabajar con archivos en tu tarjeta SD, necesitas inicializar el módulo de tarjeta SD con tu ESP32. La inicialización establece la comunicación entre el ESP32 y la tarjeta SD, asegurando que la tarjeta esté lista para operaciones con archivos. El siguiente código muestra cómo hacerlo:
#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 función SD.begin() devuelve true si la tarjeta se inicializa correctamente. Si devuelve false, la tarjeta podría no estar conectada correctamente o podría estar formateada incorrectamente. También revisa el cableado del módulo de tarjeta SD con el ESP32.
Ten en cuenta que SD.begin() tiene parámetros que puedes configurar si el valor por defecto no funciona para ti:
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);
Lo más importante es que puedes cambiar los pines para la interfaz SPI aquí. La biblioteca SD usa por defecto los pines SPI VSPI (23, 19, 18, 5).
Para cambiar los pines, redefine las constantes de pines, crea una instancia de SPIClass y pasa esta instancia al método SD.begin(). Aquí tienes un ejemplo:
#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 vez inicializada, la tarjeta SD está lista para operaciones con archivos como lectura, escritura y gestión de directorios.
Leer el tipo de tarjeta SD
Después de inicializar el módulo de tarjeta SD, puedes identificar programáticamente el tipo de tarjeta conectada. La función sd.cardType() devuelve un entero que representa el tipo de tarjeta. Aquí tienes un ejemplo de código que imprime el tipo de tarjeta en el Monitor Serial:
#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() { }
Comenzamos inicializando la tarjeta SD con SD.begin(). Luego, llamamos a SD.cardType() para obtener el tipo de tarjeta y finalmente imprimimos el tipo en el Monitor Serial.
Obtener información de la tarjeta SD
La biblioteca SD también tiene funciones para obtener el tamaño de una tarjeta SD, el espacio total disponible y el espacio usado. Mira el siguiente código:
#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() {}
Listar directorio
Lamentablemente, la biblioteca SD no tiene una función para listar recursivamente todos los archivos y carpetas almacenados en una tarjeta SD. Tendrás que implementar esta función tú mismo. Aquí está el código para una función printDir() que imprime la estructura de carpetas y archivos en el Monitor Serial:
#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() {}
Si solo necesitas los archivos dentro de una carpeta específica, puedes usar el siguiente código:
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();
}
}
Crear o eliminar directorios
Para crear o eliminar directorios, la biblioteca SD ofrece las funciones mkdir() y rmdir(). Aquí tienes un ejemplo de código que primero crea un directorio y luego lo elimina:
#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() {}
Ten en cuenta que debes crear y eliminar carpetas anidadas individualmente y que las carpetas deben estar vacías antes de poder eliminarlas. Aquí un ejemplo de cómo crear y eliminar una estructura de carpetas anidadas:
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");
}
Funciones de archivos
Puedes encontrar todas las funciones relacionadas con archivos en el archivo File.cpp de la biblioteca SD. A continuación, un resumen rápido de las funciones más importantes:
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);
En las siguientes secciones discutiremos estas funciones con más detalle.
Escribir datos en un archivo
Al escribir datos en un archivo, primero debes abrir el archivo, escribir los datos y luego cerrar el archivo. Aquí un ejemplo sin comprobación de errores:
File file = SD.open("/readme.txt", FILE_WRITE);
file.print("This is the readme");
file.close();
He comprobado que el nombre del archivo debe comenzar con una barra (‘/’) o la ruta desde la raíz, por ejemplo «/text/readme.txt». De lo contrario, no pude crear un archivo.
Ten en cuenta que también existe un método printf() que te permite escribir texto formateado en un archivo, por ejemplo:
File file = SD.open("/log.txt", FILE_WRITE);
file.printf("Temperature = %.2f\n", 21.3);
file.close();
Para simplificar, normalmente querrás envolver el código para escribir texto en un archivo dentro de una función:
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();
}
También ten en cuenta que puedes escribir datos binarios en lugar de texto en un archivo:
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 común para esto es la escritura de datos de sensores. Puedes crear una estructura con tus datos de sensor y luego escribir o añadir esta estructura como un bloque de datos a un archivo 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 vez escrito, puedes leer los datos binarios del sensor así:
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);
Si has añadido múltiples bloques de datos de sensor a un archivo y quieres leer un bloque específico, puedes usar la función 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();
Si necesitas conocer la posición actual del puntero de archivo mientras escribes, usa la función position().
Añadir datos a un archivo
Si quieres añadir datos a un archivo en lugar de sobrescribir su contenido, usa la bandera FILE_APPEND:
File file = SD.open("/readme.txt", FILE_APPEND);
file.print("This is more to read");
file.close();
Y aquí otra función de conveniencia con comprobación de errores para añadir texto a un archivo:
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();
}
Leer contenido de un archivo
Puedes leer el contenido de un archivo usando el método read():
File file = SD.open("/readme.txt");
while (file.available()) {
Serial.write(file.read());
}
file.close();
El método read() devuelve un entero y debes llamarlo repetidamente para leer el archivo completo. Normalmente haces esto en un bucle while y usas la función available() para determinar cuándo detenerte. Pero también podrías usar un bucle for para leer los primeros n caracteres de un archivo, por ejemplo.
A continuación una función de conveniencia que imprime el contenido de un archivo en el Monitor Serial:
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();
}
Vaciar archivo
Si mantienes un archivo abierto por un período prolongado, por ejemplo, para añadir periódicamente datos de sensores a un archivo de registro, asegúrate de vaciar el buffer del archivo con frecuencia. En caso de un corte de energía, esto reduce el riesgo de pérdida o corrupción de datos.
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();
Eliminar archivo
Para eliminar un archivo, llama a la función remove() con la ruta del archivo. También puedes comprobar previamente si el archivo existe:
if (SD.exists("/log.txt")) {
SD.remove("/log.txt");
}
Renombrar archivo
Renombrar un archivo es sencillo. Solo llama a la función rename():
if (!SD.rename("/logs/old_log.txt", "/logs/new_log.txt")) {
Serial.println("Could not rename file");
}
Y eso es todo. Ahora estás familiarizado con todas las funciones que necesitas para leer y escribir datos en una tarjeta SD.
Conclusión
En esta guía aprendiste cómo conectar un módulo de tarjeta Micro SD con el ESP32, formatear la tarjeta e inicializar la comunicación. También exploraste cómo listar directorios, crear y eliminar carpetas, y manejar archivos leyendo, escribiendo, añadiendo, renombrando y eliminando. Para más aplicaciones, consulta también la carpeta examples de la biblioteca SD.
Recuerda cerrar y vaciar tus archivos para evitar corrupción de datos. Esto es especialmente importante para registradores de datos, que pueden mantener un archivo abierto por períodos prolongados y son propensos a cortes de energía.
Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.
¡Feliz bricolaje!

