I want to stream 8-bit 8kHz PCM data to an Arduino board using the serial port. It almost works, but whenever I reset the Arduino and start the Processing app, the appropriate sample gets sent over to the Arduino, and it strangely loops for a couple of seconds. Here is my Arduino code:
#define SAMPLE_RATE 8000
#define BUFFER_SIZE 1024
unsigned long sounddata_length=0;
unsigned char sounddata_data[BUFFER_SIZE];
int BufferHead=0;
int BufferTail=0;
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.
ISR(TIMER1_COMPA_vect)
{
//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.
sample++;
}//End if
}//End if
else //We are at the end of audio
{
//Stop playing.
stopPlayback();
}//End Else
}//End Interrupt
void startPlayback()
{
//---------------TIMER 2-------------------------------------
// Set up Timer 2 to do pulse width modulation on the speaker
// pin.
//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
//Continued...
//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];
//--------TIMER 1----------------------------------
// Set up Timer 1 to send a sample every interrupt.
// This will interrupt at the sample rate (8000 hz)
//
cli();
// 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;
//Enable Interrupts
sei();
}//End StartPlayback
void stopPlayback()
{
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely.
TCCR1B &= ~_BV(CS10);
// Disable the PWM timer.
TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPin, LOW);
}//End StopPlayback
//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
if (y==0)
{
return(1);
}//End if
else
{
//Do recursive call.
return(powlong(x,y-1)*x);
}//End Else
}
void setup()
{
//Set pin for OUTPUT mode.
pinMode(speakerPin, OUTPUT);
//Set LED for OUTPUT mode
pinMode(ledPin, OUTPUT);
//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
//bytes.
Serial.begin(115200);
Serial.print("G");
//PC sends audio length as 10-digit ASCII
//While audio length hasn't arrived yet
while (Serial.available()<10)
{
//Blink the LED on pin 13.
digitalWrite(ledPin,!digitalRead(ledPin));
delay(100);
}
//Init number of audio samples.
sounddata_length=0;
//Convert 10 ASCII digits to an unsigned long.
for (int i=0;i<10;i++)
{
//Convert from ASCII to int
Temp=Serial.read()-'0';
//Shift the digit the correct location.
NewTemp = Temp * powlong(10,9-i);
//Add the current digit to the total.
sounddata_length = sounddata_length + NewTemp;
}//End for
//Tell the remote PC/device that the Arduino is ready
//to begin receiving samples.
Serial.println('S');
//There's data now, so start playing.
//startPlayback();
Playing = 0;
}//End Setup
void loop()
{
//If audio not started yet...
if (Playing == 0)
{
//Check to see if the first 1000 bytes are buffered.
if (BufferHead == 1023)
{
Playing=1;
startPlayback();
}//End if
}//End if
//While the serial port buffer has data
while (Serial.available()>0)
{
//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
BytesReceived++;
}//End if
//if the Serial port buffer has room
if ((BytesReceived % 128) == 0)
{
//Tell the remote PC how much space you have.
Serial.println('S');
}//End if
}//End While
}//End Loop
and here is my processing code:
import processing.serial.*;
import java.util.*;
Serial myPort; // Create object from Serial class
char val; // Data received from the serial port
int index = 0;
byte[] sound;
void setup()
{
size(200, 200);
// I know that the first port in the serial list on my mac
// is always my FTDI adaptor, so I open Serial.list()[0].
// On Windows machines, this generally opens COM1.
// Open whatever port is the one you're using.
myPort = new Serial(this, "COM6", 115200);
background(255); // Set background to white
String[] loadedSfx = loadStrings("sfx.txt");
sound = byte(int(loadedSfx[1].split(", "))); //extremely ugly, cannot cast string to char/byte :(
while ( myPort.available() > 0/* || myPort.readChar() != 'G'*/); // wait for G command
println("GO");
myPort.write(nf(sound.length, 10)); //send num of samples as decimal num
}
void draw()
{
if ( myPort.available() > 0) { // If data is available,
val = myPort.readChar(); // read it and store it in val
}
if (val == 'S') { // If the serial value is S,
myPort.write(subset(sound, index, index+128)); //send sound data
index += 128;
//i measured that this gets executed every 216-228 ms and that it's probably related to the problem
val = 0;
} else print(val);
}
void keyPressed()
{
myPort.write(subset(sound, index, index+128)); //send sound data
index += 128;
}
Why does it loop like this? How can I fix it?