SD card with ESP32-C6 and Arduino

Posted by

Today we will learn how to use an SD memory card with ESP32-C6. We will use my development board (more information about it here), based on the SeeedStudio Xiao ESP32-C6 model.

The purpose of the SD (or micro SD) card in the ESP32 is usually to store text or csv (comma-separated) files. These files are fed with data from sensors or calculations performed by the microcontroller.

The SD library for Arduino was written for this purpose. A basic example of how to use the SD library can be found here, on the official Arduino website.

micro SD card with ESP32-C6
ESP32-C6 with micro SD card module

We will use the example contained in the Arduino IDE itself, in “File > Examples > 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() {}

Note that we “forced” and defined the MISO, MOSI, SCK and CS pins to the exact SPI pins of the Xiao ESP32-C6:

#define REASSIGN_PINS
int sck = D8;
int miso = D9;
int mosi = D10;
int cs = D7;

The schematic diagram is seen below, note that for the module I am using an external 5V power supply is required. This power supply does not power the ESP32C6 dev board, only the micro SD module.

ESP32-C6 with SD card module schematics
ESP32-C6 with SD card module schematics

After uploading the above code to the ESP32-C6 dev board, observe the serial monitor of the Arduino IDE. The output of the above sketch in the serial monitor is as below. Note that it is quite a lot of information, very complete.

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

A file is created, “hello.txt” which is later renamed to “foo.txt”. Two of the main pieces of information displayed are the available space and the used space.

An example saving analog LDR data to the SD card

Below is the code for an example of saving data from the onboard LDR sensor on the ESP32-C6 dev board. This sensor is on pin A0 of the board. The data generated is in integer format between 0 and 4095 (12 bits).

The code is based on the one presented above, just adding a few lines to the loop() function:

void loop() {

  String datatosave= String(analogRead(A0)) + String(",");
  Serial.println(datatosave);
  appendFile(SD, "/ldrtocsv.csv", datatosave.c_str());
  delay(5000);
}

We create a csv file and feed it every five (5) seconds. Running the program on the ESP32-C6 dev board, on the Arduino IDE serial monitor it looks like this:

Arduino IDE serial monitor with SD card data
Arduino IDE serial monitor with SD card data

The data saved in the .csv file were:

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,

Final words

The ESP32-C6 dev board is based on the Seeedstudio Xiao chip. This board also supports the SPI and SDIO protocols, allowing it to interface with SD memory cards (in the case of this article, micro SD).

In addition to SPI, we have already seen here on the blog the use of the i2c protocol for an OLED display. Take advantage and check out our other articles.

Leave a Reply

Your email address will not be published. Required fields are marked *