Cartão de memória SD com ESP32-C6

Posted by

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.

cartão SD com ESP32-C6
Cartão de memória SD com ESP32-C6

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.

Esquema de ligação da placa ESP32-C6 com cartão 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:

Monitor serial dos valores salvos no micro SD com ESP32-C6
Monitor serial dos valores salvos no micro SD com ESP32-C6

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

O seu endereço de email não será publicado. Campos obrigatórios marcados com *