As promised, finally, "playback.cpp":
Code:
#include "playback.h"
volatile int current_sample;
int16_t buffer1[MAX_SAMPLES];
int16_t buffer2[MAX_SAMPLES];
volatile boolean using_buffer1;
volatile int buffer1_status; // status of buffer1
volatile int buffer2_status; // status of buffer2
wave_header header;
volatile long sample_num;
// file system object
SdFat sd;
SdFile *file;
void stop_interrupts() {
PIT_TCTRL1 = 0;
}
void fill_buffer() {
// int start_time = millis();
// Want to read a full buffer's worth of data.
int8_t *current_read_point;
if (using_buffer1) {
current_read_point = (int8_t *)buffer2;
} else {
current_read_point = (int8_t *)buffer1;
}
int bytes_to_read = MAX_SAMPLES * 2;
int bytes_read = 0;
int loops = 0;
while (bytes_to_read > 0) {
if (loops > MAX_READ_RETRY) {
Serial.write("Repeated short reads, aborting\n");
Serial.flush();
stop_interrupts();
cli();
return;
} else if (loops > 0) {
Serial.write("Short read, retrying\n");
Serial.flush();
}
bytes_read = file->read(current_read_point, bytes_to_read);
bytes_to_read -= bytes_read;
current_read_point += bytes_read;
loops++;
}
if (using_buffer1) {
buffer2_status = BUFFER_READY;
} else {
buffer1_status = BUFFER_READY;
}
/* int end_time = millis();
if (start_time != end_time) {
Serial.write("Buffer fill took ");
Serial.print(end_time - start_time);
Serial.write(" milliseconds.\n");
Serial.flush();
}
*/
}
extern "C" void pit2_isr(void) {
// Clear the timer interrupt flag
// This prevents this interrupt from being called endlessly.
// (Not technically needed in this sketch, because we're ONLY
// using NVIC_SET_PENDING to call this interrupt--if we were using
// the timer interrupt as an actual timer, we'd need this line.)
PIT_TFLG2 = 1;
// Run "fill_buffer". Since this interrupt has lower priority than
// pit1_isr, playback will continue while the buffer fills.
fill_buffer();
}
extern "C" void pit1_isr(void) {
// Clear the timer interrupt flag
// This prevents this interrupt from being called endlessly.
PIT_TFLG1 = 1;
uint16_t full_sample;
if (using_buffer1) {
if (buffer1_status == BUFFER_READY) {
full_sample = (uint16_t) buffer1[current_sample];
} else {
Serial.write("Buffer underrun\n");
Serial.flush();
return;
}
} else {
if (buffer2_status == BUFFER_READY) {
full_sample = (uint16_t) buffer2[current_sample];
} else {
Serial.write("Buffer underrun\n");
Serial.flush();
return;
}
}
// Write first data bit before setting LATCH_ENABLE pin
if (full_sample & HIGH_BIT) digitalWriteFast(DATA_PIN, HIGH);
else digitalWriteFast(DATA_PIN, LOW);
digitalWriteFast(CLOCK_PIN, HIGH);
digitalWriteFast(LATCH_ENABLE_PIN, HIGH);
digitalWriteFast(CLOCK_PIN, LOW);
full_sample = full_sample << 1;
// Write remaining bits
for (int i = 1; i < 16; ++i) {
if (full_sample & HIGH_BIT) digitalWriteFast(DATA_PIN, HIGH);
else digitalWriteFast(DATA_PIN, LOW);
digitalWriteFast(CLOCK_PIN, HIGH);
full_sample = full_sample << 1;
digitalWriteFast(CLOCK_PIN, LOW);
}
// Set LATCH_ENABLE low
digitalWriteFast(LATCH_ENABLE_PIN, LOW);
current_sample++;
if (current_sample == MAX_SAMPLES) {
current_sample = 0;
using_buffer1 = !using_buffer1;
if (using_buffer1) {
buffer2_status = BUFFER_FILLING;
} else {
buffer1_status = BUFFER_FILLING;
}
// Schedule PIT_CH2 timer to go off soon
// This timer's interrupt will load from the SD--with a lower priority than
// the playback IRQ.
NVIC_SET_PENDING(IRQ_PIT_CH2);
}
}
void timer_setup() {
SIM_SCGC6 |= SIM_SCGC6_PIT; // Activates the clock for PIT
// Turn on PIT
PIT_MCR = 0x00;
// Set the period of the timer. Unit is (1 / 50MHz) = 20ns
// So 44.1 kHz frequency -> 22.68us / 20ns = 1134 periods
PIT_LDVAL1 = SET_LDVAL;
// Enable interrupts on timer1
PIT_TCTRL1 = TIE;
// Set PIT_CH1 interrupt priority
NVIC_SET_PRIORITY(IRQ_PIT_CH1, PIT_CH1_PRIORITY);
// Start the timer
PIT_TCTRL1 |= TEN;
// Enable interrupts on timer2
PIT_TCTRL2 = TIE;
// And set the priority
NVIC_SET_PRIORITY(IRQ_PIT_CH2, PIT_CH2_PRIORITY);
// But don't start the timer!
NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Enable IRQs for interrupts on timer1
NVIC_ENABLE_IRQ(IRQ_PIT_CH2); // Enable IRQs for interrupts on timer2
}
void buffer_setup() {
if (!sd.begin(chipSelect, SPI_SPEED)) sd.initErrorHalt();
Serial.write("Sd Initialized\n");
// open file in current working directory
file = new SdFile(FILENAME, O_READ);
if (!file->isOpen()) sd.errorHalt("open failed");
Serial.write("File opened\n");
Serial.write("Preparing to read header--header length:");
Serial.println(sizeof(header));
if (file->read(&header, HEADER_LENGTH) != HEADER_LENGTH) {
Serial.write("Error reading header--hanging\n");
// Hang forever
file->close();
return;
}
Serial.write("Read header\n");
buffer1_status = BUFFER_FILLING;
buffer2_status = BUFFER_FILLING;
current_sample = 0;
using_buffer1 = true;
fill_buffer();
using_buffer1 = false;
fill_buffer();
using_buffer1 = true;
sample_num = 0;
Serial.write("Read first sample block\n");
}
It's not pretty, and it does all sorts of stupid things like ignore the EOF marker, and use the PIT channel 2 interrupt vector where it should be using the software interrupt vector (IRQ_SOFTWARE and software_isr). The WaveHC Arduino library is a lot better-written example of the same sort of program. But this does demonstrate Teensy 3 interrupts and interrupt priority, as well as interfacing with the PCM56P DAC, so hopefully there's some value there.
Hope this helps!
-Nick