Monday, November 24, 2014

PIC24FJ64GB004, first successful USB data transfer

I just got my first successful data transfer through to my PIC.The first transfer in a enumeration process is the "get device descriptor" transfer, which consists of three transfers, setup, in and out transfer. Here is the UART output of the transfer:

***************************

Welcome to MIDI2VC+
Version 0.15
Build date Nov 24 2014

***************************
0x01bf> USB device attached.
0x01bf> Waiting for device power stabilization
0x01ca> Device power stabilized
0x01ca> Full speed device found
0x01ca> Device reset initiation
0x01d3> Device reset complete
0x01d3> Setup token sent
0x01d3> Setup token transfer complete
0x01d3> Handshake was: 0002
0x01d3> In token sent
0x01d3> In token transfer complete
0x01d3> Handshake was: 000b

***************************
Selected register dump:
***************************
0x01d3> U1CON   = 0x0089
0x01d3> U1ADDR  = 0x0000
0x01d3> U1TOK   = 0x0090
0x01d3> U1BDTP1 = 0x0012
0x01d3> U1EP0   = 0x000d
0x01d3> U1CNFG1 = 0x0000
0x01d3> BDT[0]  = 0x6c12
0x01d3> BDT[1]  = 0x0800
0x01d3> BDT[2]  = 0x0808
0x01d3> BDT[3]  = 0x0840
0x01d3> &BDT[0] = 0x1200
0x01d3> &ep0TxBuffer[0] = 0x0840
0x01d3> ep0TxBuffer[0] = 0x0680
0x01d3> &ep0RxBuffer[0] = 0x0800
0x01d3> ep0RxBuffer[0] = 0x0112
0x01d3> U1IR = 0x0044
0x01d3> U1STAT = 0x0000
***************************
***************************


***************************
ep0RxBuffer dump:
***************************
0x01d3> ep0RxBuffer = 0x0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100 0201 0150 0121 0944 4000 0000 0110 0112 
***************************
***************************

0x01d3> Out token sent
0x01d3> Out token transfer complete
0x01d3> Handshake was: 000e

Now, everything is not great here. The last transfer has an 0xe handshake, which is a stall handshake. This can't be right since I have disabled stalling. The second thing that is fishy is the content of the RxBuffer. I analyzed the USB data transfer of my MicroKey keyboard, by using  Device Monitoring Studio (demo version). Here I can see that the transferred data should actually be:

12 01 10 01 00 00 00 40 44 09 21 01 50 01 01 02 00 01

As you can see, bytes are in the wrong order. I'll look into these issues next...
I'm really happy for this road mark, it means that I get the hardware and it's working! 

Thursday, November 13, 2014

PIC24FJ64GB004, enumeration process

Last post I wrote about the first steps of the enumeration process on the USB host, finding an attached device. The whole process includes quite a number of steps, which are not that easy to wrap your head around.
Firstly, I hadn't configured the clocks properly. The USB host mode is quite picky about the clocks, so getting it right is essential. One problem was that I haven't designed in a crystal as oscillator. The data sheet says the internal RC-clock isn't precise enough. This also means that even if everything actually seems to be working with the RC-clock, the instructions don't address this configuration. The setup is available in the source code.
When I troubleshooted the enumeration process, I made a pseudocode version of it, and I thought it would be suitable to post that here. I'm getting as far as to get a transfer complete-interrupt, which probably is a good step on the way to get through the whole enumeration. So here it is:

 [setup]  
   
 [global variables]  
 static uint16_t __attribute__((aligned(512))) BDT[4];  
 volatile uint16_t ep0RxBuffer[32];  
 volatile uint16_t ep0TxBuffer[32];  
   
 [in main()]  
 //USB1 host mode setup  
 U1CONbits.HOSTEN = 1; //enable host mode  
 U1OTGCONbits.DPPULDWN = 1; //enable pulldowns on lanes  
 U1OTGCONbits.DMPULDWN = 1;  
 U1OTGCONbits.DPPULUP = 0;  
 U1OTGCONbits.DMPULUP = 0;  
   
 U1IRbits.ATTACHIF = 0; //setup and enable attach interrupt  
 U1IEbits.ATTACHIE = 1;  
 IEC5bits.USB1IE = 1;  
   
 U1EP0 = 0x4d; //EP0 Setup for bidirectional control transfers  
 U1CNFG1 = 0x00; // PING-PONG disabled  
   
 U1BDTP1 = (uint16_t) (&BDT) >> 8; //set BDT upper byte  
 BDT[1] = (uint16_t) & ep0RxBuffer; //point first BDT entry, second byte, to RX buffer adress  
 BDT[3] = (uint16_t) & ep0TxBuffer;//point second BDT entry, second byte, to RX buffer adress  
   
   
   
 [enumeration]  
 after attach interrupt is thrown:  
 wait 100ms  
   
 if device is low speed:  
           U1ADDRbits.LSPDEN = 1;//module operates at low speed  
           U1EP0bits.LSPD = 1;  
 else:  
           U1ADDRbits.LSPDEN = 0; //module operates at full speed  
           U1EP0bits.LSPD = 0;  
   
   
 U1CONbits.USBRST = 1; //reset device  
 U1CONbits.SOFEN = 1; //enable start of frame generation  
   
 wait 60ms  
   
 U1CONbits.USBRST = 0;//release reset  
   
 wait 10ms  
   
 //send GET_DEVICE_DESCRIPTOR  
 ep0TxBuffer[0] = 0b0000 0110 1000 0000; //GET_DESCRIPTOR  
 ep0TxBuffer[1] = 0x01; //DEVICE  
 ep0TxBuffer[2] = 0x00; //NO LANGUAGE ID  
 ep0TxBuffer[3] = 0x40; //DESCRIPTOR LENGTH  
 U1ADDR = 0x00; //device adress, 0 until enumeration is finsished  
 BDT[2] = 0x8008; //Byte count=8 and BD1STAT handed over to USB module  
 U1TOK = 0xd0; //send setup token  
   

UART output functions

Also, I have done quite a bit of work in functions to output important data on the UART. This way I can program the PIC with the PICKIT 2 tool and use the same tool for reading the UART output. Even more useful than the debug tool available when using the PICKIT 3. Here are the functions:

 //*********************************************************  
 //*******Function timeStamp**********************  
 //Returns a string that begins with the hex value of the  
 //10ms system tick followed by bracket and space: "0xabcd> "  
   
 char* timeStamp() {  
   extern volatile uint16_t ms10Tick;  
   static char hexChars[9];  
   char* tempChars;  
   uint16_t i;  
   
   hexChars[0] = '0';  
   hexChars[1] = 'x';  
   hexChars[6] = '>';  
   hexChars[7] = 0x20; //space  
   hexChars[8] = 0;  
   
   
   tempChars = uint16_t2String(ms10Tick);  
   for (i = 0; i < 4; i++) {  
     hexChars[i + 2] = tempChars[i];  
   }  
   
   return hexChars;  
 }  
   
   
 //*********************************************************  
 //*******Function string2UARTTXQueue**********************  
 //Takes a c-string and sends it to the UART transmit Queue  
   
 void string2UARTTXQueue(char str[]) {  
   
   uint16_t i = 0;  
   while (str[i] != 0) {  
   
     pushUARTTXQueue(str[i]);  
     i++;  
   }  
   return;  
 }  
   
   
   
 //*********************************************************  
 //*******Function popUARTTXQueue**********************  
 //Returns next value in UART transmit Queue and  
 //sets queue values apropriately  
   
 char popUARTTXQueue() {  
   char UARTVal;  
   extern volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength;  
   extern volatile char UARTTXQueue[UART_TXQueue_SIZE];  
   
   if (UARTTXQueueLength == 0) {  
     return 0;  
   }  
   
   
   UARTTXQueueTail = (UARTTXQueueTail + 1) & (UART_TXQueue_SIZE - 1);  
   UARTVal = UARTTXQueue[UARTTXQueueTail];  
   UARTTXQueueLength--;  
   
   return UARTVal;  
 }  
   
   
 //*********************************************************  
 //*******Function pushUARTTXQueue**********************  
 //Puts a char to the UART transmit Queue and  
 //sets queue values apropriately  
   
 void pushUARTTXQueue(char UARTVal) {  
   extern volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength;  
   extern volatile char UARTTXQueue[UART_TXQueue_SIZE];  
   
   if (UARTTXQueueLength == (UART_TXQueue_SIZE - 1)) {  
     return;  
   }  
   
   UARTTXQueueHead = (UARTTXQueueHead + 1) & (UART_TXQueue_SIZE - 1);  
   UARTTXQueue[UARTTXQueueHead] = UARTVal;  
   UARTTXQueueLength++;  
   
   return;  
 }  
   
 //*********************************************************  
 //*******Function uint16_t2String**********************  
 //converts a uint16_t to a string in hex format without 0x  
   
 char* uint16_t2String(uint16_t buffer) {  
   static char hexChars[5];  
   const uint16_t asciiTable[16] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66};  
   uint16_t i;  
   for (i = 4; i > 0; i--) {  
     hexChars[i - 1] = (char) asciiTable[((0xf000 >> 4 * (i - 1)) & buffer) >> 4 * (4 - i)];  
   }  
   hexChars[4] = 0;  
   return hexChars;  
 }  
   
 void dumpRegisters() {  
   string2UARTTXQueue("\r\n***************************\r\n");  
   string2UARTTXQueue("Selected register dump:\r\n");  
   string2UARTTXQueue("***************************\r\n");  
   
   string2UARTTXQueue(timeStamp());  
   string2UARTTXQueue("U1CON  = 0x");  
   string2UARTTXQueue(uint16_t2String(U1CON));  
   string2UARTTXQueue("\r\n");  
    
   string2UARTTXQueue(timeStamp());  
   string2UARTTXQueue("U1ADDR = 0x");  
   string2UARTTXQueue(uint16_t2String(U1ADDR));  
   string2UARTTXQueue("\r\n");  
   
   string2UARTTXQueue(timeStamp());  
   string2UARTTXQueue("U1TOK  = 0x");  
   string2UARTTXQueue(uint16_t2String(U1TOK));  
   string2UARTTXQueue("\r\n");  
   
   string2UARTTXQueue(timeStamp());  
   string2UARTTXQueue("U1BDTP1 = 0x");  
   string2UARTTXQueue(uint16_t2String(U1BDTP1));  
   string2UARTTXQueue("\r\n");  

   
   string2UARTTXQueue("***************************\r\n");  
   string2UARTTXQueue("***************************\r\n\r\n");  
   return;  
 }  

Tuesday, October 14, 2014

MIDI2VC+, USB device detection

Work on the MIDI2VC+ is slow but steady. The USB protocol is pretty complicated, especially for a host. But I'm taking it one step at a time. The implementation of a UART with functions for outputting strings, makes the whole thing more manageable. I've gotten so far as to identifying an attached device as "Full Speed" or "Low Speed" and managing the detaching. Here's the code for the project so far:

 #include <xc.h>  
 #include <stdint.h>  
 #define STRING_LENGTH 64  
 #define VERSION "0.1"  
 #define POWER_ON_LED PORTCbits.RC3  
 #define LED1 PORTBbits.RB2  
 #define USB_CONNECTED PORTCbits.RC1  
 #define LED4 PORTCbits.RC0  
 #define UART_TXQueue_SIZE 256  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & FWDTEN_OFF & WINDIS_OFF & ICS_PGx3)  
 _CONFIG2(IESO_OFF & POSCMOD_NONE & OSCIOFNC_ON & FCKSM_CSDCMD & FNOSC_FRCPLL & PLL96MHZ_ON & PLLDIV_DIV2)  
 _CONFIG3(WPFP_WPFP0 & SOSCSEL_IO & WUTSEL_FST & WPDIS_WPDIS & WPCFG_WPCFGDIS & WPEND_WPENDMEM)  
 _CONFIG4(DSWDTPS_DSWDTPS3 & DSWDTOSC_LPRC & RTCOSC_LPRC & DSBOREN_OFF & DSWDTEN_OFF)  
 volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength, USBEnumerationProcess;  
 volatile char UARTTXQueue[UART_TXQueue_SIZE];  
 void string2UARTTXQueue(char str[]);  
 char popUARTTXQueue();  
 void pushUARTTXQueue(char UARTVal);  
 //*** UART1 Receive interrupt function ***  
 //**************************************** k  
 void __attribute__((__interrupt__, __auto_psv__)) _U1RXInterrupt(void) {  
   IFS0bits.U1RXIF = 0; //Reset UART1 Receive interrupt bit  
   U1TXREG = U1RXREG; //Transmit received character  
   return;  
 }  
 //*** USB1 interrupt function ***  
 //****************************************   
 void __attribute__((__interrupt__, __auto_psv__)) _USB1Interrupt(void) {  
   extern volatile uint16_t USBEnumerationProcess;  
   if (U1IRbits.ATTACHIF == 1 && U1IEbits.ATTACHIE == 1) {  
     string2UARTTXQueue("USB device attached.\r\n");  
     USBEnumerationProcess = 1;  
     U1IR = 0x41;  
     U1IE = 0x01;  
     PORTCbits.RC1 = 1; //Light an LED  
   } else if (U1IRbits.DETACHIF == 1 && U1IEbits.DETACHIE == 1) {  
     string2UARTTXQueue("USB device detached.\r\n");  
     USBEnumerationProcess = 0;  
     U1IR = 0x41;  
     U1IE = 0x40;  
     U1ADDRbits.LSPDEN = 0; //The low speed registers need to be reset here  
     U1EP0bits.LSPD = 0; //or the device will be misidentified at the next attachement  
     PORTCbits.RC1 = 0; //Unlight an LED  
   }  
   IFS5bits.USB1IF = 0; //Clear  
   return;  
 }  
 int16_t main() {  
   extern volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength, USBEnumerationProcess;  
   UARTTXQueueTail = 0;  
   UARTTXQueueHead = 0;  
   UARTTXQueueLength = 0;  
   USBEnumerationProcess = 0;  
   TRISB = 0x00;  
   TRISC = 0x00;  
   PORTC = 0x08;  
   uint16_t pll_startup_counter = 600;  
   CLKDIVbits.PLLEN = 1;  
   while (pll_startup_counter--);  
   PORTC = 0x09; //Turn on LED when PLL is locked  
   U1PWRCbits.USBPWR = 1;  
   CLKDIVbits.CPDIV = 0b10;  
   TRISA = 0x02; //RA1 as input  
   AD1PCFG = 0xffff; //No analogue inputs  
   IFS0 = 0x0000; //Interrupts reset  
   IEC0 = 0x0800; //Uart recieve interrupt enabled  
   POWER_ON_LED = 1;  
   //Reconfigurable pin setup  
   __builtin_write_OSCCONL(OSCCON & 0xBF);  
   RPOR2bits.RP5R = 3; //RP5 as UART1 TX  
   RPINR18bits.U1RXR = 0x06; //RP6 as UART1 RX  
   __builtin_write_OSCCONL(OSCCON | 0x40);  
   U1BRG = 8; //Baud rate set to 56k, U1BRG = (8MHz/2)/(4*56000) - 1  
   U1MODE = 0x8808; //UART1 enabled in simplex mode  
   U1STA = 0x8400; //Interrupt when char is transferred to TSR and Transmit enabled  
   U1MODEbits.BRGH = 1;  
   //USB1 host mode setup  
   U1CONbits.HOSTEN = 1;  
   U1OTGCONbits.DPPULDWN = 1;  
   U1OTGCONbits.DMPULDWN = 1;  
   U1OTGCONbits.DPPULUP = 0;  
   U1OTGCONbits.DMPULUP = 0;  
   U1IRbits.ATTACHIF = 0;  
   U1IEbits.ATTACHIE = 1;  
   IEC5bits.USB1IE = 1;  
   char build_date[] = __DATE__;  
   string2UARTTXQueue("***************************");  
   string2UARTTXQueue("\r\n\r\n");  
   string2UARTTXQueue("Welcome to MIDI2VC+\r\nVersion ");  
   string2UARTTXQueue(VERSION);  
   string2UARTTXQueue("\r\n");  
   string2UARTTXQueue("Build date ");  
   string2UARTTXQueue(build_date);  
   string2UARTTXQueue("\r\n\r\n");  
   string2UARTTXQueue("***************************\r\n");  
   while (1) {  
     switch (USBEnumerationProcess) {  
       case 0:  
         break;  
       case 1:  
         if (U1CONbits.JSTATE == 0) {  
           string2UARTTXQueue("Low speed device found\r\n");  
           U1ADDRbits.LSPDEN = 1;  
           U1EP0bits.LSPD = 1;  
           USBEnumerationProcess = 2;  
         } else {  
           string2UARTTXQueue("Full speed device found\r\n");  
           U1ADDRbits.LSPDEN = 0;  
           U1EP0bits.LSPD = 0;  
           USBEnumerationProcess = 2;  
         }  
         break;  
       default:  
         break;  
     }  
     if (UARTTXQueueLength > 0 && U1STAbits.UTXBF == 0) {  
       U1TXREG = popUARTTXQueue();  
     }  
   }  
   return 0;  
 }  
 //*********************************************************  
 //*******Function string2UARTTXQueue**********************  
 //Takes a c-string and sends it to the UART transmit Queue  
 void string2UARTTXQueue(char str[]) {  
   uint16_t i = 0;  
   while (str[i] != 0) {  
     pushUARTTXQueue(str[i]);  
     i++;  
   }  
   return;  
 }  
 //*********************************************************  
 //*******Function popUARTTXQueue**********************  
 //Returns next value in UART transmit Queue and  
 //sets queue values apropriately  
 char popUARTTXQueue() {  
   char UARTVal;  
   extern volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength;  
   extern volatile char UARTTXQueue[UART_TXQueue_SIZE];  
   if (UARTTXQueueLength == 0) {  
     return 0;  
   }  
   UARTTXQueueTail = (UARTTXQueueTail + 1) & (UART_TXQueue_SIZE - 1);  
   UARTVal = UARTTXQueue[UARTTXQueueTail];  
   UARTTXQueueLength--;  
   return UARTVal;  
 }  
 //*********************************************************  
 //*******Function popUARTTXQueue**********************  
 //Puts a char to the UART transmit Queue and  
 //sets queue values apropriately  
 void pushUARTTXQueue(char UARTVal) {  
   extern volatile uint16_t UARTTXQueueTail, UARTTXQueueHead, UARTTXQueueLength;  
   extern volatile char UARTTXQueue[UART_TXQueue_SIZE];  
   if (UARTTXQueueLength == (UART_TXQueue_SIZE - 1)) {  
     return;  
   }  
   UARTTXQueueHead = (UARTTXQueueHead + 1) & (UART_TXQueue_SIZE - 1);  
   UARTTXQueue[UARTTXQueueHead] = UARTVal;  
   UARTTXQueueLength++;  
   return;  
 }  

Wednesday, October 8, 2014

SA0***

I finally got my amateur radio license, which feels nice. The test was quite hard. Today I got a call from the license issuer, who asked what I call sign I wanted. Apparently I'm the first in the region who got to choose my own call sign, as long as it wasn't taken previously.

Sunday, September 21, 2014

MIDI2VC+ UART loop test and analogue synth prototype demo

MIDI2VC+ UART loop test

Work on the MIDI2VC+ has been a bit down lately, since I'm studying for my ham radio license! It's seems like a fun hobby, so maybe I'll be building RF-stuff soon...
Anyway, my MIDI2VC submission to the hackaday prize got med a T-shirt, which I think is nice. I'm happy to get anything. I mean, the contest was about electronics connected to the Internet, which my project very much wasn't!
 In the MIDI2VC project I've finally gotten the mandatory UART loop test working. This is an important step when working with a new MCU, since a successful test means that the following is working:
  1. Oscillators are running
  2. Correct bad rate calculated means that oscillators are running at the frequency we expect them to be running at
  3. Interrupts are working
  4. IO's are working
Also, we now have a great output for debug info.
Here's the code:

 //PIC24FJ64GB004 UART loop test using interrupts
 //http://electev.blogspot.com  
 /*@ignore@*/  
 #include <xc.h>  
 /*@end@*/  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & FWDTEN_OFF & WINDIS_OFF & ICS_PGx3)  
 _CONFIG2(IESO_OFF & POSCMOD_NONE & OSCIOFNC_ON & FCKSM_CSDCMD & FNOSC_FRC & PLL96MHZ_OFF)  
 _CONFIG3(WPFP_WPFP0 & SOSCSEL_IO & WUTSEL_FST & WPDIS_WPDIS & WPCFG_WPCFGDIS & WPEND_WPENDMEM)  
 _CONFIG4(DSWDTPS_DSWDTPS3 & DSWDTOSC_LPRC & RTCOSC_LPRC & DSBOREN_OFF & DSWDTEN_OFF)  
 //*** UART1 Receive interrupt function ***  
 //**************************************** k  
 void __attribute__((__interrupt__, __auto_psv__)) _U1RXInterrupt(void) {  
   IFS0bits.U1RXIF = 0; //Reset UART1 Receive interrupt bit  
   U1TXREG = U1RXREG; //Transmit received character  
   return;  
 }  
 int main() {  
   TRISA = 0x02; //RA1 as input  
   AD1PCFG = 0xffff; //No analogue inputs  
   IFS0 = 0x0000; //Interrupts reset  
   IEC0 = 0x0800; //Uart recieve interrupt enabled  
   //Reconfigurable pin setup  
   __builtin_write_OSCCONL(OSCCON & 0xBF);  
   RPOR2bits.RP5R = 3; //RP5 as UART1 TX  
   RPINR18bits.U1RXR = 0x06; //RP6 as UART1 RX  
   __builtin_write_OSCCONL(OSCCON | 0x40);  
   U1BRG = 25;//Baud rate set to 9600, U1BRG = (8MHz/2)/(16*9600) - 1  
   U1MODE = 0x8800; //UART1 enabled in simplex mode  
   U1STA = 0x8400; //Interrupt when char is transferred to TSR and Transmit enabled  
   while (1) {  
   }  
   return 0;  
 }  

Analogue synth prototype demo

I realized that I don't have a proper demo video of the analogue synth prototype I built, so I made one and here it is:

Saturday, August 2, 2014

Button debounce, analogue VS digital

On the MIDI2VC, there is an unused button (named DEMO), which I want to use for an extra feature. For this new feature I need to debounce the button, one button press shall change state once, and only once. My first approach was to do this in software. After some fiddling I ended up with this code:
 //PIC16F1823 Button debounce using Interrupt On Change and timer overflow interrupts  
 /*@ignore@*/  
 #include <xc.h>  
 /*@end@*/  
 #include "header.h"  
 //Configuration bits  
 #pragma config WDTE=OFF, PWRTE = OFF, MCLRE=ON, BOREN=OFF, FCMEN=OFF, CLKOUTEN = OFF, IESO=OFF, FOSC=INTOSC, CPD=OFF, LVP = OFF, BORV = 0, PLLEN = OFF  
 #pragma switch speed  
 #define _XTAL_FREQ 32000000  
 void main() {  
   //TRISA  
   //TRISC  
   //RX(RC5), SCL(RC0) and SDA(RC1) as inputs  
   TRISA = 0b00010000;  
   TRISC = 0b00100011;  
      //Weak pull ups enabled on RA4  
   WPUA = 0b00010000;  
   //Set all I/O's to digital  
   ANSELA = 0x00;  
   ANSELC = 0x00;  
   PORTA = 0b00110000;  
   PORTC = 0x00; //Clear RC2 to unlight LED  
      //Enable Interrupt On Change on neagative slope on RA4  
   IOCAN = 0b00010000;  
      //No interrupt on positive slope  
   IOCAP = 0x00;  
      //Reset interrupt flag  
   IOCAF = 0x00;  
   //0 Internal oscillator, 3 <fosc> on, 6-4 31kHz  
   // OSCCON = 0b00000000;  
   OSCCON = 0b11110000;  
   //Interrupt controller  
   //5 Timer0 overflow interrupt enabled  
      //6 Peripheral interrupt enabled  
   //7 Global interrupt enabled  
      //3 Interrupt on change enabled  
   INTCON = 0b11101000;  
   //Timer0  
   TMR0 = 0x00;  
   //Option Register  
   //2-0 Prescaler 1:4  
   //3 prescaler assigned to Timer0  
   //4 timer edge, high to low  
   OPTION_REG = 0b01000101;  
   //Loop forever, interrupts when Timer0 overflows  
   while (1) {  
   }  
 }  
 //Interrupt routine  
 interrupt void isr(void) {  
   static char overFlow = 0;  
   if (TMR0IF == 1)  
   {  
     TMR0IF = 0;  
     overFlow++;  
     if (overFlow == 0)  
     {  
                //Reenable Interrupt On Change  
       IOCAN = 0b00010000;  
       INTCON = 0b11101000;  
     }  
   }  
   else if (IOCAF4 == 1)  
   {  
           //Disable Interrupt On Change  
     INTCON = 0b11100000;  
           //Toggle LED  
     LATC ^= 1 << 2;  
           //Reset Interrupt On Change flag  
     IOCAF4 = 0;  
   }  
   return;  
 }  

This is a very well controlled way to specify at which rate the button can be pressed without missing push-events, but as you can see, it adds quite a lot of code to the interrupt service routine. Since I have optimized my ISR quite a bit, I want to avoid adding code to it. I am also running out of data space in general, so that is also a reason not to add more code.
 So, I tried out debouncing the analog way, by adding a low pass filter between the button and the IO pin.
Analogue Switch Debouncer
The pull-up resistor in the MCU is of unknown value, but the data sheet calls it weak, so it's probably over 10kΩ.
 The analogue solution worked fine, but I need to do some strapping for it to work. I'll make a new version of the board that incorporates this.
Analogue Debounce strap

Sunday, July 20, 2014

Hackaday Prize and MIDI2VC+ update

Hackaday Prize

I submitted the MIDI2VC project to the Hackaday Prize contest. I know that the MIDI2VC doesn't really qualify for the connected criteria. Connected meaning connected to the Internet. My interpretation of connectedness in this case is the connection between new and old, between digital and analogue. I have no aspiration of going to the moon, but a t-shirt or some electronics swag would be nice :)

MIDI2VC+ update

I received the MCU's for the MIDI2VC+ a while ago. I don't remember if I wrote about it, but it was hard sourcing the MCU for the project (PIC24FJ64GB004), so I ended up ordering a couple of samples from Microchip. A while after the samples arrived, I realised that I had ordered the wrong chips... I had ordered PIC24FJ64GA004! Those chips are almost pin compatible, but don't have the USB host capabilities I need. I've tried getting the right chips again, but still difficult. I ordered a couple from ebay, but they are not being shipped and no explanation from the seller (a chip shortage there as well?). So, I ended up ordering samples again. This time the right ones along with a couple of PIC24FJ64GB002, which can act as USB host, but has fewer pins (I thought the first chip was the one with lowest pin count, I guess I didn't do my homework). I might use those in the next version of the board. There will, by the way, be another board version. I realised I need to use an external crystal since to internal oscillator isn't precise enough for the USB standard.

Buggy Pickit 3

I tested out the MIDI2VC+ board with the chips I got from Microchip. I managed to put together a small hello world program that flashes the four LEDs nigthrider style! The program uses maximum clock speed, with internal oscillator, and interrupts.

I tried using the Pickit 3 for the MIDI2VC, but it seems to be so buggy that I ended up going back to the Pickit 2. It would probably had taken me half the time developing the knightrider program if I had used the Pickit 2 from the beginning. However, I can't use the Pickit 2 for debugging, so I may have to use 3 once in a while anyway. Pickit 3 bugs include:
  • Sometimes, not being able to identify the target device
  • Sometimes, not being able to program target  device
  • Not being able to power device two or more times in a row
  • Same code giving totally different results on different build and programming occasions
Hello knightrider code:
 /*@ignore@*/  
 #include <xc.h>  
 /*@end@*/  
 #ifdef __PIC24FJ256GB110__     //Defined by MPLAB when using 24FJ256GB110 device  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & COE_OFF & FWDTEN_OFF & ICS_PGx2)  
 _CONFIG2(IESO_OFF & FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMOD_HS & FNOSC_PRIPLL & PLLDIV_DIV2 & IOL1WAY_ON)  
 _CONFIG3(WPCFG_WPCFGDIS & WPDIS_WPDIS) //Disable erase/write protect of all memory regions.  
 #elif __PIC24FJ256GA110__     //Defined by MPLAB when using 24FJ256GA110 device  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & COE_OFF & FWDTEN_OFF & ICS_PGx2)  
 _CONFIG2(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMOD_XT & FNOSC_PRI & IOL1WAY_ON)  
 _CONFIG3(WPCFG_WPCFGDIS & WPDIS_WPDIS) //Disable erase/write protect of all memory regions.  
 #elif __PIC24FJ64GA004__     //Defined by MPLAB when using 24FJ64GA004 device  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & COE_OFF & FWDTEN_OFF & ICS_PGx1)  
 _CONFIG2(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMOD_NONE & FNOSC_FRCPLL & I2C1SEL_SEC & IOL1WAY_ON)  
 #else     //Default to the PIC24FJ128GA010  
 // JTAG/Code Protect/Write Protect/Clip-on Emulation mode  
 // Watchdog Timer/ICD pins select  
 _CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & COE_OFF & FWDTEN_OFF & ICS_PGx2)  
 // Disable CLK switch and CLK monitor, OSCO or Fosc/2, HS oscillator,  
 // Primary oscillator  
 _CONFIG2(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMOD_XT & FNOSC_PRI)  
 #endif  
 int volatile LEDS_backward;  
 int volatile LEDS_index;  
 void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void) {  
   extern volatile int LEDS_backward;  
   extern volatile int LEDS_index;  
   LEDS_index = ((LEDS_index + 1) % 3);  
   LATC &= 0xFFF4;  
   LATB &= 0xFFFB;  
   if (LEDS_backward == 0) {  
     switch (LEDS_index) {  
       case (0):  
         LATB |= (1 << 2);  
         break;  
       case (1):  
         LATC |= (1 << 0);  
         break;  
       case (2):  
         LATC |= (1 << 1);  
         LEDS_backward = 1;  
         break;  
     }  
   }  
     else if (LEDS_backward == 1) {  
     switch (LEDS_index) {  
       case (0):  
         LATC |= (1 << 3);  
         break;  
       case (1):  
         LATC |= (1 << 1);  
         break;  
       case (2):  
         LATC |= (1 << 0);  
         LEDS_backward = 0;  
         break;  
     }  
   }  
   IFS0 = 0x0000;  
   return;  
 }  
 int main() {  
   extern volatile int LEDS_backward;  
   extern volatile int LEDS_index;  
   LEDS_backward = 0;  
   LEDS_index = 0;  
   TRISB = 0x00;  
   PORTB = 0x00;  
   TRISC = 0x00;  
   PORTC = 0xff;  
   IFS0 = 0x0000;  
   IEC0 = 0x0008;  
   T1CON = 0x8010;  
   TMR1 = 0x0000;  
   while (1) {  
   }  
   return 0;  
 }  

Monday, June 30, 2014

MIDI2VC, finished basic functionality

I've now added support for Pitch Bend to the MIDI2VC, which means the project is basically finished. The updated project is here.
 A demo video, with pitch bend, is below:
I thought about experimenting with the sleep mode of the PIC, to see if I can get the circuit a bit more efficient (probably not, since there is a linear regular on it...) My thought was that the PIC could go into sleep mode when it's waiting for an UART character. Seems though, that this function needs to receive a 0x0000 word to get out of sleep mode, and since I don't control the MIDI-device attached, I probably can't get that. Maybe there's a work around, we'll see....
Otherwise, it's time to start working on the MIDI2VC+.

Thursday, June 12, 2014

Synthesizer chopping sound

The chopping sound in the release phase I wrote about in the last post was actually from the DAC going to the lowest key. I coded this a while ago when I wanted to play without having an attack and release envelope. Now this is messing upp the release phase, so I removed the feature to let the DAC voltage level stay on the last played note. Sounds nice!

Tuesday, June 10, 2014

Actual synth sound + Attack and Release

Attack and Release

The last module for my simple synthesizer was an attack and release envelope generator to be driven by the gate out port of the MIDI2VC and connected to the VCA. I saw some designs that I thought where to complicated for now, so I made up my own very simple circuit:
Simple attack and release envelope generator
It's based on a comparator that source or drain current to C1. Right now it's half working. On the release I get an oscillation that sounds like a low frequency humping. Possibly this is caused by the emitter follower on the output. I'll look into it soon. I may also increase C1 to get a longer envelope.

Actual synth sound

So, this is what the synth looks like right now:
Messy analogue synthesizer

And this is what it sounds like:


Now I want to build some better synthesizer modules :)

Wednesday, May 7, 2014

MIDI2VC+ PCB

The PCB for the MIDI2VC+ has been ordered from http://oshpark.com/ . It will work as the MIDI2VC, with the addition of being a USB-host, so that one can connect a MIDI-USB device to it.

MIDI2VC+ PCB from Oshpark

Monday, May 5, 2014

Sunday, April 27, 2014

MIDI2VC and MIDI2VC+

MIDI2VC+

When I got my mini-midi-keyboard, I decided to add USB support to the MIDI2VC, since this is what most modern MIDI devices use. This new feature is quite complex to add. It needs and MCU with USB host capabilities. The simplest PIC I found with this feature was the PIC24FJ64GA004, which is a 44 pin 16-bit MCU. Obviously changing to this device will be a major change in the project, so I decided to name the new board MIDI2VC+. I'm working on the schematic and the layout right now, and as soon as I've sent the design to OshPark, I'll finish the MIDI2VC and release it as an open source hardware project.

I2C: 3.3V and 5V bus

When working on the new MIDI2VC+ design, I was bothered with the different supply voltage of the DAC and the new PIC. The new PIC is using CMOS (3.3V) levels and the DAC TTL (5V). This means a 3.3V I2C bus and a 5V I2C bus that needs to communicate. My first solution was using the PCA9517A, a bidirectional, level changing, bus repeater. This seemed all well and dandy, until I realised that I needed to order this part from RS Components, and the shipping would cost more than 10 times than the part. This went against my principle of making a project with cheap, and easy to source components.
 After doing some research I understood that sometimes it's possible to run different I2C level buses on the same voltage bus. Here's how it works. The I2C bus is an open drain bus. This means that the bus has pull up resistors to the higher voltage level. So if no one does anything on the bus, the bus sits at its high level. As soon as any device on the bus wants to communicate, it sinks current from the bus, making it go low, to  send a logic 1. For a logic 0 the device lets the bus go to the high level. The whole protocol is well explained on several places on the web...
 Lets look at our to devices then: 
The DAC is a five volt logic device. It sinks the bus down to 0.4V when signaling a low level. Thresholds are VIL = 0.3*5V = 1.5V and VIH = 0.7*5V = 3.5V.
The PIC is a 3.3V device, but it tolerates up to 5.5V on digital only pins (which we will use). It sinks the bus to 0V for a low level. It's thresholds are VIL = 0.3*3.3V = 0.99V and VIH = 0.7*3.3V = 2.31V.
Seems like the two devices can communicate over I2C despite the different logic levels!

Tuesday, April 8, 2014

MIDI2VC, finally stable

Phew, another rewrite of the whole code.. This time I changed pretty much all variables to chars, which is 8bits wide, the size that the processor use native. I also increased the clock speed to 32MHz, which is the highest frequency with the built in oscillator.
 Now I cannot provoke an overrun error, or buffer overflow. This means that it's now time to add the pitch bend feature along with the trigger output (and then it's done ; ) .


Wednesday, April 2, 2014

MIDI2VC, Inline Assembly optimisation

I've been trying to use inline assembly to optimise the push and pop uart queue functions, since I still have some problems with overflowing of the UART reception.
 Even though the free version of XC8 seems to add some assembly instructions that doesn't do anything, it still seems to be doing a pretty good job. At least better than mine. When writing inline assembly, there are some precautions you need to take so that assembly code doesn't mess up what XC8 have been doing. This takes quite a bit of code to do, so it seems my try at optimisation wasn't that successful...
 In the end, learning assembly gives some insight into how the MCU works, so I still did some optimisation of the C code, by changing the queue to char's(8 bit), instead of int's(16bit).
 Right now it is quite difficult to provoke an UART overflow, but it's still possible. So I'm going to turn up the clock speed of the PIC, and think some more about how to make the UART functions faster.

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.

Wednesday, February 26, 2014

MIDI2VC, output levels

I haven't been writing a lot about the project in a while. This is because I have been working on some solutions and I haven't wanted to write about them before they were solved. So, first thing first:

Lost MIDI symbols

The lost symbols I wrote about in the last post seems to be an issue with the MIDI keyboard. I tried using MIDI ox instead, which worked. I tried another keyboard, also worked. Right now, I'll leave it there. When I go back to work I'll troubleshoot this with a digital scope.

MIDI2VC calibration

I realised there was to much work calibrating the MIDI2VC by hand. There are 48 keys to be entered in a look up table, which may need recalibration under various conditions. No good. 
 My first idea was to use the PICs ADC to read the DAC outputs and do the calibration. The ADC is 10 bits, and my thought was to do some measurements and then do a least squares fit to find the right values for the DAC. This proved difficult due to the large numbers needed to be multiplied. Also, a quick test showed bad accuracy.
 The solution was instead to swap the 4.096V voltage reference for a 5V, and bypassing the 1V addition. This way the output is only dependent on the voltage reference and the DAC. I believe their accuracy will be enough. Some small offsets in voltage will not matter much, since the VCO will still need to be tuned. The important factor is the linearity of the DAC, and reading the data sheet shows that it should not defer more than two bits. Two bits is about 2mV, and should not be audible.
 Anyhow. I'm trying out this and working on a new PCB simultaneously. The new approach seems to work, but there is a problem when playing MIDI-keys to fast. I believe this is caused by the PIC having to much to do in the interrupt routine, and I'm working on rewriting some of the code to get this fixed.

Tuesday, January 28, 2014

Mounted MIDI2VC PCB

I have now mounted all components on the MIDI2VC PCB. It pretty much works as expected. I had done a couple of errors on the DIN5 footprint and generally the MIDI in circuitry. This has now been fixed by patching and using chassis mount DIN5 connector. A problem persists, however, and that is symbols being dropped in the opto coupler. I've checked and rechecked the circuitry and the data sheets but cannot find any errors. It is difficult to trouble shoot this with an analog scope, so I may have to hunt this one down with a digital scope, if I can only borrow one.. I can always take a trip to work I guess .. :)

MIDI2VC, bottom, patched MIDI connector to the left.
MIDI2VC, component side.


Thursday, January 23, 2014

VCA's and PCB's

PCB

The PCB's from Osh Park came yesterday. They look like this:
PCB's from  Osh Park
Nice!
 I haven't started mounting them yet, but I immediately saw that the MIDI contact didn't fit. This wasn't so surprising, I didn't have the part when I made footprint, and the schematic of the part was a bit strange (I'm not that good at mechanical drawings).

VCA

 I need a voltage controlled amplifier for the synthesizer. This is to be used for generating the envelope. I looked at a few ways of doing this, but using the LM13700 seemed the most reasonable. Remember that I burnt an LM13700 not long ago? I realised that I probably burned just one of the channels. So I wired up a VCA circuit i found in the data sheet (page 10, fig 23):
VCA from LM13700 data sheet
The circuit, and it's output, looks like this:
Input to VCA is the sine, output is the clipped sine.

VCA from LM13700
The VCA looks to be working fine, but I might tweak some resistor values after testing it with the Attack and Release envelope generator I intend to build soon.

Broken Power Supply

When I was fiddling with the VCA the power supply to the gain control was overpowered. This surprised me, since it can drive 3A, and I calculated max current to 1.5mA. Seems like there is something wrong with the current measurement in the old power supply. It can, however, give up to 1.2mA, which is just fine right now. So I'll need to fix it, but first I've got some more interesting things to do...

Sunday, January 19, 2014

MIDI2VC code update, addition of "MIDI Key Bucket"

I've added some functionality to the MIDI2VC code. The problem that needed fixing was when multiple MIDI keys where pressed simultaneously, this could confuse the system. What I've added now is a priority system, where the lowest key pressed is the one the generates an output voltage. This is similar to what is used in the Mini Moog. In the future, I might implement a function for choosing one of several priority systems, but I'll need a PIC with more IO's to do that.
I can't test this functionality very well right now, since MIDI Ox only let's me play two simultaneous keys, and I haven't gotten my real MIDI keyboard to work with the MIDI2VC yet. I have tried sending manual MIDI keys, using UART, and that works as expected.
 Here are the updated functions.

   
 //***********************************************************  
 //**************Function processUARTBuffer*******************  
 //Interprets UARTBuffer, toggles LED and sends appropiate  
 //I2C data to DAC  
 //***********************************************************  
   
 void processUARTBuffer(int UARTBufferF[])  
 {  
   extern int i2cData[];  
   extern int MIDISimKeysPressed;  
   const int DACLookupTable[127] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x017, 0x072, 0x0C2, 0x116, 0x169, 0x1B9, 0x20C, 0x25F, 0x2B0, 0x305, 0x356, 0x3A9, 0x3FC, 0x44D, 0x4A2, 0x4F5, 0x547, 0x5C7, 0x5EF, 0x63E, 0x691, 0x6E7, 0x739, 0x78E};  
   int DACValue;  
   int DACValue;  
   
   //If UART symbol is MIDI "Note on", light LED and  
   //send lowest data in MIDI key bucket to DAC  
   if (((UARTBufferF[0] & 0xF0) == 0x90) && (UARTBufferF[2] != 0x00))  
   {  
     //Keeps track of number of keys simultaneously pressed  
     MIDISimKeysPressed++;  
   
     pushMIDISimKeysBucket(UARTBufferF[1]);  
   
   
     DACValue = DACLookupTable[isLowestInMIDISimKeysBucket()];  
     i2cData[1] = (DACValue & 0xFF);  
     i2cData[0] = ((DACValue & 0x0F00) >> 8);  
   
     lightLED();  
     i2cDataToBeSent = 1;  
   }  
   
     //If UART symbol is MIDI "Note off", check if there are  
     //other notes in bucket and send their lowest data to DAC, otherwise  
     //turn off LED and send zeros to DAC  
   else if (((UARTBufferF[0] & 0xF0) == 0x80) || (UARTBufferF[2] == 0x00))  
   {  
     MIDISimKeysPressed--;  
     popMIDISimKeysBucket(UARTBufferF[1]);  
   
     if (MIDISimKeysPressed == 0)  
     {  
       i2cData[0] = 0x00;  
       i2cData[1] = 0x00;  
   
       unlightLED();  
       i2cDataToBeSent = 1;  
     }  
     else if (MIDISimKeysPressed > 0)  
     {  
       DACValue = DACLookupTable[isLowestInMIDISimKeysBucket()];  
       i2cData[1] = (DACValue & 0xFF);  
       i2cData[0] = ((DACValue & 0x0F00) >> 8);  
   
       i2cDataToBeSent = 1;  
     }  
   }  
   
   return;  
 }  
   
 //***********************************************************  
 //**************Function pushMIDISimKeysBucket***************  
 //Function for pushing a MIDI key int into the MIDI bucket.  
 //The MIDI bucket is a data sctructure 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 pushMIDISimKeysBucket(int midiKey)  
 {  
   extern int MIDISimKeysBucket[];  
   
   //place MIDI key value in first -1 (empty) instance  
   int i = 0;  
   while (i < MIDISimKeysBucketSize)  
   {  
     if (MIDISimKeysBucket[i] == -1)  
     {  
       MIDISimKeysBucket[i] = midiKey;  
       i = MIDISimKeysBucketSize;  
     }  
     else  
     {  
       i++;  
     }  
   }  
   
   return;  
 }  
   
 //***********************************************************  
 //**************Function popMIDISimKeysBucket***************  
 //Function for removing value from bucket.  
 //***********************************************************  
   
 void popMIDISimKeysBucket(int midiKey)  
 {  
   extern int MIDISimKeysBucket[];  
   
   //Find value in array and replace with -1  
   //If no value is found, do nothing  
   int i = 0;  
   while (i < MIDISimKeysBucketSize)  
   {  
     if (MIDISimKeysBucket[i] == midiKey)  
     {  
       MIDISimKeysBucket[i] = -1;  
       i = MIDISimKeysBucketSize;  
     }  
     else  
     {  
       i++;  
     }  
   }  
   
   return;  
 }  
   
   
 //***********************************************************  
 //**************Function isLowestInMIDISimKeysBucket*********  
 //Returns the lowest value in the bucket  
 //***********************************************************  
   
 int isLowestInMIDISimKeysBucket()  
 {  
   extern int MIDISimKeysBucket[];  
   int lowestKey = 0x7f; //Highest legal MIDI key  
   
   
   //Finds lowest key, that isn't -1, in bucket  
   int i = 0;  
   while (i < MIDISimKeysBucketSize)  
   {  
     if ((MIDISimKeysBucket[i] < lowestKey) && (MIDISimKeysBucket[i] > -1))  
     {  
       lowestKey = MIDISimKeysBucket[i];  
     }  
     else  
     {  
       i++;  
     }  
   }  
   
   return lowestKey;  
 }  
   

Wednesday, January 15, 2014

Chip burning, but success in the end

LM13700 bias input

After thinking about it for a while, I realised that the bias current input of the LM13700 isn't referenced to ground, but to the negative rail. That explains the doubled amplitude of the VCO waveforms. Looking in the data sheet you can see that the bias input sits a current mirror over the negative rail. It seems like there should be two diode drops over V-. But since I'm there messing around and changing the current setting resistor, I may as well measure it properly. So, voltage between positive and negative rail is measured to V+-V-=18.034V. The resistor is 38.33kΩ and the current IA=0.4371mA, so: 18.034V-0.4371mA·38.33kΩ=1.28V, which we, with confidence, can say is two diode drops.
 Changing the current controlling resistor for IA, I made a calculation error, and since I stopped thinking for a little while, I used a too small resistor and killed the chip with 3mA on the current bias input. This is about 1 mA over the maximum current. I did get a new chip today (they are quite expensive) and wired everything up again, and now it actually works like it should...


Completing the VCO

By wiring the log converter to the VCO and connecting the input of the log converter to the VCO, I now can play some music... By connecting the MIDI2VC output to the log converter and the VCO output to a speaker, I can now play using Midi Ox as MIDI source. The VCO needs some tuning, so I added a tuning pot that adds or subtracts some voltage from the input stage inverter in the log converter. Here are some pictures:
Playable system. MIDI2VC to the right. Log converter and VCO on the upper left. To the lower left is a mixer mentioned in earlier posts.
And here's a video where you can hear some random notes from the system (sorry about low sound volume):






Logarithmic VCO, final schematic


Saturday, January 11, 2014

Current controlled oscillator, LM13700 transconductance amplifier

Theory

There is a neat device for building a voltage, or rather current, controlled oscillator. It's a transconductance amplifier called LM13700. It has a positive and negative voltage input, but a current output. It also has a current bias input. The output current is proportional to the differential input times the current bias input. The LM13700 has a lot of applications such as voltage controlled -amplifier, -resistor and -filter and multiplier.
 In the data sheet, page 16, there is a simple VCO, which works very similar to the VCO i built in an earlier blog post.
I did my own schematic of the VCO with pin numbers of the device:
Voltage (or rather current-) controlled oscillator taken from the LM13700 datasheet.

I want my oscillator to play between C2, 65.4064Hz, and C6, 1046.50Hz. The frequency of the VCO is: fOSC=IC2/(4CfIARA). The current for the lowest key, VC = 1V, was measured to 64μA, and current for the highest, VC = 5V, was 1.02mA. That means we have 4Cf IA RA = IC2fOSC, with the two values IC2fOSC = 64μA·65.4064Hz =979nC and IC2fOSC =  1.02·mA1046.50Hz = 975nC, which has a mean of 977nC. Our target will therefore be 4CfIARA = 977nC.
 We also have that the output amplitudes of the oscillator is A=IARA, so that's a good place to start deciding our component values. If we go by the data sheet and set RA= 4.7kΩ (closest E12 resistor to  5.1kΩ) and we want 1V amplitude, we get IA=1V/4.7kΩ = 213μA. With a 39kΩ resistor connected to positive rail we get  IA=231μA, close enough. We now only have to decide the value of Cf.
Cf = IC2fOSC /(4IARA)=977nC/(4·231μA·4.7kΩ) = 225nF. I found two 120nF caps in my junk box, so I'll use them in parallel to get 240nF.

Circuit build

The LM13700 is hid under the decoupling cap. The black alligator clip is the current source.

The two waveforms at VC = 3V. 
We can see that the waveforms have a nice shape, but the amplitude is twice of what I calculated. The frequency is a bit on the lower side. I'll hook the VCO up to the log converter and the MIDI2VC, and see how parameters can be calibrated. It would be interesting to figure out why the VCO differs in function from the calculations, but right now I'm more eager to test out the MIDI2VC...

Logarithmic Converter, Control Voltage to Logarithmic Current, Part 2, Implementation

Here's an image of the logarithmic converter built on a copper plane.
Logarithmic converter
The measurement cables measures IC2, from converter to ground. The two PNP transistors are glued together for better thermal stability. The IC is a dual op amp, LM358P.
The following table shows IC2 as a function of VC.

VC(V) Simulated IC2 (mA) Measured IC2 (mA)
0 0.027 0.031
1 0.055 0.064
2 0.110 0.130
3 0.220 0.268
4 0.443 0.551
5 0.871 1.02
I think simulated and measured data compares pretty good, considering that simulation is done using general op amp model, and not the right transistors. IC2 doubles pretty well with every voltage step. We really don't need exact values, since a calibration pot will be added as soon as the VCO is up and running. The next post will be about building the VCO.

Thursday, January 9, 2014

Logarithmic Converter, Control Voltage to Logarithmic Current, Part 1, theory

One standard of analogue synthesizer control is 1V/octave. This is what I'm implementing in the MIDI2VC, and now I need to build a simple logarithmic converter to place between the MIDI2VC and the VCO. In fact, I will actually build a new, really simple VCO from a LM13700.
 The logarithmic converter is thoroughly explained in Electronotes S-019. The one described on page 2 is one that sinks current, and the LM13700 VCO needs a current source. In Electronotes 129 there is a big VCO schematic that has a log converter with a current source. A combination of the two schematics is seen here:
Current source logarithmic converter
The relationship between the currents is:
\[ I_{C2 } = I_{C1} e^{-V_{IN}\cdot  \ 26  \text{mV} } \]
Ignoring the small base currents in the transistors, we also have
\[ I_{O } = I_{C1}+I_{C2} \]
We will want to limit  $I_{C2}$ to protect the LM13700. It can receive a current of 2mA, but let's set max current to 1mA to be on the safe side. We can combine the two current equations to:
\[ I_{O } = I_{C2} ( 1 +  e^{V_{IN} / 26  \text{mV} } )  \]
$A_2$ is an inverting attenuator that converts positive control voltage to negative in voltage:
\[ V_{IN} = V_C \cdot 18 \text{mV}   \]
$V_O$ will swing all the way to the positive rail (or close at least). $V_e$ is always close to 0.6mV (diode drop). We want the converter to work up to 5 Volts (max output of the MIDI2VC). This means we should set
\[ R_L \le \frac{9-0.6 \mathrm{V}}{1\mathrm{mA} (1+e^{-5 \cdot 18/26} ) } =  8144 \Omega \]
In fact, we need to go a bit lower on $R_L$, due to the approximations in the calculation. I settled fro $R_L = 6.8$k\Omega.
We now set:
\[ I_{C1} \le  1\mathrm{mA} e^{-5 \cdot 18/26} \]
We can get this current by connecting 330k\Omega to the negative rail.
An LTSPICE simulation of this circuit can give us a plot like this:
Output current as function of voltage. Note that the current doubles between each 1V increment up to the current limitation. Simulation done in LTSPICE.