Arduino (Mega) Audio Recording

57,473

138

26

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: https://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: https://www.instructables.com/id/Arduino-High-speed...

Playing wav files from the Arduino: https://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)

Step 1: Requirements

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 https://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.

Step 2: The Specifications

Serial, LCD, SD and flash memory support.

Serial set at 115200 baud.

Time stamp in Linux format from Serial monitor.

Serial Input can initiate new data capture- using the command 'again' .

Serial input can set the number of Kbytes of data read and therefore the duration of the audio file- using the command 'alter' .

Serial output of data after input request- command 'read' - output can be stopped with command 'q'

Basic graph plotted on serial monitor.

Raw data saved in wav compatible file.

Raw data translated to tabulated file- command 'write'.

Repeated 60 s time update to eeprom. LCD dims during time update to eeprom.

PWM 3 Square wave available for testing- toggle with command 'test'.

Input 2 set high avoids wait for serial time set.

Uno use may be possible with program tweaking including the the relocation of the time storage bytes (variable ememory). I leave that work to you.

NB best quality audio is obtained with the input varying around the mid voltage value of 128. This is important!

Lcd output of basic information.

Lcd adc port specified with variable lcdport.

Lcd button support:

Select initiates a new sample Left alters Sample Kbyte number- then up +10, down -10, left -1, right +1

Step 3: Getting Started

Connect the LCD keypad shield.

Connect the SD card reader

0, 5V

CLK to pin 52

D0 to pin 50

D1 to pin 51

CS to pin 53

I mounted the SD reader on a bit of strip board with pass through pins to match the required connection points.

(Details to follow)

You will probably want to test the lcd and sd card reader using sample scripts from the point of purchase.

This will sound a little casual, but it is all detailed else-where:

Build a microphone pre-amp and connect it through a potential divider- as described by Amanda.

Or use another design.

I recommend that you include a variable resistor in the potential divider section so that the input can be carefully adjusted to centre around a measured input value of 128 (Half of the 0-5 supply voltage. On my PC "5V" is less than 5V when powered by the USB.)

The lcd screen I specified uses analogue port zero for the lcd buttons- so connect your audio input to A1.

Step 4: Mounting the Sdcard Reader

The sdcard can be easily mounted on a piece of strip board and located using pass through pins.

I used two sets of pin connectors (top and bottom), to stabilize the board.

The connections are next to each other on the Arduino Mega. So the copper strip board tracks between the connections must be cut. I used a hand held junior hack saw blade (not in the saw frame) with one locating pin knocked out. Make sure you use a good quality fine toothed blade. It is also very useful for cleaning the gap between the copper strips. I found it easier to cut the 5V track whilst cutting the next two. (See picture 4) It is then a simple matter to solder a wire across the cut.

For the pins I used standard pin strip. The pin length on the Copper side was adjusted to the depth of the Arduino socket, prior to soldering. Spare pin length on the component side was cut and filed clean following soldering.

An eight way header connecter was soldered on and a 90 degree pin connecter plugged in. A small piece of fine dowel was used to support the reader. A small hole drilled through the corner of the board allows a screw to hold the dowel, although glue would be ok.

I added a pin header to the 0V line on the strip board. This ensured that I had not lost the availability of the 0V connection point.

The SDcard reader will need a soldered 90 degree socket header.

The 5V power supply can be collected with a flying lead to one of the pins between 21 and 22 (marked 5V).

Check that you have cut all the tracks shown in the picture, including the tracks cut with a drill bit!

Check your layout and soldering before plugging in!

Step 5: Wave File Format

A wave file header is a 44 byte block.

The header is at the start of the wav file.

The header block must be initialised globally with:

byte wavheader[44];

Audio data follows the header.

For an 8 bit, mono wav file data is a number between 0 and 255.

The script reads the A1 port, interpreting the incoming audio as a voltage between 0 and 255. Perfect!

Only 4 sections of the header need to be updated when the a wav file is saved.

These are shown as the commented sections in the picture.

The bytes at offset 4 contain the length of the data + the length of the header - 2*4 bytes

Or more simply the data count +36.

Each commented section is a 4 byte number.

The four bytes occur with the least significant byte first- this is "little endian format".

The following routine will accept a long value and write it to a specified point in the wav header as 4 succesive bytes:

void headmod(long value, byte location){
// write four bytes for a long

tempfile.seek(location); // find the location in the file

byte tbuf[4];

tbuf[0] = value & 0xFF; // lo byte

tbuf[1] = (value >> 8) & 0xFF;

tbuf[2] = (value >> 16) & 0xFF;

tbuf[3] = (value >> 24) & 0xFF; // hi byte

tempfile.write(tbuf,4); // write the 4 byte buffer

}

Step 6: Setting the Interrupt That Reads the Analogue Port

We can set up an interrupt so that one analogue port is repeatedly read.

(See Amanda's article for further details)

My "startad" subroutine uses a pre-scalar of 128.

This creates a repeated interrupt so that port A1 is continually read at 9.4KHz.

The interrupt subroutine has a two fold function:

1) If the counter is less than the number of readings:

  • Add the audio data byte to the 512 byte buffer.
  • when the buffer is full, write the buffer in one go to the sdcard- 512 bytes is the optimal size for fast data saves. Reset the buffer count to zero for the next incoming audio byte.

2) When the counter is equal to the required number of readings:

Stop the interrupt occurring again

  • Calculate how long the process took, frequency etc.
  • Use the frequency and file size to update the wave file header.
  • Release analogue port A1 and enable port A0 so that the lcd buttons will work.

Step 7: Setting the Date and Time and Sneaky Saving

The Arduino can keep track of time, but has to be told a starting point.

When it is restarted- it has to be told again. (Unless you fit a real time clock).

The time can be specified as a unix time string- which is the number of seconds elapsed since the " standard epoch of 1/1/1970"

The serial console can be used to send a T followed by the unix time string.

T1403524800 represents 12am June 3rd 2014

See subroutine "waitfordate" and "processSyncMessage"

if (Serial.find(TIME_HEADER)) { // Look for the T
pctime = Serial.parseInt(); // extract the time

if( pctime >= DEFAULT_TIME) { // limited check that the time is after default_time

setTime(pctime); // Sync Arduino clock to the time received on the serial port

This site offers unix time values: http://www.onlineconversion.com/unix_time.htm

Time values can be stored in eeprom memory, which is retained after reset.

The values stored are historic- they are not updated during reset.

However they are useful as a starting point for new filenames. When the script is run without time-stamping the previous time value is used with a 60s addition.

writeeeprom and readeeprom allow the storage and retrieval of time strings.

The time is written to the eeprom memory every 60 seconds.

Be aware that eeprom memory has a re-use limit - but the board sockets will probably wear out first.

Step 8: What Is the Output and How Is It Controlled

A folder called adlog is used for the data.

The following output is available:

1) A wav file for each read. The filename is in the format ddhhmmss.wav Audio capture can be repeated with the command "again".

2) Following the serial console command "write", a text file is produced with the data number and data value in columns. This is comma delimited and can readily be imported into other programs for graphical analysis. The filename format is ddhhmmss.txt

3) The serial console command "read" produces a vertically scrolling graphical representation of the audio. This can be stopped by sending a "q" from the console.

The lcd buttons can also be used to record a new audio file and alter the number of logged bytes. Select starts a new file and left starts the "select" function which updates the data number. The lcd A0 port is read and depending on whether the up, down, left or right button is pressed the data number is altered.

The serial console command "test" generates a square wave on PWM3. This is useful for testing without a microphone and pre-amp..

If PWM2 is taken high, the program will not wait for a timestamp from the serial console. The date and time will no longer be current. However this is useful if the usb is not connected.

Step 9: Program Update

I have successfully increased the data acquisition frequency to 19 KHz.

This has entailed using two 512byte buffers (double buffering) and a rewrite of the interrupt and void loop sections.

The audio is very much cleaner.

I also tested at 38 Khz, with very promising results. However at this frequency occasional buffer wait periods are present. It is probable that future improvements in SDCard design and the SD library function speed, will overcome this issue. For those that want to experiment alter the prescalar variable just before void setup.

I have set up bufa and bufb.

In the interrupt routine I set a flag called aready - it is true when writing to bufa, false for bufb.

The flag writeit is true when a write is required and set false when the SdCard finishes.

When a buffer is full (buffcount==BUF_SIZE):

  • I check if the Sdcard is finished writing, in which case writeit==false and I reset the buffer pointer back to zero, switch the buffer flag aready and set the writeit flag true.
  • If the Sdcard is still writing I go one reading back (bufcount--; and counter--;) and exit the interrupt.

Once I have the correct number of readings I shut down the interrupt, write the last data block and tidy up.

The majority of the data is written in void loop:

if(writeit){ // Data is ready to be written

if (aready){

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

} else {

// initiate block write from bufa

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

}

writeit=false; // flag that the write is done

}

Share

    Recommendations

    • Make it Glow Contest 2018

      Make it Glow Contest 2018
    • Optics Contest

      Optics Contest
    • Plastics Contest

      Plastics Contest

    26 Discussions

    0
    None
    RussellR33

    2 months ago

    Sir can I use an electret microphone to record using your project, or only the microphone studio is suitable?

    1 reply
    0
    None
    DavidPattersonRussellR33

    Reply 2 months ago

    Any microphone will require a simple pre-amp.

    I detailed a link to Amanda Ghassaei's input circuit in the article.

    A Typical circuit: http://freecircuitdiagram.com/3808-op-amp-micropho...

    There are many circuits on the internet for both electret and non-electret microphones.

    The output of your pre-amp should be connected to the 10uF capacitor shown in the diagram above. Make sure to connect the ground of your pre-amp to the Arduino ground.

    Cheers,

    David

    0
    None
    PinakiG

    3 years ago

    Sir,

    Do I need exactly Mega 2560 or I can use an Ordinary ATMEGA328 on a veroboard? I'm asking this because ATMEGA2560 offers same speed and it is also 8-bit, but offers a few extra pins. If I can use an ordinary ATMEGA328 uC then it will be great. If it can be done without problem, please mention it and also tell me something about the modifications.

    1 reply
    0
    None
    DavidPattersonPinakiG

    Reply 3 years ago

    Hi Pinakig,

    sorry I have no advise to offer on that setup.

    I use Megas for all of my Arduino applications.

    Cheers,

    David

    0
    None
    cobmetal

    3 years ago on Introduction

    Hi , im new to arduino , i need to record wav to SD card without the LCD , can you please modify the code , for the arduino due.

    any help will be appreciated .

    1 reply
    0
    None
    TillmanZ

    3 years ago on Introduction

    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?

    1 reply
    0
    None

    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

    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!

    0
    None
    nareshkumarR

    3 years ago on Introduction

    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);

    }

    }

    6 replies

    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

    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!

    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!

    0
    None
    JohnL7

    4 years ago on Introduction

    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?

    1 reply
    0
    None
    DavidPattersonJohnL7

    Reply 4 years ago on Introduction

    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.