Arduino as a SoundCard

This project came up in order to play with interrupts, low level programming and continues data transfer from the PC to the Arduino micro controller.
In order to get a continues audio stream from the PC played on the Arduino one needs on the one hand that a buffer on the Arduino is continues filled with new data. On the other hand the Arduino needs continuously put a signal to the output pins. If one would assume the most simple approach for this:
Fill the buffer, play the buffer, fill the buffer … one would end up in a non continues audio playback. Another problem would be that the audio is not played in the correct speed.

How can these two problems be addressed?

Continues data transfer to the Arduino
One approach to implement a continues data transfer from a PC to the micro controller is
to use a so so called ring buffer. A buffer of a certain size which is structured in a kind of ring form. The ring buffer contains two pointers one which points to the actual position which is filled with new data from the PC. The other pointer points to the position processed by the micro processor. If the last memory block of the ring buffer is reached the pointer is put to the beginning of the buffer and the first position is filled. Already existing data is replaced. In this way a ring structure is created with a certain size, but which provides continues write and read operations and a simple logic.
Continues operation can be archived in the following way:
If the microprocessor is busy it reads from the ring buffer and processes the data. In times when the microprocessor has time new data is read from the PC and written into the ring buffer. The only thing one needs to make sure, is that one does not process more data as one can get from the PC and that one does not overwrite data which has not yet been processed by the micro processor.

RingBuffer

A ring buffer can be written in the following way:

int bytesInBuffer=0;

struct fifo_t {
     char * buf;
     int head;
     int tail;
     int size;
};


char Buffer[1024];
struct fifo_t myFIFO;
  
//This initializes the FIFO structure with the given buffer and size
void fifo_init(struct fifo_t *f, char * buf, int size){
     f->head = 0;
     f->tail = 0;
     f->size = size;
     f->buf = buf;
}


//Read one byte from fifo
int fifo_read(struct fifo_t * f, char &a){

      if( f->tail != f->head ){ //see if any data is available
           a = f->buf[f->tail];  //grab a byte from the buffer
           f->tail++;  //increment the tail
           if( f->tail == f->size ){  //check for wrap-around
                f->tail = 0;
           }
      } else {
           return 0; //number of bytes read 
      }
      return 1;
      
      //returns 0 when no byte was read
      //returns 1 when byte was read
    
}

//This writes up to nbytes bytes to the FIFO
//If the head runs in to the tail, not all bytes are written
//The number of bytes written is returned
int fifo_write(struct fifo_t * f, const char * buf, int nbytes){
     int i;
     const char * p;
     p = buf;
     for(i=0; i head + 1 == f->tail) ||
                ( (f->head + 1 == f->size) && (f->tail == 0) )){
                 return i; //no more room
           } else {
               f->buf[f->head] = *p++;
               f->head++;  //increment the head
               if( f->head == f->size ){  //check for wrap-around
                    f->head = 0;
               }
           }
     }
     return nbytes;
}

The loop looks the flowing:

void loop()
{  
    while (Serial.available () > 0){
      char inByte = Serial.read ();
      fifo_write(&myFIFO,&inByte,1); 
      bytesInBuffer++;
    }
    
    if(bytesInBuffer<200){
      Serial.println("OK");//send Signal for new data
    }
 }

Audio timing and telling the micro processor when to work – Interrupts
So there a two remaining questions left:
1) How does the microprocessor know when to process data and when to receive new data?
2) How does one get the correct timing of the audio playback?

Both can be answered by “Interrupt”. If we assume an audio signal having a sample rate of
8k samples and 8 bits. This would mean we would need to change our output signal with a frequency of 8 kHz. Not too much for the Arduino. All the time between changing the output pins can be used for other operations… for ex. getting new data.
To tell the microprocessor to change the output every 1/8000s one can used a timer interrupt. This function has to be called in the initialization process “setup()”

void Setup_timer2() {
// Timer2 Clock Prescaler to : 1
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Timer2 PWM Mode set to Phase Correct PWM
  cbi (TCCR2A, COM2A0);  // clear Compare Match
  sbi (TCCR2A, COM2A1);

  sbi (TCCR2A, WGM20);  // Mode 1  / Phase Correct PWM
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
}

This sets up a timer interrupt with a frequency of 16000000/510 = 31372.55 Hz.
Which is approx 4 x 8k Hz. The next step is to tell the interrupt controller which code
to run when the interrupt appears:

ISR(TIMER2_OVF_vect) {

  if(i++>=4){ //Do something every 4th interrupt
     //Place code here
  }
}

Since the interrupt is called with ~32 kHz we need to divide by 4 in order to archive the 8 kHz sample rate. The code how to get the data to the pins will be described a bit further down.

So far we are able to process the data in the right time and to get get continuously new data from the PC:

How do we get the Audio signal?

Generating a analogue signal with Arduino
There are two possibilities to get the digital signal analogue. One way is to use the PWM on one pin of the Arduino and modulate it with the audio signal. This is easy and cost only one pin. Important is to have a good filter attached to this pin which filters the frequency of the PWM. After filtering one ends up with the audio signal.
Another possibility is to use a parallel out (several pins of the Arduino) and use a DAC (Digital Analogue Converter) to convert into a analogue signal. Doing this no filtering is needed but can be used to make the audio signal a bit more smooth. For this a DAC is needed, which can be build easily with some resistors.

Both methods can be seen in the code below:

ISR(TIMER2_OVF_vect) {
  if(i++>=4){
    bytesInBuffer--;
    i=0; //4*1600 
    char a;
    fifo_read(&myFIFO, a);
    OCR2A=a;
    
    a=a*(64./256.);
    a=a<<2;
    //Parallel port
    PORTD=a;
  }
}

It should be noted in case of using a DAC with parallel output direct port manipulation is used. Since the Arduino Uno does not have more then 6 continues pins for simplicity
only a 6 bit output signal was generated: So the 8 bit signal is converted to 6 bits by a=a*(64./256.);"

R2r-ladder

DAC

Data transfer from PC to Arduino
The last missing peace is how to transfer the data from the PC to the Arduino.
For this I have written a simple (dirty) program which opens a serial connection to the Arduino and then transfers the data with a baud rate of 115200 to the Arduino. For this the Qt-framework was used. I let the code speak for itself.
It should be noted that the wav file needs to be converted in raw format and with the correct sample rate and bit rate:
The track is needed as mono with a sample to 8000 Hz, RAW (Headerless) Signed 8 bit PCM.
(Conversion can be done with for ex. Audacity)
An example file can be found in the Git repository: File taken from http://www.nch.com.au/acm/8k8bitpcm.wav

Connect the Arduino to a speaker
The code on the Arduino provides two possibilities as described above:

Using the Digital Analog Converter (DAC)
Here pin 2-7 is used.

DACOutput

Using the PWM output, here in principle a filter is required to filter the PWM carrier frequency.
But for simple testing for me a simple 10nF capacitor was enough.
The PWM output comes on pin 11

PWMOutput

Code and Files
The source code for the Arduino and the transfer program can be found on GitHub:
ArduinoSoundcard on GitHub

It could be necessary to change the device file (Linux) of the Arduino or the COM port (Windows).
This can be done in serialmain.cpp
The Audio input file can be changed in audioplayer.cpp

To get the code:

git clone https://github.com/digibird1/ArduinoSoundcard
cd ArduinoSoundcard
qmake
make

One comment on “Arduino as a SoundCard

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

%d bloggers like this: