Thursday, March 27, 2014

More on MIDI2VC

MIDI2VC, ver 2 assembled

I've assembled the MIDI2VC, and discovered two errors (so far) in the layout. First is in the silk screen, the ICSP arrow points at the wrong header pin. Second error is in the MIDI in connector, where pins have been swapped. This was due to an error in the DIN5 symbol.
 I've rewritten the source code to fit the new MCU, and everything seems to work fine now, so I don't expect to find any more errors than the ones listed. I will probably not make another physical board with these fixes, but will fix the errors in the production files for later release, if anyone wants to order boards for this project.
 The assembled board looks like this:


MIDI2VC, ver 2, top side, powered by PICKIT 3
MIDI2VC, ver 2, bottom side

Interrupt efficiency

I've been writing before that the interrupt in the MIDI2VC code, needs to be very fast. Actually, both the ISR, that pushes UART symbols into the UART buffer, and the function that pops values from the buffer, needs to be very fast.
The push function, becuase the interrupt needs to finish in order to receive new symbols. The pop function because UART interrupts needs to be disabled when the function writes to the buffer.
 I've read here, that XC8, free version, bloats the compiled code, to promote the non-free versions. To get around this, I'm learning some assembler to place in the appropriate places in the C code. I'll get back on my progress on that.

Missing features

Features that I still need to add is:
  • Pitch bend support
  • Trigger output
  • MIDI-USB in support
The MIDI-USB support seems a lot more difficult than i thought. I will need to learn some about USB and will need to switch to an MCU with USB support.

Wednesday, March 19, 2014

MIDI2VC, rewritten code and version 2 PCB's

Rewritten Code

I have been working for some time on a bug in the MIDI2VC code. I discovered it when I was testing low key priority using MIDI OX. MIDI OX will only send two simultaneous keys, if more are pressed, the program waits to send keys until enough keys are depressed. When buffered key is sent, it seems to be sent very quickly, and this cause an overrun error in the MIDI2VC. To aid this I realised I needed to move workload from the interrupt routine. The only important thing that the interrupt needs to do is read the UART receive FIFO and pass the information to the main() loop. Handling UART symbols and sending I2C messages needs a low priority compared to receiving UART messages. 
 The new code has only one external variable and one external array, and handles everything in main(). I also cleaned up the code and made it easier to follow. Since the code is massively changed, I'll post the whole code here. In the future I'll only post changed functions.

 //UART to I2C test using PIC12F1822  
 #include <xc.h>  
   
 //Configuration bits  
 #pragma config WDTE=OFF, PWRTE = OFF, MCLRE=OFF, BOREN=OFF, FCMEN=OFF, CLKOUTEN = OFF, IESO=OFF, FOSC=INTOSC, CPD=OFF, LVP = ON, BORV = 0  
   
   
 #define MIDIKeysBucketSize 10  
 #define UARTQueueSize 5  
   
 //UART Global variables  
 volatile int UARTQueue[UARTQueueSize];  
 volatile int UARTQueueLast;  
   
 //Function prototypes  
 void sendKeyToDAC(int, int [], int *);  
 int processUARTSymbol(int, int []);  
 int processUARTBuffer(int []);  
 void lightLED();  
 void unlightLED();  
 void init();  
 int popUARTQueue();  
 void pushMIDIKeysBucket(int, int []);  
 int isLowestInMIDIKeysBucket(int []);  
 void popMIDIKeysBucket(int, int []);  
 int i2cTransmission(int, int *, int []);  
 int MIDIReceiveProcess(int, int [], int, int [], int [], int *);  
   
 void main() {  
   init();  
   
   int MIDIKeysPressed;  
   int MIDIKeysBucket[MIDIKeysBucketSize];  
   int MIDIKey;  
   
   int UARTSym;  
   int i2cData[3];  
   int i2cTransmitCycle = 0;  
   int DACValue = 0;  
   
   int UARTBuffer[4];  
   extern int UARTQueueLast;  
   
   int i2cDataToBeSent = 0;  
   int *i2cDataToBeSentPtr = &i2cDataToBeSent;  
   
   UARTQueueLast = -1;  
   MIDIKeysPressed = 0;  
   UARTBuffer[3] = 0;  
   
   int i;  
   for (i = 0; i < MIDIKeysBucketSize; i++) {  
     MIDIKeysBucket[i] = -1;  
   }  
   
   
   
   while (1) {  
   
     i2cTransmitCycle = i2cTransmission(i2cTransmitCycle, i2cDataToBeSentPtr, i2cData);  
   
     MIDIKeysPressed = MIDIReceiveProcess(i2cTransmitCycle, UARTBuffer, MIDIKeysPressed, MIDIKeysBucket, i2cData, i2cDataToBeSentPtr);  
   
   }  
 }  
   
   
 //***********************************************************  
 //**************Function pushMIDIKeysBucket***************  
 //Function for pushing a MIDI key int into the MIDI bucket.  
 //The MIDI bucket is a data structure that holds a number  
 //of ints. The pushed int is placed in the first available  
 //empty array slot, in no other order. Empty slots are valued  
 //-1, since 0 is a valid key value.  
 //Each value in the bucket (except -1) is unique, if not, there  
 //is something wrong. No error checking for this is implemented.  
 //***********************************************************  
   
 void pushMIDIKeysBucket(int MIDIKey, int MIDIKeysBucket[]) {  
   
   //place MIDI key value in first -1 (empty) instance  
   int i = 0;  
   while (i < MIDIKeysBucketSize) {  
     if (MIDIKeysBucket[i] == -1) {  
       MIDIKeysBucket[i] = MIDIKey;  
       i = MIDIKeysBucketSize;  
     } else {  
       i++;  
     }  
   }  
   
   return;  
 }  
   
 //***********************************************************  
 //**************Function popMIDIKeysBucket***************  
 //Function for removing value from bucket.  
 //***********************************************************  
   
 void popMIDIKeysBucket(int MIDIKey, int MIDIKeysBucket[]) {  
   //Find value in array and replace with -1  
   //If no value is found, do nothing  
   int i = 0;  
   while (i < MIDIKeysBucketSize) {  
     if (MIDIKeysBucket[i] == MIDIKey) {  
       MIDIKeysBucket[i] = -1;  
       i = MIDIKeysBucketSize;  
     } else {  
       i++;  
     }  
   }  
   
   return;  
 }  
   
   
 //***********************************************************  
 //**************Function isLowestInMIDIKeysBucket*********  
 //Returns the lowest value in the bucket  
 //***********************************************************  
   
 int isLowestInMIDIKeysBucket(int MIDIKeysBucket[]) {  
   int lowestKey = 0x7f; //Highest legal MIDI key  
   
   
   //Finds lowest key, that isn't -1, in bucket  
   int i = 0;  
   while (i < MIDIKeysBucketSize) {  
     if ((MIDIKeysBucket[i] < lowestKey) && (MIDIKeysBucket[i] > -1)) {  
       lowestKey = MIDIKeysBucket[i];  
     } else {  
       i++;  
     }  
   }  
   
   return lowestKey;  
 }  
   
 //***********************************************************  
 //**************Function sendKeyToDAC***************  
 //Takes a MIDI-key value and translates it to a DAC value  
 //which populates the i2cData array.  
 //***********************************************************  
   
 void sendKeyToDAC(int MIDIKey, int i2cData[], int *i2cDataToBeSentPtr) {  
   int DACValue;  
   
   DACValue = (MIDIKey - 12)*68 + (8 + MIDIKey) / 17 - 1;  
   DACValue = (((unsigned long long) MIDIKey - 12)*4095 * 833) / 50000;  
   
   i2cData[1] = (DACValue & 0xFF);  
   i2cData[0] = ((DACValue & 0x0F00) >> 8);  
   
   if (MIDIKey == -1) {  
     i2cData[1] = (0 & 0xFF);  
     i2cData[0] = ((0 & 0x0F00) >> 8);  
   }  
   *i2cDataToBeSentPtr = 1;  
   
   return;  
 }  
   
   
   
 //***********************************************************  
 //**************Function processUARTSymbol*******************  
 //Checks to see if received UART symbol is 0x8X or 0x9X, or  
 //a symbol that follows one of those symbols  
 //The function puts 0x8X (or 0x9X) and it's following symbol  
 //in a three symbol buffer. The fourth entry in the buffer  
 //tells the function at which index UART data is to be placed.  
 //When buffer is fully populated it returns a 1 to indicate  
 //the buffer is ready for analysis.  
 //***********************************************************  
   
 int processUARTSymbol(int UARTSymbol, int UARTBuffer[]) {  
   
   //If symbol is MIDI "Note On"(0x9X) or MIDI "Note Off"(0x80),  
   //place symbol and it's following symbols in UART buffer  
   if (((UARTSymbol & 0xF0) == 0x80) || ((UARTSymbol & 0xF0) == 0x90)) {  
     UARTBuffer[3] = 1;  
     UARTBuffer[0] = UARTSymbol;  
     return 0;  
   } else if (UARTBuffer[3] == 1) {  
     UARTBuffer[3] = 2;  
     UARTBuffer[1] = UARTSymbol;  
     return 0;  
   } else if (UARTBuffer[3] == 2) {  
     UARTBuffer[3] = 3;  
     UARTBuffer[2] = UARTSymbol;  
     return 1;  
   }  
   return 0;  
 }  
   
 //***********************************************************  
 //**************Function popUARTQueue*******************  
 //Returns the 0'th value of the UART queue array and  
 //shifts the queue down one index  
 //Returns -1 if queue is empty  
 //***********************************************************  
   
 int popUARTQueue() {  
   int UARTVal;  
   extern int UARTQueueLast;  
   extern int UARTQueue[UARTQueueSize];  
   
   if (UARTQueueLast == -1) {  
     return -1;  
   }  
   
   UARTVal = UARTQueue[0];  
   
   int i = 0;  
   for (i; i < UARTQueueLast; i++) {  
     UARTQueue[i] = UARTQueue[i + 1];  
   }  
   
   UARTQueueLast--;  
   return UARTVal;  
 }  
   
   
 //***********************************************************  
 //**************Function lightLED****************************  
 //Turn on RA0 to light LED  
 //***********************************************************  
   
 void lightLED() {  
   int GPORTA;  
   //Toggle LED  
   //Read latch A into ghost register  
   GPORTA = LATA;  
   //Toggle bit 0  
   GPORTA |= 1;  
   //Write back into port register  
   PORTA = GPORTA;  
   
   return;  
 }  
   
   
 //***********************************************************  
 //**************Function unlightLED**************************  
 //Turn off RA0 to turn off LED  
 //***********************************************************  
   
 void unlightLED() {  
   int GPORTA;  
   //Toggle LED  
   //Read latch A into ghost register  
   GPORTA = LATA;  
   //Toggle bit 0  
   GPORTA &= ~1;  
   //Write back into port register  
   PORTA = GPORTA;  
   
   return;  
 }  
   
 //***********************************************************  
 //**************Function isr****************  
 //Interrupt service routine  
 //***********************************************************  
   
 interrupt void isr(void) {  
   extern int UARTQueueLast;  
   extern int UARTQueue[UARTQueueSize];  
   
   //USART receive interrupt  
   //Adds recieved value to UART Queue  
   if (RCIF) {  
     UARTQueueLast++;  
     UARTQueue[UARTQueueLast] = RCREG;  
   }  
   
   return;  
 }  
   
   
   
 //***********************************************************  
 //**************Function init****************************  
 //PIC12F1822 Register setup  
 //***********************************************************  
   
 void init() {  
   PORTA = 0x00; //Clear RA0 to unlight LED  
   
   //Disable weak pull-up on RA0  
   WPUA = 0b00111110;  
   //I2C baud rate generator set to Fclock = Fosc/(4(SSP1ADD+1)) = 4MHz/(4(2+1))=333kHz  
   SSP1ADD = 0b00000010;  
   
   //SSP1STAT SSP1 STATUS REGISTER  
   //SSP1STAT = 0b0000 0000; //default  
   
   //SSP1CON1 SSP1 CONTROL REGISTER 1  
   //I2C Master mode  
   //Serial port pins SDA and SCL enabled  
   SSP1CON1 = 0b00101000;  
   
   //SSP1CON2 SSP1 CONTROL REGISTER 2  
   //SSP1CON2 = 0b00000000; //default  
   
   //SSP1CON3 SSP1 CONTROL REGISTER 3  
   //SSP1CON3 = 0b00000000; //default  
   
   //Set all I/O's to digital  
   ANSELA = 0x00;  
   
   //ALTERNATE PIN FUNCTION CONTROL REGISTER  
   //Set UART RX/TX to pins RA5/RA4  
   APFCON = 0b10000100;  
   
   //0 Internal oscillator, 3 <fosc> on, 6-4 4MHz  
   OSCCON = 0b01101000;  
   
   //Interrupt controller  
   //6 Peripheral interrupt enabled  
   //7 Global interrupt enabled  
   INTCON = 0b11000000;  
   
   //TRISA  
   //RA1,2,5 set as input, all other IO's set as output  
   TRISA = 0b00100110;  
   
   //Free running bad rate timer is 7  
   SPBRGH = 0x00;  
   SPBRGL = 0x07;  
   
   //TXSTA: TRANSMIT STATUS AND CONTROL REGISTER  
   //8-bit transmission, transmit enable, asynchronous mode, high baud rate selected  
   //Baud rate is FOSC/[16 (n+1)] = 4MHz/(16 (7+1)) = 31250, approx 31500 symbols/sec  
   TXSTA = 0b10100110;  
   
   //RECEIVE STATUS AND CONTROL REGISTER  
   //Serial port enabled, continuous receive enabled  
   RCSTA = 0b10010000;  
   
   //PERIPHERAL INTERRUPT ENABLE REGISTER  
   //USART Receive interrupt enabled  
   //Synchronous Serial Port (MSSP) Interrupt Enable  
   PIE1 = 0b00100000;  
   return;  
 }  
   
   
   
 //***********************************************************  
 //**************Function processUARTBuffer*******************  
 //Interprets UARTBuffer and returns a MIDI key value. If  
 //buffer is a "Note on", the key is returned as is. If the  
 //buffer is "Note off", the key value is added to 128, to indicate  
 //the off state.  
 //***********************************************************  
   
 int processUARTBuffer(int UARTBuffer[]) {  
   
   if (((UARTBuffer[0] & 0xF0) == 0x90) && (UARTBuffer[2] != 0x00)) {  
     return UARTBuffer[1];  
   } else if (((UARTBuffer[0] & 0xF0) == 0x80) || (UARTBuffer[2] == 0x00)) {  
     return UARTBuffer[1] + 128;  
   }  
   
   return -1;  
   
 }  
   
   
 //***********************************************************  
 //**************Function i2cTransmission*******************  
 //Polls flag to see if there is I2C data to be sent and  
 //walks through the transmission process  
 //***********************************************************  
   
 int i2cTransmission(int i2cTransmitCycle, int *i2cDataToBeSentPtr, int i2cData[]) {  
   
   if (i2cTransmitCycle == 0 && *i2cDataToBeSentPtr) {  
     i2cTransmitCycle = 1;  
     SSP1CON2 |= 1; //Sends I2C start condition  
   }  
   //Polls transmit cycle flag and I2C interrupt flag  
   if (i2cTransmitCycle == 1 && (PIR1 & (1 << 3))) {  
     SSP1BUF = (0x60 << 1); //converting 7-bit address to 8-bit write address  
     PIR1 &= ~(1 << 3);  
     i2cTransmitCycle++;  
   }  
   if (i2cTransmitCycle == 2 && (PIR1 & (1 << 3))) {  
     SSP1BUF = i2cData[0];  
     PIR1 &= ~(1 << 3);  
     i2cTransmitCycle++;  
   }  
   if (i2cTransmitCycle == 3 && (PIR1 & (1 << 3))) {  
     SSP1BUF = i2cData[1];  
     PIR1 &= ~(1 << 3);  
     i2cTransmitCycle++;  
   }  
   if (i2cTransmitCycle == 4 && (PIR1 & (1 << 3))) {  
     SSP1CON2 |= (1 << 2); //Sends stop I2C stop condition  
     PIR1 &= ~(1 << 3);  
     i2cTransmitCycle = 0;  
     *i2cDataToBeSentPtr = 0;  
   }  
   return i2cTransmitCycle;  
 }  
   
   
 //***********************************************************  
 //**************Function MIDIReceiveProcess*******************  
 //Checks if there are symbols in the UARTQueue, which is  
 //populated in the interrupt routine. If there are  
 //qualified symbols in the queue, symbols are interperted  
 //and the appropriate value is sent to the DAC.  
 //***********************************************************  
   
 int MIDIReceiveProcess(int i2cTransmitCycle, int UARTBuffer[], int MIDIKeysPressed, int MIDIKeysBucket[], int i2cData[], int *i2cDataToBeSentPtr) {  
   int MIDIKey;  
   int UARTSym;  
   if (i2cTransmitCycle == 0) {  
     UARTSym = popUARTQueue();  
   
     if (UARTSym != -1 && processUARTSymbol(UARTSym, UARTBuffer)) {  
       MIDIKey = processUARTBuffer(UARTBuffer);  
       //If MIDI Key is "Note on"  
       if (MIDIKey < 128) {  
         MIDIKeysPressed++;  
         pushMIDIKeysBucket(MIDIKey, MIDIKeysBucket);  
         lightLED();  
         sendKeyToDAC(isLowestInMIDIKeysBucket(MIDIKeysBucket), i2cData, i2cDataToBeSentPtr);  
       }        //If MIDI Key is "Note off"  
       else if (MIDIKey >= 128) {  
         MIDIKey -= 128;  
         MIDIKeysPressed--;  
         popMIDIKeysBucket(MIDIKey, MIDIKeysBucket);  
         if (MIDIKeysPressed == 0) {  
           sendKeyToDAC(-1, i2cData, i2cDataToBeSentPtr);  
           unlightLED();  
         } else if (MIDIKeysPressed > 0) {  
           lightLED();  
           sendKeyToDAC(isLowestInMIDIKeysBucket(MIDIKeysBucket), i2cData, i2cDataToBeSentPtr);  
         }  
       }  
     }  
   }  
   return MIDIKeysPressed;  
 }  

Version 2 PCB's

The other day I got the new version 2 PCB's. I haven't assembled anything yet, since I had gotten the wrong PIC package. I'm waiting for a couple of PIC's from Farnells, and I'll start assembling as soon I as they get here. 
 Changes between version 1 and 2 PCB are:
  • No OPAMP summing buffer.
  • MCU changed to PIC16F1823, same MCU but more IO pins.
  • One more output for trigger out.
  • Several decoupling caps added.
  • Demo button added.
  • ICSP has unique pins, so that PIC KIT 3 can be used for In Circuit Debugging.
  • DAC gets a 5V reference.
MIDI2VC, version 2 PCB's.