Audio samples lost due to SD card writes

Status
Not open for further replies.
This simple audio recorder code takes I2S input from the Teensy audio shield, and saves a mono channel continuously to SD card. I'm feeding the line-in input on the shield with a 40Hz triangle wave from a bench signal generator. The following code is stripped-down, and does nothing but record audio. When looking at the resulting raw file in Audacity, the waveform has discontinuities that are *not* periodically distributed. The upshot is that recording for 100 seconds, may only produce a file that is 98 seconds long. This gets to be a big error over time, and means the device cannot be used for synchronized data collection. If there is a solution to this, great, let me know, but I'm mostly posting to let others know that recording to SD can be a challenge. Based on my own testing, it seems that the SD card write functions cause some amount of CPU blocking that even the DMA in the audio library cannot interrupt.

Teensy 3.6 @ 180MHz, using built-in SD card slot.

I've tried two different SD cards with similar results, but the distribution and intensity of disruptions may have been different -- hard to tell.

Older, similar thread: https://forum.pjrc.com/threads/52476-Audio-board-recording-while-playing-sampling-frequency-problem

SD cards.jpg

Screen Shot 2019-01-30 at 11.42.14 AM.jpg


Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

File frec;
// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=343,366
AudioRecordQueue         queue1;         //xy=281,63
AudioConnection          patchCord1(i2s1, 0, queue1, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=590,729
// GUItool: end automatically generated code


const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;



void setup() {
  Serial.begin(9600);
  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(240);
  
// Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);
  sgtl5000_1.lineInLevel(8,8);
  //sgtl5000_1.adcHighPassFilterDisable();

 if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here if no SD card, but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }


 Serial.println("startRecording");
  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
    Serial.println("old audio file removed");
  }
  
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  
  if (frec) {
    queue1.begin();
    Serial.println("frec open");
  }



  
}

elapsedMillis flushtimer = 0;

void loop() {
   continueRecording();

  if(flushtimer > 1000)
    {
      frec.flush();
      flushtimer = 0;
    }

}

void continueRecording() {
   
  if (queue1.available() >=2 ) {
    
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer+256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
   // elapsedMicros usec = 0;
    frec.write(buffer, 512);

     // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
    
  }
  
}
 
I may have found a partial solution: Replace the SD.h library with SdFs. The following code has been working well with many fewer glitches, but I have not tested extensively. I renamed the object from SD to SDFS just to avoid confusion, but it seems possible to just use SD to avoid changing code.

Audio.h cannot be included with SdFs.h , so each of the components of the audio library must be added separately.


Screen Shot 2019-01-30 at 1.54.33 PM.jpg

Code:
#include "SdFs.h"
SdFat SDFS;
//#include <Audio.h>
#include "record_queue.h"
#include "input_i2s.h"
#include "control_sgtl5000.h"
#include <Wire.h>
#include <SPI.h>
//#include <SD.h>
#include <SerialFlash.h>

#define SDCARD_CS_PIN    BUILTIN_SDCARD
//#define SDCARD_MOSI_PIN  11  // not actually used
//#define SDCARD_SCK_PIN   13  // not actually used

File frec;
// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=343,366
AudioRecordQueue         queue1;         //xy=281,63
AudioConnection          patchCord1(i2s1, 0, queue1, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=590,729
// GUItool: end automatically generated code


const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;



void setup() {
  Serial.begin(9600);
  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(240);
  
// Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);
  sgtl5000_1.lineInLevel(8,8);
  //sgtl5000_1.adcHighPassFilterDisable();

 if (!(SDFS.begin(SDCARD_CS_PIN))) {
    // stop here if no SD card, but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }


 Serial.println("startRecording");
  if (SDFS.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SDFS.remove("RECORD.RAW");
    Serial.println("old audio file removed");
  }
  
  frec = SDFS.open("RECORD.RAW", FILE_WRITE);
  
  if (frec) {
    queue1.begin();
    Serial.println("frec open");
  }



  
}

elapsedMillis flushtimer = 0;

void loop() {
   continueRecording();

  if(flushtimer > 1000)
    {
      frec.flush();
      flushtimer = 0;
    }

}

void continueRecording() {
   
  if (queue1.available() >=2 ) {
    
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer+256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
   // elapsedMicros usec = 0;
    frec.write(buffer, 512);

     // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
    
  }
  
}
 
Status
Not open for further replies.
Back
Top