Características

La tarjeta microSD es una de las partes esenciales para el funcionamiento del kode dot. En ella se guardan las aplicaciones, los datos de configuración y los archivos de usuario. Además, para los proyectos y aplicaciones que se desarrollen en kodeOS, es la forma más sencilla de guardar los datos que se generen.

Esquema de conexión

La microSD se conecta al ESP32-S3 mediante SDIO, en modo 1-bit. Así, se usa el periférico SD/MMC Host que tiene el ESP32-S3 dedicado para leer y escribir en la microSD. La conexión entre la microSD y el ESP32-S3 es la siguiente:
MicroSDESP32-S3
CommandGPIO5
ClockGPIO6
DataGPIO7
Card DetectedEXP14
El pin de Card Detected está conectado al EXP14 del expansor de pines. Ve a Expansor de pines para más información.

Librerías recomendadas

Arduino

ESP-IDF

No requiere librerías adicionales.

Ejemplo de código

Este código hará un test de la microSD en este orden:
  1. Listar directorios
  2. Crear un directorio
  3. Listar directorios
  4. Eliminar un directorio
  5. Listar directorios
  6. Escribir un archivo
  7. Añadir un mensaje al final de un archivo
  8. Leer un archivo
  9. Eliminar un archivo
  10. Renombrar un archivo
  11. Leer un archivo
  12. Test de rendimiento de lectura y escritura
microsd_test.ino
/**
 * Gestiona una tarjeta SD en modo SD_MMC (1-bit) en ESP32-S3.
 * Realiza operaciones de archivos (listar, crear, leer, escribir, renombrar, borrar) y mide rendimiento.
 * Usa pines personalizados y monta la tarjeta en /sdcard.
*/
/* ───────── KODE | docs.kode.diy ───────── */

#include "FS.h"
#include "SD_MMC.h"

/* Pines personalizados para SD_MMC (SD en modo 1-bit) */
int clk = 6;    /* Pin de reloj (CLK) */
int cmd = 5;    /* Pin de comando (CMD) */
int d0  = 7;    /* Pin de datos 0 (D0) */

/* Función recursiva para listar directorios */
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\n", dirname);

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

  /* Itera por archivos y subdirectorios */
  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name());
      /* Recurre en subdirectorios si levels > 0 */
      if (levels) {
        listDir(fs, file.path(), levels - 1);
      }
    } else {
      /* Es un archivo: imprime nombre y tamaño */
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

/* Crea un 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");
  }
}

/* Elimina un 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");
  }
}

/* Lee y muestra el contenido de un 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: ");
  /* Lee byte a byte y escribe por Serial */
  while (file.available()) {
    Serial.write(file.read());
  }
}

/* Escribe un mensaje en un archivo (sobrescribe) */
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;
  }
  /* Escribe el mensaje y verifica el resultado */
  if (file.print(message)) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
}

/* Añade un mensaje a un archivo */
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");
  }
}

/* Renombra un 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");
  }
}

/* Elimina un archivo */
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");
  }
}

/* Prueba de rendimiento de E/S de archivos sobre un archivo grande */
void testFileIO(fs::FS &fs, const char *path) {
  File file = fs.open(path);
  static uint8_t buf[512];  /* Búfer de 512 bytes */
  size_t len = 0;
  uint32_t start = millis();
  uint32_t elapsed;

  if (file) {
    /* Obtiene tamaño de archivo */
    len = file.size();
    size_t originalLen = len;
    start = millis();
    /* Lee el archivo por bloques */
    while (len) {
      size_t toRead = (len > sizeof(buf)) ? sizeof(buf) : len;
      file.read(buf, toRead);
      len -= toRead;
    }
    elapsed = millis() - start;
    Serial.printf("%u bytes read in %lu ms\n", originalLen, elapsed);
    file.close();
  } else {
    Serial.println("Failed to open file for reading");
  }

  /* Medición de rendimiento de escritura */
  file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  start = millis();
  /* Escribe 2048 bloques de 512 bytes (1 MiB) */
  for (size_t i = 0; i < 2048; i++) {
    file.write(buf, sizeof(buf));
  }
  elapsed = millis() - start;
  Serial.printf("%u bytes written in %lu ms\n", 2048 * sizeof(buf), elapsed);
  file.close();
}

void setup() {
  Serial.begin(115200);

  /* Asigna pines personalizados para SD_MMC en modo 1-bit */
  if (!SD_MMC.setPins(clk, cmd, d0)) {
    Serial.println("Pin change failed!");
    return;
  }

  /* Monta la tarjeta SD vía SD_MMC (path y busWidth=1 para 1-bit) */
  if (!SD_MMC.begin("/sdcard", 1)) {
    Serial.println("Card Mount Failed");
    return;
  }

  /* Revisa el tipo de tarjeta */
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD_MMC card attached");
    return;
  }
  Serial.print("SD_MMC 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");

  /* Tamaño de la tarjeta en MB */
  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);

  /* Ejemplos de operaciones con archivos y directorios */
  listDir(SD_MMC, "/", 0);
  createDir(SD_MMC, "/mydir");
  listDir(SD_MMC, "/", 0);
  removeDir(SD_MMC, "/mydir");
  listDir(SD_MMC, "/", 2);
  writeFile(SD_MMC, "/hello.txt", "Hello ");
  appendFile(SD_MMC, "/hello.txt", "World!\n");
  readFile(SD_MMC, "/hello.txt");
  deleteFile(SD_MMC, "/foo.txt");
  renameFile(SD_MMC, "/hello.txt", "/foo.txt");
  readFile(SD_MMC, "/foo.txt");
  testFileIO(SD_MMC, "/test.txt");

  /* Imprime espacio total y usado en MB */
  Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
}

void loop() {
  /* Bucle principal sin trabajo: breve retardo para ceder CPU */
  delay(10);
}

Descarga de ejemplos

Puedes probar los códigos de ejemplo mediante el IDE de Arduino o el IDE de ESP-IDF o descargar los códigos en nuestro drive: Ejemplos de microSD