ESP32 - modul karty MicroSD

21.12.2022 Arduino #esp32 #microsd

Základní zapojení a programování ESP32 a modulu paměťové karty MicroSD.


Modul karty MicroSD

Existují různé moduly microSD kompatibilní s ESP32. Uvedený na obrázku využívá komunikační rozhraní SPI. Rozdíl v různých čtecích modulů bývá například v napájení. Některé moduly jsou napájeny 3.3V nebo 5V. Vzhledem k tomu, že MCU ESP32 poskytuje napětí 3.3V je výhodné používat i požadovaný modul MicroSD. Při požití modulu s napájením 5V se musí zajistit externí napájení modulu.

Piny modulu MicroSD karty – SPI

Modul microSD karty komunikuje pomocí komunikačního protokolu SPI. Můžete jej připojit k ESP32 pomocí výchozích pinů SPI.

Modul karty microSD ESP32
3V3 3,3 V
CS GPIO 5
MOSI GPIO 23
CLK GPIO 18
MISO GPIO 19
GND GND

Zapojení ESP32 a Micro SD

Chcete-li připojit modul karty microSD k desce ESP32, můžete postupovat podle následujícího schématu (pro výchozí piny ESP32 SPI):

sd-esp32-laskakit 

Příprava karty microSD

Než budete pokračovat, ujistěte se, že jste naformátovali kartu microSD jako FAT32.

ESP32 Manipulace se soubory pomocí modulu karty MicroSD

Existují dvě různé knihovny pro ESP32 (zahrnuté v jádru Arduino pro ESP32): knihovna SD a knihovna SDD_MMC.h.

Pokud používáte knihovnu SD, používáte řadič SPI. Pokud používáte knihovnu SDD_MMC, používáte řadič ESP32 SD/SDIO/MMC. Můžete se dozvědět více o ovladači ESP32 SD/SDIO/MMC.

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

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

  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(){

}

Tento příklad ukazuje, jak provést téměř jakýkoli úkol, který můžete potřebovat s kartou microSD:

Případně můžete použít příklady SD_MMC – ty jsou podobné příkladům SD, ale použijte ovladač SDMMC. Pro ovladač SDMMC potřebujete kompatibilní modul microSD karty.

Jak kód funguje

Nejprve musíme importovat následující knihovny: FS.h, která pracuje se soubory, SD.h pro rozhraní s microSD kartou a SPI.h pro používání komunikačního protokolu SPI.

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

Vypsání adresářů

funkce listDir() zobrazí seznam adresářů na SD kartě. Tato funkce přijímá jako argumenty souborový systém (SD), název hlavního adresáře a úrovně, které se mají do adresáře přesunout.

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();
  }
}

Zde je příklad, jak tuto funkci volat. The/odpovídá kořenovému adresáři microSD karty.

listDir(SD, "/", 0);

Vytvoření adresáře

Funkce createDir()vytvoří nový adresář. Jako argument je SD souborový systém a cesta k názvu adresáře.

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

Například následující příkaz vytvoří nový adresář v kořenovém adresáři s názvem mydir.

createDir(SD, "/mydir");

Odebrat adresář

Chcete-li odstranit adresář z karty microSD, použijte funkci removeDir() a předejte jako argument souborový systém SD a cestu k názvu adresáře.

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

Zde je příklad:

removeDir(SD, "/mydir");

Čtení obsahu souboru

Funkce readFile() přečte obsah souboru a vytiskne obsah v aplikaci Serial Monitor. Stejně jako u předchozích funkcí je argumentem SD souborový systém a cesta k souboru.

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();
}

Například následující řádek přečte obsah souboru hello.txt.

readFile(SD, "/hello.txt")

Zápis obsahu do souboru

Chcete-li zapsat obsah do souboru, můžete použít funkci writeFile(). Předejte jako argument SD souborový systém, cestu k souboru a zprávu.

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();
}

Příkladem je následující řádek do souboru hello.txt.

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

Připojit obsah k souboru

Podobně můžete přidat obsah k souboru (aniž byste přepsali předchozí obsah) pomocí funkce appendFile().

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();
}

Následující řádek připojuje zprávu World!\n do souboru hello.txt. Řetězec \n znamená, že až příště něco zapíšete do souboru, zapíše se to na nový řádek.

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

Přejmenování souboru

Soubor můžete přejmenovat pomocí funkce renameFile(). Předejte jako argumenty souborový systém SD, původní název souboru a nový název souboru.

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

Následující řádek přejmenuje soubor hello.txt na soubor foo.txt.

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

Smazat soubor

Použijte funkci deleteFiile() pro smazání souboru. Předejte jako argument SD souborový systém a cestu k souboru, který chcete odstranit.

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

Následující řádek smaže soubor foo.txt z microSD karty.

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

Otestujte soubor

Funkce testFileIO() ukazuje, jak dlouho trvá načtení obsahu souboru.

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();
}

Následující funkce testuje soubor test.txt.

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

Inicializujte kartu microSD

Ve funkci setup() následující řádky inicializují kartu microSD pomocí SD.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;
}

Pokud nepředáte žádný argument funkci, pokusí se inicializovat SPI komunikaci s microSD kartou na výchozím pinu pro výběr čipu (CS). Pokud chcete použít jiný pin CS, můžete jej předat jako argument funkce SD.begin(). Pokud jste například chtěli použít GPIO 17 jako CS pin, měli byste použít následující řádky kódu:

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

Pokud chcete s kartou microSD používat vlastní piny SPI, přejděte do této části.

Získání typu karty microSD

Následující řádky vytisknou typ karty microSD na sériovém monitoru.

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

Získejte velikost karty microSD

Velikost microSD karty zjistíte zavoláním nacardSize()metoda:

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

Testování funkcí MicroSD karty

Následující řádky volají funkce, které jsou vysvětleny v předchozím textu.

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));

Použití vlastních pinů SPI s kartou MicroSD

Knihovny SD.h a SD_MMC.h standardně používají piny VSPI SPI (23, 19, 18, 5). Ostatní piny můžete nastavit jako piny SPI. ESP32 má dvě rozhraní SPI: HSPI a VSPI na následujících pinech:

SPI MOSI MISO CLK CS
VSPI GPIO 23 GPIO 19 GPIO 18 GPIO 5
HSPI GPIO 13 GPIO 12 GPIO 14 GPIO 15

Chcete-li použít další piny SPI, můžete postupovat následovně:

Na začátku kódu deklarujte piny, které chcete použít, například:

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

Vytvořte novou třídu SPI na HSPI nebo VSPI. Používáme VSPI. Obojí bude fungovat dobře.

SPIClass spi = SPIClass(VSPI);

V begin(), inicializujte komunikační protokol SPI na dříve definovaných pinech:

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

Nakonec inicializujte kartu microSD pomocí metody begin(). Předejte jako argument pin CS, instanci SPI, kterou chcete použít, a frekvenci sběrnice.

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

Ukázka celého programového kódu s vlastní definicí:

#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 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);
  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(){

}

Literatura

ESP32: Guide for MicroSD Card Module using Arduino IDE. Random Nerd Tutorials [online]. 2022 [cit. 2022-12-21]. Dostupné z: https://randomnerdtutorials.com/esp32-microsd-card-arduino/#interfacemicrosdcard