SD Card - Recording high quality audio

Hi!
I'm working on a project that requires recording data with high-quality audio (44.1 kHz @ 24-bit is desirable). The hardware includes ESP32 WROOM 32E, PMOD I2S2, and a Micro SD Module.
The code was adapted from this video and the first comment https://www.youtube.com/watch?v=WsDwj6SENOE.

Obviously I'm having problems with SD latency to write data.
I think I read all the topics related, but most of them are 10 years old (most of the answers provided by fat16).

Since I'm not able to run his benchmark test code, I asume I achieved maximum data rate with this code, because data is stored in binary, buffer size is 512. .open() and .close() are called once per recording.:

// ... includes, pin definitions, etc

// Buffer size to save data to SD is 512 bytes.
#define buffSize1           256
#define buffSize2   buffSize1/2
#define max_buffSize        512
const int sampleRate = 12000; // PMOD I2S2 sample rate. long int@ 12 kHz funciona. 
     int    rxbuf[buffSize1], txbuf[buffSize1],
            l_in[buffSize2] , r_in[buffSize2],
            l_out[buffSize2], r_out[buffSize2];

// ... setups ...


void loop() { // Since the idea is to record T_ON minutes, then shut down T_OFF minutes
  record(T_ON);
  esp_deep_sleep_start();
}

void record (unsigned long record ){
  unsigned long endTime = millis() + record;
  if (!SD.begin(SD_CS)) { // Hacer funcion de error.
    while (true) {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(100);
      digitalWrite(LED_BUILTIN, LOW);
      delay(250);
    }
  }
  File myFile = SD.open("/data.bin", FILE_WRITE);
  if (!myFile) {
    Custom_Error();
  }

  while(millis() < endTime){
    size_t readsize = 0;
    // Read PMOD I2S2 AD
    err = i2s_read(I2S_NUM_0, &rxbuf[0], buffSize1 * 4, &readsize, 1000);
    if (err == ESP_OK && readsize == buffSize1 * 4) {
      int y = 0;
      for (int i = 0; i < buffSize1; i += 2) { // Split left and right channel.
      }

      for (int i = 0; i < buffSize2; i++) {    // Some DSP operations (optional).
      }

      y = 0;
      for (int i = 0; i < buffSize2; i++) {    // Generate I2S buffers.
      }
      // Write PMOD I2S2 DA
      i2s_write(I2S_NUM_0, &txbuf[0], max_buffSize, &readsize, 10);
      // Save data to SD.
      myFile.write((const uint8_t *)r_out, sizeof(r_out));
      myFile.flush();
    }
  }
  myFile.close();
}

Maybe someone can help me improve this code. But I would like to ask if is possible to save data to multiple SD Cards?
My data es 32 bits (I2S works for me with 32 bits, but not 24 - ADC capability), so I was thinking to write each byte on a different SD Card with the same SPI bus (which would require 4 chip select and 4 sd card modules).
Or, use 2 SPI busses and save 2 bytes in each SD Card (with 2 sd cards, one per SPI bus).
Have someone tried something like this before?
Having other smalled boards as slaves is a possibility (attiny, micro, dont know, the cheaper the better).

A detail, this device is battery powered and the idea is to record information for 1 or 2 weeks.

Likely, but ONLY ONE can be open at a time.

This operation is unnecessary and if removed, might make a large difference in the write time. myFile.flush();

I've found that it helps to use an audio buffer at least 4x larger (an exact multiple of 512 bytes) than the SD card buffer.

The compiler probably removes all these useless lines, but that does depend on the optimization levels set. Take them out for now.

  for (int i = 0; i < buffSize2; i++) {    // Some DSP operations (optional).
      }

Found the way to increase SPI bus clock speed.
Weird because it's supposed to be CPUfreq/integer. But works great at 18 MHz for a ESP32 32E.
I can write 1 MB/sec with a ancient 16gb SD Card (which is 10x the default config). Anyways, it's not enought for 1 audio channel at 32 bit/44.1 kHz.
Increasing working frequency might be troublesome.

/* Just run the script to test write speed.
  Adjust frequency depending on your board and SD Card. 
  Other parameters might not be neccesary to modify.
  Check pinout. 
  */

#include <SPI.h>
#include <SD.h>

File myFile;
const int SD_CS =       5;        // SD Chip Select (SS).
const int SD_CLK =     18         // SD Clock (SCK).
const int SD_MOSI =    23;        // SD Master Out Slave In (MOSI).
const int SD_MISO =    19;        // SD Master In Slave Out (MISO).
const int iterations = 2048*4;    // Total number if iterations. 2048 = 1 MB. Since every iter is 512 bytes.
const int dataLength = 512;       // Vector size. 512 is the largest accepted for SD buffer (apparently).
const int tries = 3;              // Number of tests.
uint32_t  frequency = 18000000;   // SPI clock. Check datasheet. It could be CPU freq/2 or /4.

// ESP32 32E has 2 avaiable SPI devices, test any of them.
SPIClass vspi = SPIClass(VSPI);
SPIClass hspi = SPIClass(HSPI);

void setup() {
  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  Serial.begin(115200);
  hspi.begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS); // Test hspi or vspi.
  pinMode(SD_CS, OUTPUT); //SD SS.

  while (!SD.begin(SD_CS, hspi, frequency)) { // Init SD with chosen frequency.
    Serial.println("Card Mount Failed");
    return;
  }
  delay(100);
}

size_t i = 0, iter = 0;
template<typename T>
void testWriteSpeed(const char* typeName, size_t numElements) {
  T localDataArray[numElements];
  
  for (i = 0; i < numElements; i++) { // Dummy data.
    if (sizeof(T) == 1) localDataArray[i] = random(0, 255);
    else if (sizeof(T) == 2) localDataArray[i] = random(0, 65535); // ESP32 int is 4 bytes.
    else if (sizeof(T) == 4) localDataArray[i] = random(0, 4294967295);
  }
  // Remove previous file.
  SD.remove("/test.bin");
  File file = SD.open("/test.bin", FILE_WRITE);
  if (!file) {
    Serial.println("Can't write file.");
    return;
  }

  // Write test. The idea is to test with 512 bytes regarless of the sizeof(localDataArray[0])
  unsigned long startTime = millis();
  for (iter = 0; iter < iterations; iter++) {
    file.write((byte*)localDataArray, dataLength);
  }
  file.flush();
  file.close();
  unsigned long endTime = millis();
  unsigned long totalTime = endTime - startTime;
  // File size check.
  file = SD.open("/test.bin", FILE_READ);
  if (!file) {
    Serial.println("Can't read file.");
    return;
  }
  Serial.println("  --  iter = " + String(iter));
  Serial.println(String(typeName) + ": " + String(totalTime) + " ms");
  Serial.println("File size: " + String(file.size() / 1024.0) + " Kbytes");
}

template<typename T>
void runTestMultipleTimes(const char* typeName, size_t numElements, int repeatCount) {
  for (int i = 0; i < repeatCount; i++) {
    Serial.print("Try: " + String(i + 1));
    testWriteSpeed<T>(typeName, numElements);
    Serial.println("...\n");
  }
}

void loop() {
  runTestMultipleTimes<byte>("byte", dataLength / sizeof(byte), tries);
  runTestMultipleTimes<long>("long", dataLength / sizeof(long), tries);  // 512 bytes = 128 long
  digitalWrite(2, HIGH);
  while (1)
    ;
}

Then you will be right at home with the SD card software. It was all designed based on the IBM and compatible floppy disk and MS DOS.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.