I ended up using two timers to generate the audio output. Timer2 is set to run phase-correct PWM at about 32KHz with output on D3, and timer1 just generates an interrupt 8000 times per second. The ISR loads a byte from the circular buffer and stores it in OCR2A.
But I have a problem trying to determine how well the circular buffer keeps up to speed. My code calculates how full the buffer is, and keeps track of the lowest fill state during the entire file read. But with a 512-byte buffer, I keep getting 255 as the smallest buffer fill. That's a problem because when I switch to a 256-byte buffer, I get 217 as the smallest buffer state, and there's no reason a larger buffer should perform worse than a smaller one. Also, 255 kinda suggests that I've messed up the unsigned subtraction. But I can't find what's wrong.
The code that keeps track of the smallest buffer fill is shown below. "Max" is the buffer size minus 1, so 0x1FF for the 512-byte buffer.
while (file.available()) {
bufState = (head - tail) & Max;
if (bufState < smallestState) smallestState = bufState;
nexthead = (head + 1) & Max;
if (nexthead != tail) {
buf[nexthead] = file.read();
head = nexthead;
}
}
while (head != tail); // wait until buffer emptied by ISR
And here's the whole sketch. Maybe somebody can see where I've gone wrong.
// for 328P
// 10 CS, 11 MOSI, 12 MISO, 13 SCK
// audio out D3, signal LED D6
#include <SPI.h>
#include <SD.h>
File file;
const int signalLED = 6;
const unsigned int bufsize = 512, Max = bufsize - 1; // size must be power of 2
volatile byte buf[bufsize];
volatile unsigned int head, tail, nexthead;
unsigned int i, j, bufState, smallestState;
bool match;
void setup() {
Serial.begin(9600);
digitalWrite(signalLED, LOW);
pinMode(signalLED, OUTPUT);
Serial.println("start");
delay(3000);
if (!SD.begin(10)) exit();
file = SD.open("440wav.wav"); // 8-bit samples, 8000 samples per second, mono
if (!file) exit();
SPCR &= 0xFC; // SPI speed = 4MHz
for (i = 0; i < bufsize; i++) { // fill buffer from file
if(file.available()) {
buf[i] = file.read();
}
else {
i++;
break;
}
}
head = i - 1; // head points to last byte added to buffer from SD
// tail points to last byte fetched by ISR
// if head+1 == tail, the buffer is full
// if head == tail, the buffer is empty
match = false;
for (j = 0; j < (bufsize - 7); j++) {
if ((buf[j] & 0xDF) != 'D') continue;
if ((buf[j+1] & 0xDF) != 'A') continue;
if ((buf[j+2] & 0xDF) != 'T') continue;
if ((buf[j+3] & 0xDF) != 'A') continue;
match = true;
break;
}
if (match) tail = j + 7; // set tail to last byte before audio
else exit();
Serial.println(head);
Serial.println(tail);
Serial.println();
delay(100);
while (file.available()) { // refill buffer
nexthead = (head + 1) & Max;
if (nexthead != tail) {
buf[nexthead] = file.read();
head = nexthead;
}
else break;
}
smallestState = (head - tail) & Max; // print beginning smallestState
Serial.println(smallestState);
Serial.println();
delay(100);
noInterrupts(); // disable all interrupts
TCCR1A = 0; // set up Timer1 on Mode 4 - CTC,
TCCR1B = 0; // at exactly 8000 interrupts/sec
TCNT1 = 0;
TCCR1A = 0; // CTC mode, top = OCR1A, no output
TCCR1B = (1 << CS11) + (1 << WGM12); // CLK/8
OCR1A = 249; // 16mil / 8 / 250 = 8000 interrupts/sec
TIMSK1 = 0; // interrupt disabled for now
TIFR1 = 0x27; // and flags cleared by writing 1
TCCR2A = 0; // set up Timer2 on phase-correct PWM,
TCCR2B = 0; // 31373HKz, output on D3
TCNT2 = 0;
TCCR2A = (1 << COM2B1) + (1 << WGM20); // non inverting / 8Bit PWM
TCCR2B = 1 << CS20; // CLK/1
OCR2B = 128; // 128 is the center ("0") of the waveform
TIMSK2 = 0; // interrupt disabled
TIFR2 = 7; // and flags cleared by writing 1
pinMode (3, OUTPUT); // allow Timer2 to drive D3
interrupts(); // enable all interrupts
TIMSK0 &= 0xFE; // disable timer0 overflow interrupt - millis
TIMSK1 = 1 << OCIE1A; // TC1 compare match Interrupt Enable
// (no interrupts from TC2)
while (file.available()) {
bufState = (head - tail) & Max;
if (bufState < smallestState) smallestState = bufState;
nexthead = (head + 1) & Max;
if (nexthead != tail) {
buf[nexthead] = file.read();
head = nexthead;
}
}
while (head != tail); // wait until buffer emptied by ISR
TIMSK1 = 0; // disable timer1 interrupt
TIMSK0 |= 1; // enable timer0 overflow interrupt - millis
pinMode(3, INPUT); // disconnect timer2 from D3
TCCR2A = 0; // turn off timer2
TCCR2B = 0;
TCCR1A = 0;
TCCR1B = 0;
file.close();
digitalWrite(signalLED,HIGH);
Serial.println(smallestState);
}
void exit() {
digitalWrite(signalLED, HIGH);
while (1);
}
void loop() {
}
//**************************************************************************
// Timer1 interrupt at 8 KHz puts next audio sample into Timer2 OCR2B
//**************************************************************************
ISR(TIMER1_COMPA_vect) {
if (head != tail) { // if buffer not empty, get next byte
tail = (tail + 1) & Max;
OCR2B = buf[tail];
digitalWrite(signalLED,LOW);
}
else digitalWrite(signalLED,HIGH); // LED lights if buffer empty
}