Audo recording on Arduino Uno: "Skips" & "Pops" answered.

I just had my first success at recording a .WAV file on Arduino, and playing it back on my laptop. I’m very happy with the sound quality (better than I’d hoped). The volume is very low because I just plugged my Walkman’s earphone jack into Uno’s analog input A0. That’s not a problem as I’ll ad an op-amp later.

But there are about 4 to 10 tiny random skips per second, most shorter than it takes to pronounce the letter “T”. But of course, they are annoying, and occasionally long enough to be confusing.

I’m impressed that the Atmega328 is capable of taking in 38,500 one-byte audio samples per second via an interrupt, and simultaneously saving them on an SD CARD via an SPI connection. I had thought the interrupt would interfere with the SPI signal transmission, etc. I’m amazed they get along so well.

The interrupt writes to one array of bytes while the SD CARD is reading from another array of bytes, then the two array’s are swapped and the read/write process continues.

This is the audio file. You can play it to hear the “skips”. I’m hoping someone here will understand what’s causing these recorded “skips”, and can suggest a solution.

It might also be helpful to show you the sketch, so here it is:

//This sketch is based on the
//Audio in with 38.5kHz sampling rate, interrupts, and clipping indicator
//by Amanda Ghassaei
//http://www.instructables.com/id/Arduino-Audio-Input/
//Sept 2012

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
*/

#include <SD.h>

//#include <SPI.h>
const byte audioPin = 2;
const byte ledPin = 3;
// const byte SS=10; // Slave Select (already defined)
// const byte MOSI =11; (already defined)
// const byte MISO = 12; (already defined)
// const byte CLOCK = 13;
File myFile;
byte writes = 0;
volatile uint16_t buffCnt;
byte buffA[256];
byte buffB[256];
volatile boolean myFileWrite = false;
volatile unsigned long counter = 0;
volatile boolean aReady;

// wavheader setup
// little endian (lowest byte 1st)
byte wavheader[44];
String FileDate()
{
  return "12345678.WAV"; //here GET REAL VALUE FROM DM_Master
}

boolean doFileOpen()
{
  String fName = FileDate();
  char filename[fName.length()+1];
  fName.toCharArray(filename, sizeof(filename));
  if (SD.exists(filename))
    SD.remove(filename);
  myFile = SD.open(filename, FILE_WRITE | O_TRUNC);
  //myFile = SD.open(filename, O_CREAT | O_TRUNC);
  delay(10);
  if (!myFile)
  {
    Serial.println(F("myFile failed to open"));
    //Serial.print("F02^");
    return false;
  }
  else
  {
    myFile.write(wavheader, 44); // write wav header
    myFile.seek(44); //set data start
    Serial.println("The file named "+fName+" was created");
    //Serial.print("T^");
    return true;
  }
}

//variable to store incoming audio sample
byte incomingAudio;

void setup()
{
  Serial.begin(9600/*115200*/);
  delay(10);
  //====================================
  wavheader[0]='R';
  wavheader[1]='I';
  wavheader[2]='F';
  wavheader[3]='F';
  wavheader[4]=255; // file size - 8
  wavheader[5]=255;
  wavheader[6]=255;
  wavheader[7]=255;
  wavheader[8]='W';
  wavheader[9]='A';
  wavheader[10]='V';
  wavheader[11]='E';
  wavheader[12]='f';
  wavheader[13]='m';
  wavheader[14]='t';
  wavheader[15]=' ';
  wavheader[16]=16; // verify above byte count
  wavheader[17]=0;
  wavheader[18]=0;
  wavheader[19]=0;
  wavheader[20]=1; // 1 = PCM format
  wavheader[21]=0;
  wavheader[22]=1; // number of channels
  wavheader[23]=0;
  wavheader[24]=100; // samples per second
  wavheader[25]=150;
  wavheader[26]=0;
  wavheader[27]=0;
  wavheader[28]=100; // bytes per second
  wavheader[29]=150;
  wavheader[30]=0;
  wavheader[31]=0;
  wavheader[32]=1; // bytes per sample
  wavheader[33]=0;
  wavheader[34]=8; // bits per sample
  wavheader[35]=0;
  wavheader[36]='d';
  wavheader[37]='a';
  wavheader[38]='t';
  wavheader[39]='a';
  wavheader[40] = 255; // size of data section
  wavheader[41] = 255;
  wavheader[42] = 255;
  wavheader[43] = 255;
  pinMode(audioPin,INPUT_PULLUP);
  pinMode(SS,OUTPUT);
  digitalWrite(SS, HIGH);
  pinMode(ledPin,OUTPUT);//led indicator pin
  pinMode(10, OUTPUT);
  Serial.println("Pins set");
  Serial.println("SPI.begin");
  if (!SD.begin(10))
  {
    Serial.println("SD Card failed to start!");
  }
  else
  {
    Serial.println("SD Card started.");
  }
  delay(2000); //here
  delay(10);
  doFileOpen();
  myFile.flush();

//--------------------------------------

  cli();//disable interrupts
  
  //set up continuous sampling of analog pin 0
  
  //clear ADCSRA and ADCSRB registers
  ADCSRA = 0;
  ADCSRB = 0;
  
  ADMUX |= (1 << REFS0); //set reference voltage
  ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only
  
  ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
  ADCSRA |= (1 << ADATE); //enabble auto trigger
  ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN); //enable ADC
  ADCSRA |= (1 << ADSC); //start ADC measurements
  
  sei();//enable interrupts

  //if you want to add other things to setup(), do it here
}

ISR(ADC_vect)
{//when new ADC value ready
  if(aReady)
    buffA[buffCnt]=ADCH;
  else
    buffB[buffCnt]=ADCH;
  if (buffCnt == 254)
  {
    buffCnt = 0;
    aReady = ! aReady;
    myFileWrite = true;
  }
  else
    buffCnt++;
  counter++;
 }

void loop()
{
  while (true) // reduces jitters
  {
    if (myFileWrite)
    {
      myFileWrite = false;
      if (aReady)
        myFile.write(buffB,255);
      else
        myFile.write(buffA,255);
      if (writes > 99)
      {
        if (digitalRead(audioPin) == HIGH) // btn pressed to stop?
        {       
          myFile.flush();
          writes = 0;
        }
        else
        {
          noInterrupts();
          myFile.close();
        }
      }
      else
        writes++;
    }
  }
}

Well, since writing the above last night, I've tried some experiments that improved the situation:

One was the removal of the line "myFile.flush();". This line repeatedly updated the file being saved to the SD Disk, but the time that took was the major cause of the "skips". The line is not necessary anyway, as long as you save the file at the end by grounding input pin 3. After a moment, doing so executes the line "myFile.close();". This is totally necessary, because with neither flushing nor closing, you'll end up with an empty file.

In addition, I placed a 220 mfd capacitor across 5v and ground at the point where I have two resistors creating the 2.5v bias voltage for the analog input A0. This removed nearly all the "buzzing" you hear in my demo file above.

Thinking lack of synchronization between the interrupt service routine (ISR) and the main loop might be the problem, I tried placing EVERYTHING inside the ISR procedure. This wasn't a good answer since it delayed the timing between interrupts, resulting in slow recording that played back at normal speed sounding like the Chipmunks! It also make the pitch shaky/uneven.

There are still random "clicks", but later when I have the op-amp and start recording at full volume, I think the clicks will be quiet enough by comparison to be acceptable, so I'm satisfied with the sound now as it is.

BUT.... If anyone as ideas how I can improve the sound more, I'd love to give your inspiration a try!

============================

It's evening again now, and I found an article, Arduino Audio Output, that explains pops and snaps as being "discontinuities" in timing between the changes to bits and bytes within the AVR's digital processing. In other words, not everything can happen all at exactly the same time, causing spikes before other bits and bytes catch up to match the earier bits and bytes. The result is the sharp spikes like those seen in this photo from that article:

|500x333

The answer, it explains, is to use external RC (capacitor and resistor) high-pass filtering to remove them.

The Arduino uses timing with TIMER0 for example for millis(). In some situations it is possible to stop TIMER0.

When TIMER0 is stopped, that interrupt for the Arduino 'system' timing is stopped. Any interrupt will delay the code and other interrupts. But the sketch must allow it. I remember vaguely that TIMER0 was stopped in a sketch that outputs VGA or VIDEO, but I'm not sure. The 'system' timing magic happens here : https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c

The Serial library also uses interrupts, but only if something is being send or received.

The SPI bus can be set faster, if the SD card supports it. http://www.arduino.cc/en/Reference/SPISetClockDivider With the DIV2, it is twice as fast. I use that for my W5100 Ethernet chip, but not for my SD card, since I don't want to risk any trouble with older SD cards.

The author of that instructable claims that the discontinuities shown in the picture are caused by a loss of synchronization between writing the higher-order bits and the lower-order bits, since they're on two different ports of the ATmega328P. You don't seem to be using a homebrew DAC, so I doubt that your pops and clicks have the same source.

Intuitively, I suspect that writing to the SD card takes longer than you think, from time to time. My knowledge of the ways of SD cards is quite limited. But, I've seen posts that suggest that an SD card can exhibit substantial latency, on the order of many milliseconds, if not tens of milliseconds. I think that, from time to time, it takes so long to write one of the buffers to the card that the other buffer overruns, and some of the data that you wanted to write to the card is either garbled or skipped.

A way to test that theory would be to write some readily-identifiable data to the card, rather than musical data. You might change this code:

  if(aReady)
    buffA[buffCnt]=ADCH;
  else
    buffB[buffCnt]=ADCH;

to thi:

  if(aReady)
    buffA[buffCnt]=buffcnt;
  else
    buffB[buffCnt]=buffcnt;

That would write a straightforward ramp, but would still retain all the timing of the ADC. Then, we could look at the resulting file in Audacity, or something like it, and see whether there appear to be missing or garbled groups of samples.

You two have some really good answers! I feel confident you've "hit the nail on the heard".

I'll be testing these theories out when some ordered parts arrive; namely, the LM358 amplifier chip for the microphone, and the LM386 amplifier for the speaker.

I feel you've fully answered my questions, and am removing the "?" icon from this post.

Thanks a million.

Tommy