I really enjoyed Michael Smith's "speaker_pcm" but thought I could improve it to allow it to play full-length songs.
This only required minor changes to Michael's code to allow the frequency data to be read from a circular buffer in ram instead of from program memory.
The program on the remote host which supplies the audio data is very simple to write and could easily be done in most any programming language.
The trickiest part was getting through the Arduino's 128-byte buffer and still sustaining 8khz (8,000 audio samples per second) over the serial connection without losing data.
23 August 2010
Adapted largely from Michael Smith's speaker_pcm.
Plays 8-bit PCM audio on pin 11 using pulse-width modulation.
For Arduino with Atmega at 16 MHz.
The audio data needs to be unsigned, 8-bit, 8000 Hz.
Although Smith's speaker_pcm was very well programmed, it
had two major limitations: 1. The size of program memory only
allows ~5 seconds of audio at best. 2. To change the adudio,
the microcontroller would need to be re-programmed with a new
StreamingAudio overcomes these limitations by dynamically
sending audio samples to the Arduino via Serial.
It uses a 1k circular buffer for the audio samples, since
the ATMEGA328 only has 2k of RAM. For chips with less RAM,
the BUFFER_SIZE variable can be reduced.
The only limit on length is the number of samples must fit
into an long integer. (ie: 4,294,967,295 samples).
At 8000 samples / second, that allows 8,947 minutes of audio.
Even this could be overcome if needed. (The only reason to
have the number of samples is to know when to turn the speaker
1. Sends 10 bytes of data representing the number
of samples. Each byte is 1 digit of an unsigned long.
2. Each time the host recieves a byte it sends the next
128 audio samples to fill the Arduino's receive buffer.
#define SAMPLE_RATE 8000
#define BUFFER_SIZE 1024
unsigned long sounddata_length=0;
unsigned char sounddata_data[BUFFER_SIZE];
unsigned long sample=0;
unsigned long BytesReceived=0;
unsigned long Temp=0;
unsigned long NewTemp=0;
int ledPin = 13;
int speakerPin = 11;
int Playing = 0;
//Interrupt Service Routine (ISR)
// This is called at 8000 Hz to load the next sample.
//If not at the end of audio
if (sample < sounddata_length)
//Set the PWM Freq.
OCR2A = sounddata_data[BufferTail];
//If circular buffer is not empty
if (BufferTail != BufferHead)
//Increment Buffer's tail index.
BufferTail = ((BufferTail+1) % BUFFER_SIZE);
//Increment sample number.
else //We are at the end of audio
//Set pin for OUTPUT mode.
// Set up Timer 2 to do pulse width modulation on the speaker
//This plays the music at the frequency of the audio sample.
// Use internal clock (datasheet p.160)
//ASSR = Asynchronous Status Register
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Set fast PWM mode (p.157)
//Timer/Counter Control Register A/B for Timer 2
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A (p.155)
// On the Arduino this is pin 11.
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
//16000000 cycles 1 increment 2000000 increments
//-------- * ---- = -------
// 1 second 8 cycles 1 second
//2000000 increments 1 overflow 7812 overflows
//------- * --- = -----
// 1 second 256 increments 1 second
// Set PWM Freq to the sample at the end of the buffer.
OCR2A = sounddata_data[BufferTail];
// Set up Timer 1 to send a sample every interrupt.
// This will interrupt at the sample rate (8000 hz)
// Set CTC mode (Clear Timer on Compare Match) (p.133)
// Have to set OCR1A *after*, otherwise it gets reset to 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler (p.134)
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A).
// OCR1A is a 16-bit register, so we have to do this with
// interrupts disabled to be safe.
OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
//Timer/Counter Interrupt Mask Register
// Enable interrupt when TCNT1 == OCR1A (p.136)
TIMSK1 |= _BV(OCIE1A);
//Init Sample. Start from the beginning of audio.
sample = 0;
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely.
TCCR1B &= ~_BV(CS10);
// Disable the PWM timer.
TCCR2B &= ~_BV(CS10);
//Use the custom powlong() function because the standard
//pow() function uses floats and has rounding errors.
//This powlong() function does only integer powers.
//Be careful not to use powers that are too large, otherwise
//this function could take a really long time.
long powlong(long x, long y)
//Base case for recursion
//Do recursive call.
//Set LED for OUTPUT mode
//Start Serial port. If your application can handle a
//faster baud rate, that would increase your bandwidth
//115200 only allows for 14,400 Bytes/sec. Audio will
//require 8000 bytes / sec to play at the correct speed.
//This only leaves 44% of the time free for processing
//PC sends audio length as 10-digit ASCII
//While audio length hasn't arrived yet
//Blink the LED on pin 13.
//Init number of audio samples.
//Convert 10 ASCII digits to an unsigned long.
for (int i=0;i<10;i++)
//Convert from ASCII to int
//Shift the digit the correct location.
NewTemp = Temp * powlong(10,9-i);
//Add the current digit to the total.
sounddata_length = sounddata_length + NewTemp;
//Tell the remote PC/device that the Arduino is ready
//to begin receiving samples.
//There's data now, so start playing.
//If audio not started yet...
if (Playing == 0)
//Check to see if the first 1000 bytes are buffered.
if (BufferHead = 1023)
//While the serial port buffer has data
//If the sample buffer isn't full
if (((BufferHead+1) % BUFFER_SIZE) != BufferTail)
//Increment the buffer's head index.
BufferHead = (BufferHead+1) % BUFFER_SIZE;
//Store the sample freq.
sounddata_data[BufferHead] = Serial.read();
//Increment the bytes received
//if the Serial port buffer has room
if ((BytesReceived % 128) == 0)
//Tell the remote PC how much space you have.