Instructables
loading
loading
Picture of Arduino (Mega) Audio Recording
serial1.jpg
serial2.jpg

Record Audio to your Audino Mega SD card.

The audio file can be played back on a standard audio application or analysed byte by byte.

This Instructable will show you how audio input can be repeatedly added to a 512 byte buffer and then transferred to a SD card in real time. The period recorded can be altered.

The sample rate is 9.4 KHz and the wav file output 8 bit, mono. Whilst not hi-fidelity, the sound quality is perfectly adequate.

The recorded wav file can be saved as tabulated data. It can also be displayed as a simple scrolling graph on the monitor.

All files are time stamped using a unix time code sent from the serial monitor.

The inspiration for this article came from reading Amanda Ghassaei: http://www.instructables.com/id/Arduino-Audio-Inp...

.

My latest program update at the end of this instructable, increases the sample rate to 19 KHz with a significant improvement in audio quality.

.

You may also be interested in my instructables on:

A high speed Arduino Oscilloscope: http://www.instructables.com/id/Arduino-High-speed...

Playing wav files from the Arduino: http://www.instructables.com/id/Arduino-playing-wa...

.

Inevitably SD card technology has already improved. I have tested with a Sandisk Ultra 8GB sd card. This is significantly faster than the Kingston 4GB card I started with. Using my updated software I am able to record at 38.3KHz, with no degradation in quality. (4/8/2014)

 
Remove these adsRemove these ads by Signing Up

Step 1: Requirements

Picture of Requirements
53b71f91f92ea1d1c9000032.jpg
53b71edcf92ea1f268000094.jpg

Arduino Mega 2560

The following components work- alternatives may be viable (with program tweaking- I leave that to you!)

LCD Keypad Shield http://www.hobbytronics.co.uk/arduino-lcd-keypad-...

MicroSD Breakout Board Regulated with Logic Conversion V2

http://www.hobbytronics.co.uk/microsd-card-regula...

4GB Micro SD Memory Card http://www.hobbytronics.co.uk/microsd-card-regula...

Microphone pre-amplifier-

ac coupled with a potential divider to centre the voltage between Arduino 0-5 V rail

Amanda Ghassaei has published a circuit at http://www.instructables.com/id/Arduino-Audio-Inpu...

I designed my own with bass, treble and volume controls. However there are plenty of pre-amp designs on the web.

TillmanZ1 month ago

Hi David!
Thanks a lot for this instructable!

I am planning on building the circuit but want to use a Winbond SPI flash (W25Q64 - 64Mbit) instead of the SD card.

If I am not mistaken then the buffer size for the Winbond should probably be 256bytes instead of the 512bytes.

Did you ever play around with flash chips?

DavidPatterson (author)  TillmanZ1 month ago

Hi,

I haven't used an SPI flash device.

I cannot think of a downside in reducing the buffer size to 256.

If the flash write cycle is faster than the sdcard write time (512bytes in 1.6 ms)- you should be able to easily achieve 38KHz. (Assuming you adapt my double buffering code.).

What is the write-rate for your flash?

Let me know what happens.

David

DavidPatterson (author) 2 months ago

Science marches on!

I tested the latest Sandisk extreme 16GB card today: (On special offer at Tesco)

512 bytes are read in 1.56 mS. That is 25% faster than the previous card (2.07mS)

The sound quality is very good!

nareshkumarR2 months ago

Hi David,

This is an awesome article, thanks for posting.

I have recently started working with arduino UNO and the "write" function in sd Card library is not working. Below is my code. I have tried with "println" which is working fine. Any help regarding this is welcome.

void loop()

{

byte b=10;

File dataFile = SD.open("datalog.txt", FILE_WRITE);

if (dataFile)

{

dataFile.write(b);

}

}

DavidPatterson (author)  nareshkumarR2 months ago

Hello Nareshkumar,

Check you have the latest version of the SD library installed.

I wrote the following code to test your problem.

It works if you send byte 10 as a single variable b, or send 10 as the first byte in buffer bufa.

I presume you are testing how to send bytes, not strings? The suffix ".txt" in your filename was confusing.


#include <SD.h>
// set up variables using the SD utility library functions:

const int defaultselect= 10; // uno (mega is 53)
const int chipselect = 10; // any pin you want, including default

File datafile;
Sd2Card card;

void setup(){
Serial.begin(115200); // start serial for output
Serial.flush();
// setup 2 buffers, any size you might need
uint8_t bufa[20];
uint8_t bufb[20];
// initilise buffers
bufa[0]=10;
bufb[0]=0;
byte b =10;
Serial.print("bufa[0] ");Serial.println(bufa[0]);
Serial.print("bufb[0] ");Serial.println(bufb[0]);
// make sure default select line is set to output

if (chipselect != defaultselect) { pinMode(defaultselect, OUTPUT); }

if (SD.begin(chipselect)) {
card.init(SPI_FULL_SPEED, chipselect);
Serial.println("Card works");
// delete old file
if (SD.exists("datalog.txt")) SD.remove("datalog.txt");
// write the byte
File datafile = SD.open("datalog.txt", FILE_WRITE);
if (datafile){

// comment out one at a time to test
datafile.write(bufa,1);
//datafile.write(b);
// ___________________________________________
datafile.close();
Serial.println("Byte written!");
// try to read the byte back
datafile = SD.open("datalog.txt", FILE_READ);
if (!datafile){
Serial.println("File failed to open for read");
} else{
datafile.read(bufb,1);
datafile.close();
Serial.print("Byte returned ");
Serial.println(bufb[0]);
}
} else{
Serial.println("File failed to open for write");
}
} else{
Serial.println("Card failed to initialise");
}
}

void loop(){
}

David

I have tried the above code but still I am unable to "write" to the sd card. I am using arduino 1.0.6 and the SD library is up to date as shown in the image below. I am trying to use your code to capture audio and I am unable to do with write function of SD library; when I am using for loops and then printing each byte of the buffer arrays to the SD card, the frequency is decreasing to 3 KHz as opposed to 9 KHz . The code of yours I am using is pasted below. Is there any way to overcome this? Thanks!

Code:

#include <SD.h>

#include <SPI.h>

// set up variables using the SD utility library functions:

Sd2Card card;

SdVolume volume;

SdFile root;

const int chipSelect = 10; // (Or any suitable pin!)

File tempfile;

boolean hascard=false;

boolean written, aready, writeit;

unsigned long starttime , endtime, filesize;

float frequency;

float period , interval;

int const adport = 2; // Set the port to be used for input!

// buf is used to store icoming data and is written to file when full

// 512 bytes is optimized for sdcard

#define BUF_SIZE 128

int bufa[BUF_SIZE];//uint8_t bufa[BUF_SIZE];

int bufb[BUF_SIZE];//uint8_t bufb[BUF_SIZE];

uint16_t bufcount;

char bufFile[]="/adlog/00112233.txt";

unsigned long readings = 100000; // initial sample size- kept small to avoid delay- enough to create data to look at

int i;

unsigned long counter;

// Defines for clearing register bits

#ifndef cbi

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))

#endif

// Defines for setting register bits

#ifndef sbi

#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#endif

// alter prescalar here 32, 64, 128

// 32 is the fastest sampling rate

byte prescalar=128;

void setup(){

//Specimem frequency used for first reading to estimate initial sample duration

/* if(prescalar==32) frequency = 38.3829;

if(prescalar==64) frequency= 19.2300;

if(prescalar==128) frequency= 9.6182;*/

pinMode(10, OUTPUT);

Serial.begin(115200); // start serial for output

Serial.flush();

if (SD.begin(chipSelect)) {

hascard=true;

aready=true;

card.init(SPI_FULL_SPEED, chipSelect);

// Create adlog sub-directory

if (SD.exists("/adlog") == false) {

SD.mkdir("/adlog");

}

hascard=fileopen();

}

cli();

startad();

}

void startad(){

// Setup continuous reading of the adc port 'adport' using an interrupt

cli();//disable interrupts

//clear ADCSRA and ADCSRB registers

ADCSRA = 0;

ADCSRB = 0;

ADMUX |= adport; //set up continuous sampling of analog pin adport

//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

// pre scalar to set interrupt frequency:

//ADCSRA |= (1 << ADPS2); // 16 prescalar 72Khz, but what could you do at that rate?

// 128 prescalar - 9.4 Khz sampling

if (prescalar==128) ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

//32 prescaler - 16mHz/32=500kHz - produces 37 Khz sampling

if (prescalar==32) ADCSRA |= (1 << ADPS2) | (1 << ADPS0);

// 64 prescalar produces 19.2 Khz sampling

if(prescalar==64) ADCSRA |= (1 << ADPS2) | (1 << ADPS1);

ADCSRA |= (1 << ADATE); //enable auto trigger

ADCSRA |= (1 << ADIE);

//ADCSRA |= (1 << ADEN); //enable ADC- works fine, but so does the next line

sbi(ADCSRA,ADEN); // //enable ADC

ADCSRA |= (1 << ADSC); //start ADC measurements on interrupt

writeit=false;

starttime=millis();

sei();//enable interrupts

}

// Interrupt routine *******************************************

// this is the key to the program!!

ISR(ADC_vect) {

if (counter < readings) {

if(aready) { // get the new value from analogue port

bufa[bufcount]=ADCH;

} else {

bufb[bufcount]=ADCH;

}

counter++; // increment data counter

bufcount++; // increment buffer counter

if(bufcount==BUF_SIZE){

if(writeit==false) {

bufcount=0;

aready = ! aready;

writeit=true; // flag that a write is needed

} else { // wait for file write to complete

bufcount--;

counter--;

}

}

} else {

// All data collected

cli();//disable interrupts

cbi(ADCSRA,ADEN); // disable ADC

sei();//enable interrupts

endtime=millis();

// write the last block

if (aready){

for(int i=0;i<BUF_SIZE;i++)

{

tempfile.println(bufa[i]);

}/// write the data block

// tempfile.write(bufa, BUF_SIZE);

} else {

for(int i=0;i<BUF_SIZE;i++)

{

tempfile.println(bufb[i]);

}

// initiate block write from B

//tempfile.write(bufb, BUF_SIZE); // write the data block

}

Serial.println(F("Logging stopped"));

period= endtime-starttime;

frequency = float(readings)/period;

Serial.println(frequency);

interval = 1000/frequency;

tempfile.flush();

}

}

// End Interupt section *******************************************

void loop(){

if(writeit){

if (aready){

for(int i=0;i<BUF_SIZE;i++)

{

tempfile.println(bufb[i]);

}

//tempfile.write(bufb, BUF_SIZE); // write the data block

} else {

for(int i=0;i<BUF_SIZE;i++)

{

tempfile.println(bufa[i]);

}

// initiate block write from buf A

//tempfile.write(bufa, BUF_SIZE); // write the data block

}

writeit=false; // the write is done!

}

}

// End Void Loop section ******************************************

boolean fileopen(){

if (SD.exists(bufFile)) {SD.remove(bufFile);}

tempfile = SD.open(bufFile, FILE_WRITE | O_TRUNC);

if (!tempfile) {

Serial.println(F("Tempfile failed to open"));

return(false);

} else{

// hasdata=false;

written=false;

counter=0;

bufcount=0;

float var = float(readings)/1000;

float estimate = var / frequency;

Serial.print(F("\nLogging ("));

Serial.print(estimate,2);

Serial.println(F(" S)"));

return(true);

}

}

sd.png
DavidPatterson (author)  nareshkumarR2 months ago

Hi,

What happened when you tried the write one byte example?

What output appeared in the serial monitor?

The Uno runs at a lower frequency than the mega and has considerably less memory. Both these factors are working against you!

Hi,

In the one byte example,the text file gets created but the byte doesn't get printed to the text file. I can't rely on the serial monitor output because you are directly printing bufb[0] in the end, by only checking whether the text file has been created. Thanks!

DavidPatterson (author)  nareshkumarR2 months ago

Hi,

You can rely on the serial output:

bufb(0) is initialised with zero

bufb(0) is read from the file in the line

datafile.read(bufb,1);

a one byte buffer is read into the array bufb!

.

If bufb(0) is still zero, then the file is in error.

It would be helpful to supply a screen capture from your serial monitor after each of the test routine options have executed.

Hi,

I have understood the issue; number 10 happens to be new line character in ASCII. So, I was unable to view the character in the text file. When I try with different numbers their respective ASCII values are getting printed. When we are reading them in an arduino code they are again being converted to their respective human understandable values. Thanks for your help for the whole day. Cheers!

JohnL79 months ago

Thank you for posting this, this is a great project and you have clearly laid out all the steps.

In your comments on your latest version, it states that 32 is the fastest sampling rate, even though you left in the code for a prescaler of 16.

Have you been able to get any faster rates with lower prescalers?

DavidPatterson (author)  JohnL79 months ago

Hi,

The 32 prescalar works well with the latest high speed sd card, producing a sample rate of 38.3Khz on my arduino mega.

A prescalar of 16 should interrupt at 72Khz- this rate will not leave enough time for the buffer writes to complete. 512 bytes would be read in 512*1000/72000 mS:

7.1ms

This will almost certainly be less than the write time for the buffer at the current speed of software and sdcard.

So the interrupt will prevent the completion of the write- hence the degradation in observed frequency response. (As you observed)

I find the audio quality for 38.3Khz very acceptable-

Have you got it working?

.

Reducing the buffer size is not an option- according to best advice 512 bytes is the optimal block size.

A future development might be stereo- two preamps and a 512byte buffer comprising 256 left channel, right channel data pairs......

My follow up instructable detals how to play back the wav files from the sdcard- I have had no problem with files recorded at 44Khz. So 38.3Khz files will work quite happily.

JohnL7 JohnL79 months ago

The 16 prescaler actually slows down the sample rate compared to the 32 prescaler. With the 32 prescaler, i was showing ~38kHz sample rate. With the 16 prescaler, I was showing ~31.8kHz sample rate. Is this because of the interrupts?

amandaghassaei10 months ago

thanks for posting this! a lot of great info here. Do you think it's possible to increase the sampling rate above 9.4kHz? is there a limit to how fast you can interface with the sd card?

DavidPatterson (author)  amandaghassaei10 months ago

I did a re-write this evening, utilizing double buffering.

I now have the frequency easily up to 19Khz, with very good clarity.

Further testing at higher frequencies is needed, but results are very promising.

DavidPatterson (author)  amandaghassaei10 months ago

I investigated sampling with pre-scalars of 16 (72Khz), 32,(36Khz), 64(18.6Khz) and 128 (9.4Khz). I have left the necessary code in the startad subroutine for those who wish to try.

Without doubt, the limiting factor is the 512 byte block transfer to the sd card.

I believe that it is unlikely that further optimization is possible within the block write section of the code.

Obviously, if it takes longer than the sample period to write a sd block, then the audio is missed (or the frequency perverted) and the sound quality collapses.

At present, 9.4Khz is the limit for my SDcard writer.

No doubt card writer technology will speed up.

I have read claims for faster (beta) versions for sd libraries. However I wanted to use the standard sd library.

Faster sampling is possible if memory is used as the buffer, rather than the sd card.

My best optimization for that system allowed for a 6000 byte buffer on a Mega.

At 38 Khz, only a fraction of a second can be read. This is no good for recording audio, but has applications as an oscilloscope / fast logger.

bergerab10 months ago

This is simply incredible. I love how you go over wav files. I always thought about this but I never thought it could actually be done with an arduino. Thanks for sharing

DavidPatterson (author)  bergerab10 months ago

Thanks for that.

Let us know if you you get it up and running.