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):
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:
- Vypsat adresář;
- Vytvořit adresář;
- Odebrat adresář;
- Číst obsah souboru;
- Zápis obsahu do souboru;
- Připojit obsah k souboru;
- Přejmenovat soubor;
- Smazat soubor;
- Inicializace karty microSD;
- Získání typu karty microSD;
- Získání velikosti karty 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