Guía para usar una microSD con ESP32

¿Te has encontrado alguna vez en la situación de que tu proyecto con ESP32 se queda sin espacio para almacenar datos? Ya sea que estés construyendo una estación meteorológica que necesita registrar años de lecturas, un dispositivo inteligente para el hogar que requiere un archivo de configuración extenso, o incluso un pequeño reproductor de música que debe almacenar tus melodías favoritas, la memoria integrada del ESP32 puede convertirse rápidamente en un cuello de botella. Afortunadamente, las tarjetas microSD ofrecen una solución accesible y eficiente para expandir la capacidad de almacenamiento de tus proyectos, permitiéndote seguir adelante sin complicaciones ni gastos excesivos.

En esta guía, aprenderás cómo conectar un módulo de tarjeta microSD a tu ESP32, así como a leer y escribir archivos almacenados en la tarjeta. También exploraremos cómo manejar carpetas, crear y eliminar archivos, agregar datos y verificar el tamaño y velocidad de la tarjeta. ¡Comencemos!

Índice de contenido
  1. Módulo de tarjeta microSD
    1. Pinout del módulo microSD
  2. Conexión del módulo microSD al ESP32
  3. Preparación de la tarjeta microSD
    1. Método 1
    2. Método 2
  4. Configuración del IDE de Arduino
  5. Código de ejemplo
  6. Explicación del código
    1. La función setup()
    2. Funciones personalizadas en el boceto

Módulo de tarjeta microSD

Existen varios tipos de módulos de tarjeta microSD disponibles en el mercado, pero en este tutorial, utilizaremos un módulo específico que se adapta perfectamente a nuestras necesidades. Este módulo opera a 3.3 voltios, que es el mismo nivel de voltaje utilizado tanto por la tarjeta microSD como por la placa ESP32. Esto significa que todo funciona en armonía sin necesidad de componentes adicionales para modificar los niveles de voltaje.

El módulo utiliza el protocolo de comunicación SPI para conectarse al ESP32. En este módulo, las líneas de datos SPI están conectadas a resistencias de pull-up de 10K que ayudan a garantizar una transferencia de datos confiable. Es importante tener en cuenta que la cantidad de corriente que utiliza la tarjeta microSD puede variar dependiendo de las operaciones que esté realizando:

  • Cuando la tarjeta no está en uso, generalmente consume alrededor de 500 µA.
  • Al leer datos de la tarjeta, puede utilizar entre 15 y 30 mA.
  • Durante la escritura de datos, algunos modelos de tarjetas microSD pueden llegar a consumir hasta 100 mA.

Por lo tanto, es crucial asegurarse de que tu fuente de alimentación pueda proporcionar suficiente corriente. La salida de 3.3V del ESP32 puede suministrar hasta 500mA, lo que suele ser suficiente para ambas operaciones de lectura y escritura. Sin embargo, si experimentas problemas al intentar leer o escribir datos, las cuestiones de alimentación son uno de los primeros aspectos que deberías comprobar.

Pinout del módulo microSD

El módulo de tarjeta microSD tiene seis pines, y aquí te explicamos la función de cada uno:

  • 3V3: es la entrada de alimentación. Conéctalo al pin de 3.3V del ESP32.
  • CS (Chip Select): es un pin de control que se usa para activar el módulo en el bus SPI, permitiendo que se comunique cuando sea necesario.
  • MOSI (Master Out Slave In): es el pin de entrada SPI al módulo de tarjeta microSD que recibe datos del ESP32.
  • CLK (Serial Clock): recibe pulsos de temporización del dispositivo maestro (tu ESP32) para mantener la sincronización de la transmisión de datos.
  • MISO (Master In Slave Out): es la salida SPI del módulo de tarjeta microSD que envía datos al ESP32.
  • GND: es un pin de tierra.

Conexión del módulo microSD al ESP32

Ahora vamos a conectar el módulo de tarjeta microSD a tu ESP32. Comienza conectando el pin 3V3 del módulo microSD al pin de alimentación de 3.3V del ESP32. Luego, conecta el pin GND del módulo a uno de los pines GND del ESP32.

Seguido de esto, configuraremos los pines utilizados para la comunicación SPI. Dado que las tarjetas microSD requieren una transferencia de datos rápida, funcionan mejor cuando están conectadas a los pines SPI de hardware del ESP32. Los pines SPI predeterminados en el ESP32 son: GPIO 18 (CLK), GPIO 19 (MISO), GPIO 23 (MOSI) y GPIO 5 (CS).

A continuación, se muestra una tabla de referencia rápida para las conexiones de pines:

Pin del módulo microSDPin de ESP32
3V33.3V
GNDGND
MOSIGPIO 23
MISOGPIO 19
CLKGPIO 18
CSGPIO 5

Preparación de la tarjeta microSD

Antes de utilizar tu tarjeta microSD en el proyecto, es importante asegurarte de que esté formateada correctamente con el sistema de archivos adecuado: FAT16 o FAT32. Esto ayudará al ESP32 a leer y escribir archivos sin problemas.

Si estás usando una tarjeta SD nueva, probablemente venga ya formateada con un sistema de archivos FAT. Sin embargo, este formateo de fábrica podría no ser perfecto, y podrías encontrar problemas. Si utilizas una tarjeta más antigua que ha sido usada antes, definitivamente necesitarás volver a formatearla. En cualquier caso, es recomendable formatear la tarjeta antes de usarla en tu proyecto.

Existen dos métodos para formatear tu tarjeta microSD:

Método 1

Primero, inserta tu tarjeta microSD en tu computadora. Luego, localiza la unidad de la tarjeta SD, haz clic derecho sobre ella y selecciona "Formatear" en el menú. Aparecerá una ventana; selecciona FAT32 como el sistema de archivos y haz clic en "Iniciar" para comenzar el formateo. Sigue las instrucciones en pantalla hasta que finalice.

Método 2

Para obtener mejores resultados y menos errores, se recomienda utilizar la herramienta de formateo oficial proporcionada por la Asociación SD. Esta herramienta es más confiable que el formateador básico que viene con tu computadora. Puedes descargarla desde el sitio web de la Asociación SD. Una vez instalada, ejecuta el programa, selecciona tu tarjeta SD de la lista de unidades y haz clic en el botón "Formatear". Esta herramienta especial ayuda a evitar problemas comunes causados por formateos defectuosos o incompletos, lo que te puede ahorrar muchos quebraderos de cabeza más adelante.

Configuración del IDE de Arduino

Utilizaremos el IDE de Arduino para programar el ESP32, así que asegúrate de tener instalada la extensión de ESP32 antes de continuar.

Código de ejemplo

El IDE de Arduino incluye varios ejemplos integrados que vienen con el núcleo de Arduino para el ESP32, los cuales muestran cómo trabajar con archivos en una tarjeta microSD utilizando el ESP32.

Para acceder a estos ejemplos, abre el IDE de Arduino y ve a Archivo > Ejemplos > SD. Verás dos ejemplos de bocetos. Puedes elegir cualquiera de ellos para cargarlo en tu IDE. Carguemos el SD_Test.

Este boceto de ejemplo abarca casi todas las tareas básicas que podrías querer realizar con una tarjeta microSD. Muestra cómo listar el contenido de una carpeta (también llamada directorio), crear un nuevo directorio y eliminar uno. También muestra cómo leer el contenido de un archivo, escribir nuevos datos en un archivo y agregar más contenido a un archivo existente. Además, el ejemplo incluye cómo renombrar un archivo y eliminar un archivo de la tarjeta.

Más allá del manejo de archivos, el boceto también ilustra cómo inicializar la tarjeta microSD, verificar qué tipo de tarjeta está conectada y conocer el tamaño de la tarjeta.

Prueba este boceto antes de entrar en todos los detalles.

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

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  Serial.printf("Listing directory: %sn", 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();
  }
}

// Otras funciones aquí...

Explicación del código

El código comienza incluyendo las tres bibliotecas principales necesarias: FS.h, SD.h y SPI.h. Estas bibliotecas permiten al ESP32 utilizar el protocolo SPI para comunicarse con la tarjeta microSD y realizar operaciones del sistema de archivos como leer, escribir y gestionar archivos y carpetas.

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

También hay una sección de código (actualmente comentada) que permite reasignar los pines SPI. Si no deseas utilizar los pines SPI predeterminados del ESP32, puedes descomentar esta sección y establecer tus propios números de pin personalizados para la conexión SPI.

/*
Descomentar y configurar si deseas usar pines personalizados para la comunicación SPI
#define REASSIGN_PINS
int sck = -1;
int miso = -1;
int mosi = -1;
int cs = -1;
*/

La función setup()

La función setup() es donde ocurre toda la acción. Primero, inicializamos el monitor serie para que podamos ver la salida de nuestro programa.

A continuación, intentamos inicializar la tarjeta SD. El código primero verifica si has definido REASSIGN_PINS para utilizar pines personalizados; de lo contrario, utiliza los pines SPI estándar del ESP32 para la tarjeta SD. Si la tarjeta no se inicializa correctamente, imprimirá un mensaje de error y se detendrá.

#ifdef REASSIGN_PINS
  SPI.begin(sck, miso, mosi, cs);
  if (!SD.begin(cs)) {
#else
  if (!SD.begin()) {
#endif
    Serial.println("Card Mount Failed");
    return;
}

Una vez que la tarjeta se monta correctamente, verificamos su tipo (MMC, SDSC o SDHC) e imprimimos esta información, junto con el tamaño total de la tarjeta en megabytes. Esta configuración inicial nos proporciona un buen panorama de la tarjeta con la que estamos trabajando.

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: %lluMBn", cardSize);

Después de las verificaciones iniciales, la función setup() procede a demostrar varias operaciones del sistema de archivos en una tarjeta microSD. Comenzamos listando el contenido del directorio raíz (“/“).

Luego, creamos un nuevo directorio llamado mydir y listamos nuevamente el directorio raíz para confirmar que se ha creado.

createDir(SD, "/mydir");
listDir(SD, "/", 0);

Justo después, eliminamos ese mismo directorio y listamos el contenido una vez más. Esto nos muestra cómo crear y eliminar directorios.

removeDir(SD, "/mydir");
listDir(SD, "/", 2);

El código luego avanza hacia las operaciones de archivos. Creamos un archivo llamado hello.txt y escribimos el texto Hello en él.

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

A continuación, agregamos el texto World!n al mismo archivo sin sobrescribir el contenido original. Luego leemos el archivo completo e imprimimos su contenido en el monitor serie.

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

Para mostrar cómo manejar archivos, el código intenta eliminar un archivo llamado foo.txt que no existe, demostrando un caso de falla.

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

A continuación, renombramos nuestro archivo hello.txt a foo.txt y leemos el archivo recién renombrado para confirmar el cambio.

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

El programa luego realiza una prueba de entrada/salida de archivos mediante testFileIO(). Esta función lee y escribe una gran cantidad de datos en un archivo llamado test.txt, y reporta cuánto tiempo toma cada operación. Esto te ayuda a comprender la velocidad de lectura y escritura de tu tarjeta SD.

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

Finalmente, el programa imprime cuánto espacio total y usado hay en la tarjeta SD.

Serial.printf("Total space: %lluMBn", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMBn", SD.usedBytes() / (1024 * 1024));

Funciones personalizadas en el boceto

Ahora, revisemos cada una de las funciones personalizadas utilizadas en el boceto:

Listado de un directorio

La función listDir() se utiliza para listar todos los archivos y carpetas en un directorio específico de la tarjeta SD. Se le proporciona el sistema de archivos SD, la ruta a la carpeta que deseas examinar y un número que indica qué tan profundo debe buscar en las subcarpetas. La función abre la carpeta, verifica si es válida y luego recorre cada archivo o carpeta dentro de ella, imprimiendo sus nombres y tamaños. Si encuentra otra carpeta y el nivel es mayor que 0, profundiza en esa carpeta también.

void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
  Serial.printf("Listing directory: %sn", 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();
  }
}

Por ejemplo, para listar los contenidos del directorio raíz, lo llamarías así:

listDir(SD, "/", 0);

Creación de un directorio

La función createDir() crea una nueva carpeta en la tarjeta SD. Se le proporciona el sistema de archivos SD y el nombre (o ruta) de la carpeta. Intenta crear la carpeta y te dice si tuvo éxito o no.

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

Por ejemplo, para crear una carpeta llamada mydir, la llamarías así:

createDir(SD, "/mydir");

Eliminación de un directorio

La función removeDir() elimina una carpeta de la tarjeta SD. Al igual que la función createDir(), se le proporciona el sistema de archivos SD y la ruta de la carpeta. Intentará eliminarla e imprimirá un mensaje para informarte si fue exitoso.

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

Por ejemplo, para eliminar mydir, la llamarías así:

removeDir(SD, "/mydir");

Lectura de un archivo

La función readFile() abre un archivo, lo lee carácter por carácter y lo imprime en el Monitor Serie. Al igual que las funciones anteriores, se le proporciona el sistema de archivos SD y la ruta del archivo.

void readFile(fs::FS &fs, const char * path) {
  Serial.printf("Reading file: %sn", 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 del archivo hello.txt.

readFile(SD, "/hello.txt");

Escritura en un archivo

La función writeFile() abre un archivo en modo escritura y escribe un mensaje proporcionado, sobrescribiendo cualquier contenido existente. Necesitas darle el sistema de archivos SD, la ruta del archivo y el mensaje que deseas escribir.

void writeFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Writing file: %sn", 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 escribe "Hello" en el archivo hello.txt.

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

Agregar contenido a un archivo

La función appendFile() también escribe un mensaje en un archivo, pero en lugar de sobrescribir el contenido, agrega el mensaje al final. Esto se denomina agregar. Es útil cuando quieres seguir añadiendo nuevos datos sin borrar lo que ya estaba allí.

void appendFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Appending to file: %sn", 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 World!n en el archivo hello.txt. El n significa que la próxima vez que escribas algo en el archivo, irá en una nueva línea.

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

Renombrando un archivo

La función renameFile() cambia el nombre de un archivo existente. Se le proporciona el sistema de archivos SD, el nombre actual del archivo y el nuevo nombre que deseas. Si el cambio de nombre es exitoso, imprimirá un mensaje que así lo indica.

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

La siguiente línea renombra el archivo hello.txt a foo.txt.

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

Eliminando un archivo

La función deleteFile() elimina un archivo de la tarjeta SD. Se le proporciona el sistema de archivos SD y la ruta del archivo que deseas eliminar.

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

La siguiente línea elimina el archivo foo.txt de la tarjeta microSD.

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

Medición de la velocidad de lectura y escritura

La función testFileIO() es un ejemplo más avanzado. Prueba qué tan rápido puede tu tarjeta microSD leer y escribir datos. Primero, abre un archivo, lee una gran cantidad de bytes en fragmentos para medir la velocidad de lectura y luego ciérralo. Luego, abre el mismo archivo para escritura y escribe una gran cantidad de datos en fragmentos para medir la velocidad de escritura. Esta función es útil si deseas entender el rendimiento de tu tarjeta SD para operaciones con grandes volúmenes de datos.

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 msn", 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 msn", 2048 * 512, end);
  file.close();
}

Carlos Julián

Carlos Julián es el fundador de Ingtelecto, es Ingeniero Mecatrónico, Profesor y Programador, cuenta con una Maestria en Ciencias de la Educación, creador de contenido activo a través de TikTok @carlosjulian_mx

Estos temas te pueden interesar

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Tu puntuación: Útil

Subir