Mostly finished converting to PIC32 registers

Haven't tested on-chip yet, but I think things look correct,
so I will wire it up and begin testing soon.

Eventually this will be merged back into master.
This commit is contained in:
A.M. Rowsell 2020-10-01 16:51:21 -04:00
parent 0a5be6b7fa
commit 3b0424306b
Signed by: amr
GPG Key ID: 0B6E2D8375CF79A9
5 changed files with 721 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
nbproject/
build/
debug/
dist/
Makefile

47
fuses.h Normal file
View File

@ -0,0 +1,47 @@
/*
* File: fuses.h
* Author: amr
*
* Created on September 30, 2020, 1:34 PM
*/
#ifndef FUSES_H
#define FUSES_H
// DEVCFG3
#pragma config USERID = 0xFFFF // Enter Hexadecimal value (Enter Hexadecimal value)
#pragma config PMDL1WAY = OFF // Peripheral Module Disable Configuration (Allow multiple reconfigurations)
#pragma config IOL1WAY = OFF // Peripheral Pin Select Configuration (Allow multiple reconfigurations)
#pragma config FUSBIDIO = ON // USB USID Selection (Controlled by the USB Module)
#pragma config FVBUSONIO = ON // USB VBUS ON Selection (Controlled by USB Module)
// DEVCFG2
#pragma config FPLLIDIV = DIV_2 // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20 // PLL Multiplier (20x Multiplier)
#pragma config UPLLIDIV = DIV_4 // USB PLL Input Divider (4x Divider)
#pragma config UPLLEN = OFF // USB PLL Enable (Disabled and Bypassed)
#pragma config FPLLODIV = DIV_4 // System PLL Output Clock Divider (PLL Divide by 4)
// DEVCFG1
#pragma config FNOSC = FRCPLL // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = ON // Secondary Oscillator Enable (Enabled)
#pragma config IESO = ON // Internal/External Switch Over (Enabled)
#pragma config POSCMOD = OFF // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = OFF // CLKO Output Signal Active on the OSCO Pin (Disabled)
#pragma config FPBDIV = DIV_2 // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/2)
#pragma config FCKSM = CSDCMD // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576 // Watchdog Timer Postscaler (1:1048576)
#pragma config WINDIS = OFF // Watchdog Timer Window Enable (Watchdog Timer is in Non-Window Mode)
#pragma config FWDTEN = OFF // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
#pragma config FWDTWINSZ = WINSZ_25 // Watchdog Timer Window Size (Window Size is 25%)
// DEVCFG0
#pragma config JTAGEN = OFF // JTAG Enable (JTAG Disabled)
#pragma config ICESEL = ICS_PGx1 // ICE/ICD Comm Channel Select (Communicate on PGEC1/PGED1)
#pragma config PWP = OFF // Program Flash Write Protect (Disable)
#pragma config BWP = OFF // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF // Code Protect (Protection Disabled)
#endif /* FUSES_H */

272
i2c.cpp Normal file
View File

@ -0,0 +1,272 @@
/** @file i2c.cpp
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <xc.h>
#include "i2c.h"
/**
* @fn i2c::i2c
* @param speed the requested I2C bus speed
* @param pclk the speed of the peripheral clock
*
* This constructor takes the requested bus speed and the peripheral
* clock speed, and calculates the appropriate baud rate divisor for
* I2C2BRG. Then it enables the I2C module.
*/
i2c::i2c(uint32_t speed, uint32_t pclk) {
double baud = (((1.0 / (2.0 * (double) speed)) - 104E-9) * (double) pclk) - 2.0;
int i_baud = (int) (baud + 0.5); // cast/round/truncate/whatever
I2C2BRG = i_baud; // set baud rate
I2C2CONbits.ON = 1; // enable I2C module
//I2C2CONbits.I2CEN = 1;
}
/**
* @fn i2c::transact
* @brief Function for all I2C transactions
* @param addr the 7-bit peripheral address
* @param send buffer for data to be sent, can be NULL
* @param recv buffer for data to be received, can be NULL
* @param size number of bytes to send to received
* @param mode which transactMode to use
* @param memAddr optional memory address, only used when communicating with
* EEPROMs, can be NULL if not needed. This is a pointer to the variable
* holding the memory address. This is done so that it can be NULL
* when not needed.
* @return a status code, not fully implemented
*
* Note that the address needs to be sent in 7-bit mode
* Note also that null pointers can be used when a mode
* doesn't require use of one of the pointers
* Mode options:
* 0: send single byte
* 1: send multiple bytes
* 2: receive single byte
* 3: receive multiple bytes
* 4: test mode
*
* Status byte codes:
* 1: NACK on address
* 2: NACK on data
*/
uint8_t i2c::transact(uint8_t addr, uint8_t *send, uint8_t *recv, \
uint8_t size, transactMode mode, uint16_t *memAddr) {
uint8_t fullAddress, status = 0;
uint8_t loopVar;
const int max = 255;
const int prime = 317;
switch (mode) {
case SINGLE_SEND: // Single send
fullAddress = addr << 1; // we are writing, thus R/W = 0
while (I2C2STATbits.TRSTAT); // wait for any transmission to finish
I2C2CONbits.SEN = 1; // send a start
while (I2C2CONbits.SEN); // wait for start to complete
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 1; // NACK on address
}
if(memAddr != NULL) {
// Send memory address
I2C2TRN = (uint8_t)(*memAddr >> 8);
while (I2C2STATbits.TRSTAT); // wait for transmission
I2C2TRN = (uint8_t)(*memAddr & 0x00FF);
while (I2C2STATbits.TRSTAT); // wait for transmission
}
if(send != NULL) {
// Now send the actual data
I2C2TRN = *send; // load buffer with data
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 2; // NACK on data
}
} else {
status |= NULL_ERROR;
}
I2C2CONbits.PEN = 1; // send STOP
while (I2C2CONbits.PEN); // wait for stop to complete
break;
case MULTIPLE_SEND: // Multiple send
fullAddress = addr << 1; // we are writing, thus R/W = 0
while (I2C2STATbits.TRSTAT); // wait for any transmission to finish
I2C2CONbits.SEN = 1; // send a start
while (I2C2CONbits.SEN); // wait for start to complete
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 1; // NACK on address
}
if(memAddr != NULL) {
// Send memory address
I2C2TRN = (uint8_t)(*memAddr >> 8);
while (I2C2STATbits.TRSTAT); // wait for transmission
I2C2TRN = (uint8_t)(*memAddr & 0x00FF);
while (I2C2STATbits.TRSTAT); // wait for transmission
}
if(send != NULL) { // guard against NULL pointer
// Now send the actual data
for (loopVar = size; loopVar > 0; loopVar--) { // do until size hits zero
I2C2TRN = *send; // load buffer with (next) data
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 2; // NACK on data
}
send++; // good old pointer increment... hope it works!
}
} else {
status |= NULL_ERROR;
}
I2C2CONbits.PEN = 1; // send STOP
while (I2C2CONbits.PEN); // wait for stop to complete
break;
case SINGLE_RECV: // Single receive
if(memAddr != NULL || send != NULL) {
fullAddress = addr << 1; // we are writing, thus R/W = 0
while (I2C2STATbits.TRSTAT); // wait for any transmission to finish
I2C2CONbits.SEN = 1; // send a start
while (I2C2CONbits.SEN); // wait for start to complete
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 1; // NACK on address
}
}
if(memAddr != NULL) {
// Send memory address
I2C2TRN = (uint8_t)(*memAddr >> 8);
while (I2C2STATbits.TRSTAT); // wait for transmission
I2C2TRN = (uint8_t)(*memAddr & 0x00FF);
while (I2C2STATbits.TRSTAT); // wait for transmission
}
// Now send the actual data
if(send != NULL) {
I2C2TRN = *send; // load buffer with data
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= DATA_NACK; // NACK on data
}
}
if(recv != NULL) { // guard against NULL pointers
// RECEIVE SECTION
I2C2CONbits.RSEN = 1; // send restart
while (I2C2CONbits.RSEN); // wait for restart
fullAddress = (addr << 1) | 0x01; // address is now for reading
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= ADDR_NACK; // NACK on address
}
I2C2CONbits.RCEN = 1; // enable receiever
while (!I2C2STATbits.RBF); // wait for 8 bits to be receieved
*recv = I2C2RCV;
I2C2CONbits.ACKDT = 1; // change to NACK
I2C2CONbits.ACKEN = 1; // send ACKDT
while (I2C2CONbits.ACKEN); // wait for NACK to send
} else {
status |= NULL_ERROR;
}
I2C2CONbits.PEN = 1; // send STOP
while (I2C2CONbits.PEN); // wait for stop to complete
break;
case MULTIPLE_RECV: // Multiple receive
if(memAddr != NULL || send != NULL) { // only send the write address if needed
fullAddress = addr << 1; // we are writing, thus R/W = 0
while (I2C2STATbits.TRSTAT); // wait for any transmission to finish
I2C2CONbits.SEN = 1; // send a start
while (I2C2CONbits.SEN); // wait for start to complete
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 1; // NACK on address
}
}
if(memAddr != NULL) {
// Send memory address
I2C2TRN = (uint8_t)(*memAddr >> 8);
while (I2C2STATbits.TRSTAT); // wait for transmission
I2C2TRN = (uint8_t)(*memAddr & 0x00FF);
while (I2C2STATbits.TRSTAT); // wait for transmission
}
if(send != NULL) {
// Now send the actual data
I2C2TRN = *send; // load buffer with data
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 2; // NACK on data
}
}
if(recv != NULL) {
// RECEIVE SECTION
I2C2CONbits.RSEN = 1; // send restart
while (I2C2CONbits.RSEN); // wait for restart
fullAddress = (addr << 1) | 0x01; // address is now for reading
I2C2TRN = fullAddress; // send address
while (I2C2STATbits.TRSTAT); // wait for transmission
if (I2C2STATbits.ACKSTAT == 0) {
// acknowledge received
} else {
status |= 1; // NACK on address
}
for (loopVar = size; loopVar > 0; loopVar--) {
I2C2CONbits.RCEN = 1; // enable receiever
while (!I2C2STATbits.RBF); // wait for 8 bits to be receieved
*recv = I2C2RCV;
recv++;
if (loopVar > 1) {
I2C2CONbits.ACKDT = 0; // ACK
I2C2CONbits.ACKEN = 1;
while (I2C2CONbits.ACKEN); // wait for ACK
} else {
I2C2CONbits.ACKDT = 1; // change to NACK
I2C2CONbits.ACKEN = 1; // send ACKDT
while (I2C2CONbits.ACKEN); // wait for NACK to send
}
}
} else {
status |= NULL_ERROR;
}
I2C2CONbits.PEN = 1; // send STOP
while (I2C2CONbits.PEN); // wait for stop to complete
break;
case TEST_MODE: // infinite test mode
do {
for (int i = 0; i < max; ++i) {
uint8_t c = (int) (i * prime) % max;
I2C2CONbits.SEN = 1;
while (I2C2CONbits.SEN);
I2C2TRN = c;
while (I2C2STATbits.TBF); // wait for transmission
I2C2CONbits.PEN = 1;
while (I2C2CONbits.PEN);
}
} while (size--);
break;
default:
status |= 0x04;
break;
}
return status;
}

51
i2c.h Normal file
View File

@ -0,0 +1,51 @@
/** @file i2c.h
* File: myI2C.h
* Author: Alexander Rowsell
*
* Created on May 3, 2015, 1:06 AM
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef MYI2C_H
#define MYI2C_H
#include <xc.h>
enum transactMode {
SINGLE_SEND = 0,
MULTIPLE_SEND = 1,
SINGLE_RECV = 2,
MULTIPLE_RECV = 3,
TEST_MODE = 4
};
enum statusCodes {
SUCCESS = 0,
ADDR_NACK = 1,
DATA_NACK = 2,
NULL_ERROR = 127
};
/**
* @class i2c
* @brief The i2c class, controls all i2c comms
*
* This class is an abstraction of the I2C peripheral
* inside the PIC32MX1xx/2xx family of devices.
* Simply create an instance of this class, and then
* use the transact() function to send and receive
* data over the I2C bus. Colours are excellent
*/
class i2c {
public:
i2c(uint32_t speed, uint32_t pclk);
uint8_t transact(uint8_t addr, uint8_t *send, \
uint8_t *recv, uint8_t size, transactMode mode, \
uint16_t *memAddr = NULL);
};
#endif

346
main.cpp Normal file
View File

@ -0,0 +1,346 @@
/*
* File: main.c
* Author: amr
*
* Created on September 26, 2020, 3:32 AM
*/
#include "fuses.h"
#include <xc.h>
#include <sys/attribs.h>
#include <stdint.h>
#include <stdlib.h>
#include "i2c.h"
#define FIFO_SIZE 64
#define STATE_IDLE 0
#define STATE_PLAYING 1
#define STATE_PROGRAMMING 2
#define FLASH_OKAY 0
#define FLASH_BUSY 1
#define FLASH_PROG_ERROR 2
#define FLASH_ERASE_ERROR 3
volatile uint8_t globalState = STATE_IDLE;
// FIFO variables
int readIndex = 0;
int writeIndex = 0;
int usedSize = 0;
uint8_t *fifoBuffer;
// Flash data variables
volatile uint32_t flashAddress = 0;
volatile uint32_t byteCount = 0;
volatile uint32_t readByteCount = 0;
volatile uint8_t curByte;
volatile uint8_t currentClip = 0;
// Audio clip address table
const uint32_t audioAddr[] = { 0x00000000, 0x0001F5C2 };
const uint32_t audioLen[] = { 0x0001F5C2, 0x000144FA };
void createFIFO(void) {
fifoBuffer = (uint8_t *)malloc(sizeof(uint8_t) * FIFO_SIZE); // create a 64-byte buffer
readIndex = 0;
writeIndex = 0;
usedSize = 0;
}
uint8_t readFromFIFO(void) {
uint8_t retValue = 0;
if(usedSize == 0) {
return 0;
}
retValue = fifoBuffer[readIndex];
readIndex++;
if(readIndex == FIFO_SIZE) {
readIndex = 0;
}
usedSize--;
return retValue;
}
uint8_t writeToFIFO(uint8_t item) {
if(usedSize >= FIFO_SIZE) {
return 0;
}
fifoBuffer[writeIndex] = item;
writeIndex++;
if(writeIndex == FIFO_SIZE) {
writeIndex = 0;
}
usedSize++;
return 1;
}
void appData(uint8_t c) {
U1TXREG = c;
while(!U1STAbits.TRMT); // wait for transmit
return;
}
// begin flash section
void enableFlashWrite(void) {
// enable writing
PORTACLR = 0x10; // set CS low
while(SPI1STAT & 0x02); // wait for buffer to be empty
SPI1BUF = 0x06; // send WREN
PORTASET = 0x10; // set CS high
return;
}
uint8_t readStatusRegister(void) {
uint8_t statusReg = 0;
PORTACLR = 0x10; // set CS low
while(SPI1STAT & 0x02); // wait for buffer to be empty
SPI1BUF = 0x05;
while(SPI1STAT & 0x02);
SPI1BUF = 0xFF;
while(!(SPI1STAT & 0x01));
statusReg = SPI1BUF;
PORTASET = 0x10;
if(statusReg & 0x01) {
return FLASH_BUSY;
}
else if(statusReg & 0x20) {
return FLASH_ERASE_ERROR;
}
else if(statusReg & 0x40) {
return FLASH_PROG_ERROR;
}
else {
return FLASH_OKAY;
}
}
void sectorErase(uint8_t sector) {
const uint8_t sectorTable[] = { 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x20 };
sector = sectorTable[sector]; // convert sector number to address prefix
// erase sectors
PORTACLR = 0x10; // set CS low
while(SPI1STAT & 0x02); // wait for buffer to be empty
SPI1BUF = 0xD8; // send sector erase
while(SPI1STAT & 0x02);
SPI1BUF = sector;
while(SPI1STAT & 0x02);
SPI1BUF = 0x00;
while(SPI1STAT & 0x02);
SPI1BUF = 0x00;
while(SPI1STAT & 0x02);
PORTASET = 0x10; // set CS high
// we need to wait for the erase
while(readStatusRegister() != FLASH_OKAY) ;
// this might end up crashing if there's an error, but... whatever
}
uint8_t readFromFlash(uint32_t addr, uint32_t length) {
PORTACLR = 0x10; // set CS low
while(SPI1STAT & 0x02); // wait for buffer to be empty
SPI1BUF = 0x03;
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr >> 16) & 0x000000FF);
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr >> 8) & 0x000000FF);
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr) & 0x000000FF);
while(SPI1STAT & 0x02);
for(uint32_t i = 0; i < length; i++) {
SPI1BUF = 0xFF;
while(!(SPI1STAT & 0x01));
if(writeToFIFO(SPI1BUF) == 0) {
return 0;
}
}
PORTASET = 0x10; // set CS high
return length;
}
uint8_t writeToFlash(uint32_t addr, uint32_t length, uint8_t *data) {
enableFlashWrite();
// send address and data
PORTACLR = 0x10; // set CS low
while(SPI1STAT & 0x02); // wait for buffer to be empty
SPI1BUF = 0x02;
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr >> 16) & 0x000000FF);
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr >> 8) & 0x000000FF);
while(SPI1STAT & 0x02);
SPI1BUF = (uint8_t)((addr) & 0x000000FF);
for(uint32_t i = 0; i < length; i++) {
while(SPI1STAT & 0x02);
SPI1BUF = *(data + i);
}
PORTASET = 0x10; // set CS high
while(readStatusRegister() != FLASH_OKAY) ; // wait for programming
return 0;
}
uint8_t programFlash(void) {
const uint8_t prompt[39] = "Please send the data to be programmed\n";
const uint8_t readySend = 0x33;
uint8_t *progBuffer = (uint8_t *)malloc(sizeof(uint8_t) * 64); // 64-byte buffer
uint32_t address = 0;
uint8_t length = 0;
// enableFlashWrite();
//
// sectorErase(0);
// sectorErase(1);
// sectorErase(2);
for(uint8_t k = 0; k < 38; k++) {
appData(prompt[k]); // send out the prompt
}
while(1) {
length = 0;
appData(readySend);
do {
while(!(U1STAbits.URXDA)) ; // wait for a character
*(progBuffer + length) = U1RXREG;
} while(++length < 64);
}
}
void _delay(uint32_t cycles) {
cycles += (uint32_t)((double)cycles * 1.4);
while(cycles--);
return;
}
uint8_t initSystem(void) {
/* set up GPIO */
SYSKEY = 0x0;
SYSKEY = 0xAA996655;
SYSKEY = 0x556699AA;
CFGCON &= ~(1<<13); // unlock PPS
// Pin configs
/* SPI:
* SO = RB5
* SI = RC8
*
* I2C:
* SDA: RB2
* SCL: RB3
*
* UART:
* TX = RB4
*/
SDI1R = 0b0110; // RC8
RPB5R = 0b0011; // SD01
RPB4R = 0b0001; // U1TX
SYSKEY = 0x12345678; // lock SYSKEY
// set up button & LED
TRISCSET = 0x20; // PC5 is button input
TRISCCLR = 0x01; // PC0 is LED
PORTCCLR = 0x01; // turn LED off at boot
CNENCSET = 0x20; // enable on PC0?
CNPUCSET = 0x20; // pullup enable
CNCONCSET = 0x8000;
IFS1CLR = 0x8000;
IPC8SET = 0xC0000; // priority 3
IEC1SET = 0x8000; // enable pin change interrupt port C
/* Set up SPI1 */
SPI1BRG = 1; // 2.5MHz
SPI1CONbits.ENHBUF = 1; // enable enhanced buffer
SPI1CONbits.MSTEN = 1; // master mode
/* Set Up I2C1 */
i2c dac(400e3, 10e6);
/* set up UART */
U1BRG = 31; // 19200 baud
U1STAbits.UTXEN = 1; // enable transmitter
U1MODEbits.ON = 1; // enable UART
// set up timer (needs to be about 8kHz)
T2CON = 0x2000; // timer continues in idle mode
TMR2 = 0x00;
PR2 = 0x4E2; // 1250 = 10e6/8e3
IFS0CLR = 0x0100;
IPC2SET = 0x1C; // priority 7
IEC0SET = 0x0100; // enable timer interrupt
INTCONSET = _INTCON_MVEC_MASK;
return 0;
}
int main(void) {
initSystem();
// this needs to be as early as possible, but after SPI and UART setup
if(LATC & 0x20) {
// if button is held down at boot, it's programming mode
globalState = STATE_PROGRAMMING;
programFlash();
}
createFIFO();
//T2CONSET = 0x8000; // enable timer
// main state machine loop
while (1) {
__builtin_enable_interrupts();
asm("wait"); // go to sleep
if(globalState == STATE_PLAYING) {
if(usedSize < 16) {
// read 48 bytes into buffer
readFromFlash(audioAddr[currentClip] + readByteCount, 48);
readByteCount += 48;
}
curByte = readFromFIFO();
byteCount++;
if(byteCount >= audioLen[currentClip]) {
byteCount = 0;
readByteCount = 0;
globalState = STATE_IDLE;
}
__builtin_enable_interrupts();
T2CONSET = 0x8000; // enable timer
asm("wait");
}
else if(globalState == STATE_IDLE) {
__builtin_enable_interrupts();
asm("wait");
}
}
}
extern "C" {
void __ISR(_TIMER_2_VECTOR, IPL7AUTO) Timer2Handler(void) {
__builtin_disable_interrupts();
// load the next byte into the buffer
asm("eret"); // return from interrupt
}
void __ISR(_CHANGE_NOTICE_VECTOR, IPL3AUTO) PinChangeHandler(void) {
// // button is pushed
// if(globalState == STATE_PLAYING) {
// return; // do nothing, we're already playing
// }
// // pick a random number
// currentClip += 1;
// currentClip = currentClip % 8; // roll back around
// globalState = STATE_PLAYING;
programFlash();
asm("eret"); // return from interrupt
}
} // end extern C