💾 Almacenamiento Masivo con ESP32: Guía Definitiva del Módulo MicroSD


¿Te has quedado sin memoria interna en tus proyectos de IoT? El ESP32 es una bestia en conectividad, pero cuando necesitamos realizar Datalogging (registro de datos), almacenar archivos de configuración pesados o incluso servir imágenes en un servidor web local, la memoria flash integrada se queda corta.

La solución es económica y robusta: integrar un módulo lector de tarjetas MicroSD. En este post, aprenderás paso a paso cómo conectar este periférico utilizando el protocolo SPI, cómo gestionar el sistema de archivos (FAT32) desde el IDE de Arduino y, lo más importante, cómo realizar operaciones avanzadas de lectura y escritura. Ya sea que estés construyendo un reproductor MP3 o una estación meteorológica que guarde datos por meses, esta guía es tu punto de partida.

Módulo de tarjeta MicroSD

Existen diferentes módulos de tarjetas microSD compatibles con el ESP32. En este post vamos a ver una aplicación de estos económicos módulos que nos pueden servir para un sinfín de aplicaciones.

Este módulo de lector de tarjetas SD facilita las aplicaciones que implican el uso de una tarjeta SD proporcionando una interfaz sencilla con un microcontrolador y que se puede utilizar como unidades periféricas normales.

Programando el microcontrolador en consecuencia, es posible leer y escribir en una tarjeta SD.
Las señales procedentes de la tarjeta SD (MOSI, SCK, MISO y CS) son reportadas en el modulo y luego conectadas al conector de 2×8 pines pre-soldados. Esto facilita la integración del lector de tarjetas SD en diferentes tipos de proyectos.

La programación permite leer y escribir en la tarjeta SD usando su microcontrolador, y el modulo es adecuado para varios tipos de proyectos y requisitos, como reproductores MP3, sistemas de control MCU/ARM, etc.

En este post vamos a utilizar por tanto el módulo de tarjeta microSD que se muestra en la imagen superior (es un módulo de tarjeta microSD también es compatible con otros microcontroladores como Arduino y las placas ESP8266 NodeMCU así como el ESp32 y compatibles ), el cual se comunicará mediante el protocolo de comunicación SPI (aunque se puede usar cualquier otro módulo de tarjeta microSD con una interfaz SPI).

Asignación de pines del módulo de la tarjeta MicroSD – SPI

El módulo de la tarjeta microSD se comunica mediante el protocolo de comunicación SPI. Puede conectarlo al ESP32 usando los pines SPI predeterminados.

Módulo de tarjeta microSDESP32
VCC Vin (5v)
CSGPIO5
MOSIGPIO23
CLKGPIO18
MISOGPIO19
TIERRATIERRA

Piezas necesarias

Para este post, necesitamos las siguientes partes:

  • Placa de desarrollo ESP32 o Arduino
  • Módulo de tarjeta MicroSD
  • Tarjeta micro SD
  • Cables de puente
  • Tablero de circuitos

Para conectar el módulo de la tarjeta microSD a la placa ESP32, puede seguir el siguiente diagrama esquemático (para los pines SPI ESP32 predeterminados):

Diagrama de cableado del módulo de tarjeta microSD ESP32

Preparación de la tarjeta microSD

Antes de continuar con el tutorial, asegúrese de formatear su tarjeta microSD como FAT32 . Siga las siguientes instrucciones para formatear su tarjeta microSD o use una herramienta de software como SD Card Formater (compatible con Windows y Mac OS).

1.  Inserte la tarjeta microSD en su computadora. Vaya a  Mi PC  y haga clic derecho en la tarjeta SD. Seleccione  Formato  como se muestra en la siguiente figura.

Tarjeta MicroSD Módulo formatear tarjeta sd

2.  Aparece una nueva ventana. Seleccione  FAT32 , presione  Iniciar  para iniciar el proceso de formateo y siga las instrucciones en pantalla.

Tarjeta MicroSD Módulo formatear tarjeta sd

ESP32 Manejo de archivos con un módulo de tarjeta MicroSD

Hay dos bibliotecas diferentes para ESP32 (incluidas en el núcleo de Arduino para ESP32): la biblioteca SD y la biblioteca SDD_MMC.h .

Si usa la biblioteca SD, está usando el controlador SPI. Si usa la biblioteca SDD_MMC, está usando el controlador ESP32 SD/SDIO/MMC. Puede obtener más información sobre el controlador ESP32 SD/SDIO/MMC .

ESP32 Manejar archivos en tarjeta microSD Ejemplo Leer y escribir

Hay varios ejemplos en Arduino IDE que muestran cómo manejar archivos en la tarjeta microSD usando el ESP32. En el IDE de Arduino, vaya a Archivo > Ejemplos > SD(esp32) > SD_Test , o copie el siguiente código.

/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-microsd-card-arduino/
This sketch can be found at: Examples > SD(esp32) > SD_Test
*/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
const int chipSelect = SS;//10;
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.println("LISTDIR.....");
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char * path){
Serial.println("CREATEDIR.....");
Serial.printf("Creating Dir: %s\n", path);
if(fs.mkdir(path)){
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char * path){
Serial.println("REMOVEDIR.....");
Serial.printf("Removing Dir: %s\n", path);
if(fs.rmdir(path)){
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char * path){
Serial.println("READFILE.....");
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\n", path);
Serial.println("WRITEFILE.....");
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void appendFile(fs::FS &fs, const char * path, const char * message){
Serial.println("APPENDFILE.....");
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)){
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2){
Serial.println("RENAMEFILE.....");
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(fs::FS &fs, const char * path){
Serial.println("DELETEFILE.....");
Serial.printf("Deleting file: %s\n", path);
if(fs.remove(path)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char * path){
Serial.println("TESTFILEIO.....");
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
void setup(){
Serial.begin(115200);
delay(2000);
if(!SD.begin(SS)){
// if(!SD.begin(5)){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
delay(2000);
Serial.println("TIPO DE TARJETA...");
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC"); //ESTA
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
delay(2000);
Serial.println("TAMAÑO TARJETA...");
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize); //1910MB
listDir(SD, "/", 0);
delay(2000);
createDir(SD, "/mydir");
delay(2000);
listDir(SD, "/", 0);
delay(2000);
removeDir(SD, "/mydir");
delay(2000);
listDir(SD, "/", 2);
delay(2000);
writeFile(SD, "/hello.txt", "Hello ");
delay(2000);
appendFile(SD, "/hello.txt", "World!\n");
delay(2000);
readFile(SD, "/hello.txt");
delay(2000);
deleteFile(SD, "/foo.txt");
delay(2000);
renameFile(SD, "/hello.txt", "/foo.txt");
delay(2000);
readFile(SD, "/foo.txt");
delay(2000);
testFileIO(SD, "/test.txt");
delay(2000);
delay(2000);
Serial.println("ESPACIO LIBRE TARJETA..");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
void loop(){
//nada que hacer
delay(20000);
Serial.printf("OTRO BUCLE MAS");
}

Este ejemplo muestra cómo realizar casi cualquier tarea que pueda necesitar con la tarjeta microSD:

  • Listar un directorio 
  • Crear un directorio 
  • Eliminar un directorio 
  • Leer el contenido de un archivo 
  • Escribir contenido en un archivo
  • Agregar contenido al archivo 
  • Cambiar el nombre de un archivo 
  • Eliminar un archivo 
  • Inicializar tarjeta microSD 
  • Obtenga el tipo de tarjeta microSD 
  • Obtenga el tamaño de la tarjeta microSD 

Alternativamente, puede usar los ejemplos de SD_MMC ; estos son similares a los ejemplos de SD, pero usan el controlador SDMMC. Para el controlador SDMMC, necesita un módulo de tarjeta microSD compatible. El módulo que estamos usando en este post no es compatible con SDMMC.

Cómo funciona el código

Primero, debe incluir las siguientes bibliotecas:FS.hpara manejar archivos,SD.hpara interactuar con la tarjeta microSD ySPI.hpara utilizar el protocolo de comunicación SPI.

#include "FS.h"
#include "SD.h"
#include "SPI.h"

El ejemplo proporciona varias funciones para manejar archivos en la tarjeta microSD.

Listar un directorio

loslistDir()función enumera los directorios en la tarjeta SD. Esta función acepta como argumentos el sistema de archivos (Dakota del Sur), el nombre del directorio principal y los niveles para entrar en el directorio.

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if(!root){
    Serial.println("Failed to open directory");
    return;
  }
  if(!root.isDirectory()){
    Serial.println("Not a directory");
    return;
  }

  File file = root.openNextFile();
  while(file){
    if(file.isDirectory()){
      Serial.print("  DIR : ");
      Serial.println(file.name());
      if(levels){
        listDir(fs, file.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
     file = root.openNextFile();
  }
}

Aquí hay un ejemplo de cómo llamar a esta función. los/corresponde al directorio raíz de la tarjeta microSD.

listDir(SD, "/", 0);

Crear un Directorio

loscrearDir()La función crea un nuevo directorio. Pasar como argumento elDakota del Sursistema de archivos y la ruta del nombre del directorio.

void createDir(fs::FS &fs, const char * path){
  Serial.printf("Creating Dir: %s\n", path);
  if(fs.mkdir(path)){
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed");
  }
}

Por ejemplo, el siguiente comando crea un nuevo directorio en la raíz llamadomidir.

createDir(SD, "/mydir");

Eliminar un directorio

Para eliminar un directorio de la tarjeta microSD, use eleliminarDir()y pase como argumento el sistema de archivos SD y la ruta del nombre del directorio.

void removeDir(fs::FS &fs, const char * path){
  Serial.printf("Removing Dir: %s\n", path);
  if(fs.rmdir(path)){
    Serial.println("Dir removed");
  } else {
    Serial.println("rmdir failed");
  }
}

Aquí hay un ejemplo:

removeDir(SD, "/mydir");

Leer contenido del archivo

losleerArchivo()La función lee el contenido de un archivo e imprime el contenido en el monitor serie. Al igual que con las funciones anteriores, pase como argumento elDakota del Sursistema de archivos y la ruta del archivo.

void readFile(fs::FS &fs, const char * path){
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }

  Serial.print("Read from file: ");
  while(file.available()){
    Serial.write(file.read());
  }
  file.close();
}

Por ejemplo, la siguiente línea lee el contenido delhola.txtexpediente.

readFile(SD, "/hello.txt")

Escribir contenido en un archivo

Para escribir contenido en un archivo, puede utilizar elescribirArchivo()función. Pasar como argumento, elDakota del Sursistema de archivos, la ruta del archivo y el mensaje

void writeFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

La siguiente línea escribeHolaen elhola.txtexpediente.

writeFile(SD, "/hello.txt", "Hello ");

Agregar contenido a un archivo

De manera similar, puede agregar contenido a un archivo (sin sobrescribir el contenido anterior) usando elagregar archivo ()función.

void appendFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Appending to file: %s\n", path);

  File file = fs.open(path, FILE_APPEND);
  if(!file){
    Serial.println("Failed to open file for appending");
    return;
  }
  if(file.print(message)){
    Serial.println("Message appended");
  } else {
    Serial.println("Append failed");
  }
  file.close();
}

La siguiente línea agrega el mensaje¡Mundo!\nen elhola.txtexpediente. los\nortesignifica que la próxima vez que escriba algo en el archivo, se escribirá en una nueva línea.

appendFile(SD, "/hello.txt", "World!\n");

Cambiar el nombre de un archivo

Puede cambiar el nombre de un archivo usando elrenombrar archivo()función. Pase como argumentos el sistema de archivos SD, el nombre de archivo original y el nuevo nombre de archivo.

void renameFile(fs::FS &fs, const char * path1, const char * path2){
  Serial.printf("Renaming file %s to %s\n", path1, path2);
  if (fs.rename(path1, path2)) {
    Serial.println("File renamed");
  } else {
    Serial.println("Rename failed");
  }
}

La siguiente línea cambia el nombre delhola.txtarchivo afoo.txt.

renameFile(SD, "/hello.txt", "/foo.txt");

Eliminar un archivo

Utilizar elborrar archivo()Función para eliminar un archivo. Pasar como argumento elDakota del Sursistema de archivos y la ruta del archivo que desea eliminar.

void deleteFile(fs::FS &fs, const char * path){
  Serial.printf("Deleting file: %s\n", path);
  if(fs.remove(path)){
    Serial.println("File deleted");
  } else {
    Serial.println("Delete failed");
  }
}

La siguiente línea elimina elfoo.txtarchivo de la tarjeta microSD.

deleteFile(SD, "/foo.txt");

Probar un archivo

lostestFileIO()funciones muestra cuánto tiempo lleva leer el contenido de un archivo.

void testFileIO(fs::FS &fs, const char * path){
  File file = fs.open(path);
  static uint8_t buf[512];
  size_t len = 0;
  uint32_t start = millis();
  uint32_t end = start;
  if(file){
    len = file.size();
    size_t flen = len;
    start = millis();
    while(len){
      size_t toRead = len;
      if(toRead > 512){
        toRead = 512;
      }
      file.read(buf, toRead);
      len -= toRead;
    }
    end = millis() - start;
    Serial.printf("%u bytes read for %u ms\n", flen, end);
    file.close();
  } 
  else {
    Serial.println("Failed to open file for reading");
  }

  file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file for writing");
    return;
  }

  size_t i;
  start = millis();
  for(i=0; i<2048; i++){
    file.write(buf, 512);
  }
  end = millis() - start;
  Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
  file.close();
}

La siguiente función prueba laprueba.txtexpediente.

testFileIO(SD, "/test.txt");

Inicializar la tarjeta microSD

En elconfiguración(), las siguientes líneas inicializan la tarjeta microSD conSD.begin().

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");
  return;
}

Si no pasa ningún argumento a laempezar()función, intentará inicializar la comunicación SPI con la tarjeta microSD en el pin predeterminado de selección de chip (CS). Si desea utilizar otro pin CS, puede pasarlo como argumento alempezar()función. Por ejemplo, si desea usar GPIO 17 como un pin CS, debe usar las siguientes líneas de código:

Serial.begin(115200);
if(!SD.begin(17)){
  Serial.println("Card Mount Failed");
  return;
}
uint8_t cardType = SD.cardType();

Si desea utilizar pines SPI personalizados con la tarjeta microSD, vaya a esta sección .

Obtener tipo de tarjeta microSD

Las siguientes líneas imprimen el tipo de tarjeta microSD en el monitor serie.

Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
  Serial.println("MMC");
} else if(cardType == CARD_SD){
  Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
  Serial.println("SDHC");
} else {
  Serial.println("UNKNOWN");
}

Obtener tamaño de tarjeta microSD

Puede obtener el tamaño de la tarjeta microSD llamando altamaño de tarjeta()método:

uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);

Prueba de las funciones de la tarjeta MicroSD

Las siguientes líneas llaman a las funciones que hemos visto anteriormente.

listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));

Demostración

Sube el boceto anterior a tu placa ESP32. Después de eso, abra el monitor en serie y presione el botón RST integrado ESP32. Si la inicialización tiene éxito, obtendrá mensajes similares en el monitor serie.

Características de prueba del módulo de tarjeta MicroSD de la placa ESP32: lectura, escritura, eliminación
Características de prueba del módulo de tarjeta MicroSD de la placa ESP32: lectura, escritura, eliminación

Use pines SPI personalizados con la tarjeta MicroSD

losSD.h y SD_MMC.h son bibliotecas que utilizan los pines VSPI SPI (23, 19, 18, 5) de forma predeterminada. Puede configurar otros pines como pines SPI. El ESP32 presenta dos interfaces SPI: HSPI y VSPI en los siguientes pines:

SPIMOSIMISOCLKCS
VSPIGPIO23GPIO19GPIO18GPIO5
HSPIGPIO13GPIO12GPIO14GPIO15

Para usar otros pines SPI, puede proceder de la siguiente manera:

Al comienzo de su código, declare los pines que desea usar, por ejemplo:

#define SCK  18
#define MISO  19
#define MOSI  23
#define CS  5

En elconfiguración(), cree una nueva clase SPI en HSPI o VSPI. Estamos usando VSPI. Ambos funcionarán bien.

SPIClass spi = SPIClass(VSPI);

Inicialice el protocolo de comunicación SPI en los pines definidos anteriormente:

spi.begin(SCK, MISO, MOSI, CS);

Finalmente, inicialice la tarjeta microSD con elempezar()método. Pase como argumento el pin CS, la instancia SPI que desea usar y la frecuencia del bus.

if (!SD.begin(CS,spi,80000000)) {
  Serial.println("Card Mount Failed");
  return;
}

Aquí está el código de muestra modificado para usar pines SPI personalizados:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-microsd-card-arduino/
  
  This sketch was mofidied from: Examples > SD(esp32) > SD_Test
*/

#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCK  17
#define MISO  19
#define MOSI  23
#define CS  5

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if(!root){
    Serial.println("Failed to open directory");
    return;
  }
  if(!root.isDirectory()){
    Serial.println("Not a directory");
    return;
  }

  File file = root.openNextFile();
  while(file){
    if(file.isDirectory()){
      Serial.print("  DIR : ");
      Serial.println(file.name());
      if(levels){
        listDir(fs, file.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

void createDir(fs::FS &fs, const char * path){
  Serial.printf("Creating Dir: %s\n", path);
  if(fs.mkdir(path)){
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed");
  }
}

void removeDir(fs::FS &fs, const char * path){
  Serial.printf("Removing Dir: %s\n", path);
  if(fs.rmdir(path)){
    Serial.println("Dir removed");
  } else {
    Serial.println("rmdir failed");
  }
}

void readFile(fs::FS &fs, const char * path){
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }

  Serial.print("Read from file: ");
  while(file.available()){
    Serial.write(file.read());
  }
  file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Appending to file: %s\n", path);

  File file = fs.open(path, FILE_APPEND);
  if(!file){
    Serial.println("Failed to open file for appending");
    return;
  }
  if(file.print(message)){
    Serial.println("Message appended");
  } else {
    Serial.println("Append failed");
  }
  file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
  Serial.printf("Renaming file %s to %s\n", path1, path2);
  if (fs.rename(path1, path2)) {
    Serial.println("File renamed");
  } else {
    Serial.println("Rename failed");
  }
}

void deleteFile(fs::FS &fs, const char * path){
  Serial.printf("Deleting file: %s\n", path);
  if(fs.remove(path)){
    Serial.println("File deleted");
  } else {
    Serial.println("Delete failed");
  }
}

void testFileIO(fs::FS &fs, const char * path){
  File file = fs.open(path);
  static uint8_t buf[512];
  size_t len = 0;
  uint32_t start = millis();
  uint32_t end = start;
  if(file){
    len = file.size();
    size_t flen = len;
    start = millis();
    while(len){
      size_t toRead = len;
      if(toRead > 512){
        toRead = 512;
      }
      file.read(buf, toRead);
      len -= toRead;
    }
    end = millis() - start;
    Serial.printf("%u bytes read for %u ms\n", flen, end);
    file.close();
  } else {
    Serial.println("Failed to open file for reading");
  }


  file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file for writing");
    return;
  }

  size_t i;
  start = millis();
  for(i=0; i<2048; i++){
    file.write(buf, 512);
  }
  end = millis() - start;
  Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
  file.close();
}

void setup(){
  Serial.begin(115200);
  SPIClass spi = SPIClass(VSPI);
  spi.begin(SCK, MISO, MOSI, CS);

  if (!SD.begin(CS,spi,80000000)) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if(cardType == CARD_NONE){
    Serial.println("No SD card attached");
    return;
  }

  Serial.print("SD Card Type: ");
  if(cardType == CARD_MMC){
    Serial.println("MMC");
  } else if(cardType == CARD_SD){
    Serial.println("SDSC");
  } else if(cardType == CARD_SDHC){
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }

  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);

  listDir(SD, "/", 0);
  createDir(SD, "/mydir");
  listDir(SD, "/", 0);
  removeDir(SD, "/mydir");
  listDir(SD, "/", 2);
  writeFile(SD, "/hello.txt", "Hello ");
  appendFile(SD, "/hello.txt", "World!\n");
  readFile(SD, "/hello.txt");
  deleteFile(SD, "/foo.txt");
  renameFile(SD, "/hello.txt", "/foo.txt");
  readFile(SD, "/foo.txt");
  testFileIO(SD, "/test.txt");
  Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void loop(){

}

🏁 Conclusión: Expandiendo los límites de tu ESP32

Integrar una tarjeta MicroSD no solo añade gigabytes de almacenamiento a tu microcontrolador, sino que abre la puerta a proyectos profesionales y escalables. Hemos visto desde la conexión básica de pines hasta la implementación de funciones complejas para la gestión de directorios y archivos.

Recuerda que la clave del éxito con estos módulos reside en una alimentación estable y un formateo correcto de la tarjeta (FAT32). Si tu ESP32 tiene problemas para montar el módulo, revisa siempre la calidad de tus cables de conexión y asegúrate de que el pin Chip Select (CS) coincida exactamente con lo declarado en tu código.

🛠️ Sigue optimizando tu hardware: Si te interesa sacar el máximo provecho a tus equipos, no te pierdas nuestra guía sobre cómo optimizar un portátil HP i3 para AutoCAD o nuestro tutorial avanzado para reparar y flashear Android TV Box bloqueados. En SoloElectronicos nos enfocamos en dar una segunda vida a tu tecnología.