Hoje vamos aprender a utilizar um cartão de memória SD com ESP32-C6. Utilizaremos minha placa de desenvolvimento (mais info sobre ela aqui), baseada no modelo da SeeedStudio Xiao ESP32-C6.
O objetivo do cartão SD (ou micro SD) no ESP32 geralmente é armazenar arquivos de texto ou csv (separado por vírgulas). Estes arquivos são alimentados com dados de sensores ou cálculos executados pelo microcontrolador.
Para este fim foi escrita a biblioteca SD para Arduino. Exemplo básico da utilização da biblioteca SD pode ser encontrado aqui, no site oficial do Arduino.
Utilizaremos o exemplo contido na própria IDE do Arduino, em “Arquivo > Exemplos > SD > SD_test”.
#include "FS.h"
#include "SD.h"
#include "SPI.h"
//Uncomment and set up if you want to use custom pins for the SPI communication
#define REASSIGN_PINS
int sck = D8;
int miso = D9;
int mosi = D10;
int cs = D7;
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.path(), 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 %lu 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 %lu ms\n", 2048 * 512, end);
file.close();
}
void setup() {
SD.begin(D7);
Serial.begin(115200);
while (!Serial) {
delay(10);
}
#ifdef REASSIGN_PINS
SPI.begin(sck, miso, mosi, cs);
if (!SD.begin(cs)) {
#else
if (!SD.begin()) {
#endif
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() {}
Veja que “forçamos” e definição dos pinos de MISO, MOSI, SCK e CS para os pinos SPI exatos da Xiao ESP32-C6:
#define REASSIGN_PINS
int sck = D8;
int miso = D9;
int mosi = D10;
int cs = D7;
O diagrama esquemático é visto abaixo, note que para o módulo que estou utilizando é necessária uma fonte 5V externa. Essa fonte não alimenta a placa dev ESP32C6, apenas o módulo micro SD.
Após fazer upload do código acima para a placa dev ESP32-C6, observe o monitor serial da IDE do Arduino. A saída do sketch acima no monitor serial é conforme abaixo. Note que é bastante informação, bem completo.
Listing directory: /
DIR : System Volume Information
FILE: test.txt SIZE: 1048576
FILE: foo.txt SIZE: 13
DIR : mydir
Removing Dir: /mydir
Dir removed
Listing directory: /
DIR : System Volume Information
Listing directory: /System Volume Information
FILE: WPSettings.dat SIZE: 12
FILE: IndexerVolumeGuid SIZE: 76
FILE: test.txt SIZE: 1048576
FILE: foo.txt SIZE: 13
Writing file: /hello.txt
File written
Appending to file: /hello.txt
Message appended
Reading file: /hello.txt
Read from file: Hello World!
Deleting file: /foo.txt
File deleted
Renaming file /hello.txt to /foo.txt
File renamed
Reading file: /foo.txt
Read from file: Hello World!
1048576 bytes read for 2504 ms
1048576 bytes written for 3280 ms
Total space: 1877MB
Used space: 1MB
É criado um arquivo, “hello.txt” que é posteriormente renomeado para “foo.txt”. Duas das principais informações mostradas são o espaço disponível, além do espaço utilizado.
Um exemplo salvando dados de um LDR
Abaixo o código de um exemplo de salvamento de dados do sensor LDR onboard, da placa dev ESP32-C6. Este sensor está no pino A0 da placa. Os dados gerados estão em formato inteiro entre 0 e 4095 (12 bit).
O código usa como base aquele já apresentado acima, apenas adicionando algumas linhas na função loop():
void loop() {
String datatosave= String(analogRead(A0)) + String(",");
Serial.println(datatosave);
appendFile(SD, "/ldrtocsv.csv", datatosave.c_str());
delay(5000);
}
Criamos um arquivo csv e o alimentamos a cada cinco (5) segundos. Rodando o programa na placa dev ESP32-C6, no monitor serial da IDE do Arduino fica assim:
Já os dados salvos no arquivo .csv foram:
2929,2928,2929,2928,2033,1905,2928,2929,2928,2928,2929,2929,2929,2928,2928,2928,2928,2928,2928,2929,2929,2928,2929,2929,2929,2929,2928,2929,2929,2945,2928,2928,2928,2928,2929,2929,2944,2928,2929,2928,2929,2929,2928,2929,2928,2928,2928,2928,2929,2928,2928,2929,2928,2928,2929,2928,2928,2929,
Palavras finais
A placa dev ESP32-C6 é baseada no chip da linha Xiao da Seeedstudio. Esta placa também tem suporte aos protocolos SPI e SDIO, possibilitando interfacea-la com cartões de memória tipo SD (no caso do artigo, micro SD).
Além de SPI nós já vimos aqui no blog a utilização do protocolo i2c para um display OLED. Aproveite e conheça nossos outros artigos.
Deixe um comentário