[ echo-II ] Sparkfun’s SD library: timing writes

Today’s problem was simple. What’s the fastest rate at which we can write an SD card using the Adafruit Adalogger (based on Atmel SAMD21) and the standard SD library in the Arduino IDE? The answer that I have this far appears to be one write every ~70 micros (at best), but it gets weird.

Why does this matter? I want to write two 32-bit numbers (per stereo sample) to an SD card at 96 kHz. Coding this will be a breeze if I can just patch together some examples. I tried that today, and although things could work reliably at say 1-2 kHz (would change based on load on the processor), they definitely wouldn’t work at 96 kHz unless we tweak the library to exploit the SAMD21 a bit more. DMA would help.

But how good / bad can things be if we to just stitch together some calls to library functions? Well, good enough for a non-audio related project, but not when we need high quality audio recording (atleast not without re-configuring some things).

I ran a very simple test: I just wrote out the time elapsed on the microcontroller (in microseconds), and made plots. I think this is the best the SD library can do given that the processor is not doing anything else in the void loop (code is appended). Doing other things will probably slow the writes down more (unless we go for DMA, which we should!)

Check these timing plots out:

051516_results

On the top, I am showing you the total time elapsed as we keep writing micros()’s output to the SD card (note: micros() is used to time code execution in microseconds…just like millis() does it in milliseconds). Note the slope of the blue line – that’s the time period between consecutive samples – is *much* larger than what we need (see the red line for the expected slope for sampling @96 kHz). That is, writes to the SD card are too slow if we just use println() from the SD library in the void loop().

The second plot emphasizes another detail that’s easy to miss on the first plot, that is, how consistent is the timing of these writes anyway? Looks like most of the writes happen at intervals of ~70 micros, but there’s some rather consistent weirdness going on every 57th sample (no cursors shown), and then again every ~910th sample (cursors shown). For these “outliers”, the time elapsed between consecutive writes goes up from ~70 micros to about ~4200 micros in the middle of the plot, and ~8000 for the weirdest outliers towards the top. My first bet is that these have something to do with the actual data dump to the SD card, but that needs to be verified.

Summary this far: the simplest approach of stitching library examples together has failed. We need to go in and configure some registers to make things work faster, and in parallel.

Now, for the code, see [ github ].

Here’s the arduino sketch for quick reference:

 

// Project Echo | Timing writes to SD card using Sparkfun's SD library
// The goal here is to print the output of micros() to an SD card as quickly as possible
//  while using standard calls to the SD library.

// This sketch is adapted from:
//  https://learn.adafruit.com/adafruit-feather-m0-adalogger/using-the-sd-card
//   and Examples/SD/DumpFile

// Hardware: Adfruit Adalogger running Atmel SAMD21 ARM M0+ @48MHz

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

// Set the pins used
#define cardSelect 4
#define redLED 13 // used for indicating button presses
#define grnLED 8 // lights up when logging
char filename[9]; // could reduce size...
File logfile;

// Some dummy data for more timing comparisons
// Replace with 32-bit left/right samples obtained from codec
float placeHolder1 = 1.0202020;
float placeHolder2 = 1.0202020;

void setup() {
  pinMode(redLED, INPUT);
  attachInterrupt(redLED, switchISR, FALLING); // for stopping logging.
  // Could use other interrupts; I happened to have a pushbutton already there.
  pinMode(grnLED, OUTPUT);

  Serial.begin(115200);
  while (!Serial) {} // wait until serial is available. Not neccessary?
  Serial.println("Welcome to the SD echo test!");

  // see if the card is present and can be initialized:
  if (!SD.begin(cardSelect)) {
    Serial.println("Card init. failed!");
  }

  strcpy(filename, "LOG00.CSV");
  for (uint8_t i = 0; i < 100; i++) {
    filename[3] = '0' + i / 10;
    filename[4] = '0' + i % 10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) break;
  }

  logfile = SD.open(filename, FILE_WRITE);
  if ( ! logfile ) {
    Serial.print("Couldn't create ");
    Serial.println(filename);
  }
  Serial.print("Writing to "); Serial.println(filename);
  Serial.println(F("\nSend any character to begin")); // from MPU-6050 teapot demo!
  while (Serial.available() && Serial.read()); // empty buffer
  while (!Serial.available());                 // wait for data
  while (Serial.available() && Serial.read()); // empty buffer again
  digitalWrite(grnLED, HIGH);
  Serial.println("Push button to stop logging!");
}

void loop() {
  logfile.println(micros());
  //logfile.print(micros()); //logfile.print("\t");
  //logfile.println(placeHolder1);// logfile.print("\t");
  //logfile.println(placeHolder2);
  //placeHolder1 += 1.0;
  //placeHolder2 *= 2.0;
  //placeHolder = analogRead(0); // apparently costs ~400 micros -> can be reduced
  //placeHolder = analogRead(1);
  //delayMicroseconds(75); // adds up
}

void fileDump() { // print contents of file for copy/paste/plotting
  Serial.println("Reading back what we wrote...");
  File dataFile = SD.open(String(filename));

  if (dataFile) { // read from the file if its available
    while (dataFile.available()) {
      Serial.write(dataFile.read());
    }
    Serial.println("FIN"); // we did it!
    dataFile.close();
  } else { // else: error!
    Serial.println("error opening datalog");
  }
}

void switchISR() {
  logfile.flush(); logfile.close();
  digitalWrite(grnLED, LOW); // not logging anymore
  fileDump();
  exit(0); // z Z z
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s