Project Source

- Introduction

Much people saw that I made a MIDI Interrupter (or synthetizer as you like) with an FPGA. But few people has access to an FPGA, and most to do something *simple*, so I resolv to made one with PIC 16F628.

In the beggining I was thinking to choose a less-sized PIC, but these doesnt have the Serial Receiver via hardware, so it would be complicated to implement it. Other point, is that the difference of price isnt much between PIC's, so it was basicly a mean of space. With a bigger PIC, we can have space for something in the future if needed.

The PIC 16F628A has 2KB of Flash Memory, 224 Bytes of RAM, 128 Bytes of EEPROM, one Comparator, 3 Timers, and one Universal Serial Transceiver. It is on 18 Pin DIP package and has 2 ports of 8 bits (depending of the configuration you can *lose* a 3 bits)

Here is the Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/40044F.pdf

It has an Internal RC Oscillator of 4MHz, with +/- 1%, so we can use it.

- MIDI Protocol

Lets talk about MIDI before the rest. The MIDI Signal is basicly a Serial Signal much like the RS232 Standard. It sends 10 bits of which is one Start Bit, 8 bit of Data, and one Stop Bit. It only doesnt fit at RS232 Standard because the Baud Rate is not in the default values. The MIDI Baudrate is 31250 Hz (or 31.25 kBits per second). But as it is the Baud Rate the only thing of the Serial Default, we can use the USART from PIC to receive the data using 31250 as Baudrate.

Other point from the MIDI Protocol is that all data on midi is composed by 3 Bytes (24 bits), being the first of them the Operation byte, and other two the Data Byte. For an easier identification of which is what, they standardized that if the first bit of MSB is High Level (1), its a Operation Byte, if Low Level (0) its a Data Byte.

This simplified table of MIDI Signals:

Tabela do Protocolo MIDI

In the table is there what we will use, excluding the Pitch Bend.

So we will made the PIC read three bytes and after that make the necessary process.

- Musical Notes and PIC Timer

Other important thing is the musical notes. The MIDI only sends the Note Number, that goes from 0 to 127. So we need to calculate the note period.

The frequency of a note must double from each 12 notes. For that

Frequency = FreqBase * 2^(n/12)

Where Frequency is the note frequency that we want, FreqBase is the frequency of a Base Note, n is the relative note number of the Base Note. The bas not that we will use is the 10º MIDI Note and its frequency is 27.5Hz. For calculating the period is simple, just get the inverse from the frequency.

Period = 1 / ( FreqBase * 2^(n/12) )


We can calculate that in realtime on PIC, but that will cause too much delays, so we will made a table with the periods we need. Lets use a PHP Code for that:



for($i=0;$i<128;$i++) {
    $freq = 27.5 * pow(2,($i-9)/12);
    echo("Nota: $i Frequência: $freq\r\n");
}

?>

 Codepad: http://codepad.org/4GloHc3b

With something like, we can do this for the period:



for($i=0;$i<128;$i++) {
    $period = 1/ (27.5 * pow(2,($i-9)/12));
    echo("Nota: $i Periodo: $period\r\n");
}

?>

Codepad: http://codepad.org/yqXDTXQZ

In the PIC, we will use TIMER1 as reference, and it is 16 bits, that means it goes from 0 to 65535. We will made it increment from 1 to 1 micro seconds, so we need to put the period in micro seconds too. Other detail is when the timer is running, we can't stop it. We need to wait until it overflows. So we only will made it count what it needs. Being the maximum is 65535, when it starts a note, we will put 65535-period in it start value, so it will only count the period size. Lets made a script that generates an array with all data we need.

 


echo " const unsigned int16 notes[] = {";
for($i=0;$i<128;$i++) {
    $period = 65535-round(1000000 / (27.5 * pow(2,($i-9)/12)));
    if($i==0) echo($period); else echo(",$period");
}
echo "};";
?>

Codepad: http://codepad.org/yUNkOm7V

const unsigned int16 notes[] = {4379,
7811,11051,14109,16995,19720,22291,24718,27009,29171,31212,33139,34957,
36673,38293,39822,41265,42627,43913,45127,46272,47353,48374,49337,50246,
51104,51914,52679,53400,54081,54724,55331,55904,56444,56954,57436,57890,
58320,58725,59107,59468,59808,60130,60433,60719,60990,61245,61485,61713,
61927,62130,62321,62501,62672,62832,62984,63127,63262,63390,63510,63624,
63731,63832,63928,64018,64103,64184,64259,64331,64399,64462,64523,64579,
64633,64684,64731,64777,64819,64859,64897,64933,64967,64999,65029,65057,
65084,65109,65133,65156,65177,65197,65216,65234,65251,65267,65282,65296,
65310,65322,65334,65345,65356,65366,65376,65385,65393,65401,65408,65416,
65422,65429,65435,65440,65446,65451,65455,65460,65464,65468,65472,65475,
65479,65482,65485,65488,65490,65493,65495};

 


That will give us the array with all values that we need to put at TIMER1. Now we can start to program :D

- PIC Program

The PIC isnt taht complex. We will use the CCS C as C Compiler, but this code can easily be adapted for other compilers. We will use the USART Interruption for receiving the bytes and store in a 3 bytes array. When it finishes to store the 3 bytes, we will set a variable to 1 to know that we already received 3 bytes.

We will define two vars as int16 one for period and other for tOn, one char array with size 3 for the bytes, two int1 for identify if there is any turned on note and for store the loaded note.

static unsigned int16 tOn = 250;    //tOn
static unsigned int16 period = 0;   //Period
static int1 noteOn = 0;             //Turned Note
static int8 loadedNote = 0;         //Loaded Note
static char buffer[3];              //Bytes Buffer
static int1 buffer_loaded;          //Buffer State
static int  buffer_counter = 0;     //Buffer counter

That will define the vars that we will use in the code. Now we will made a function to use in the Serial Port Interruption. The CCS C identifies the Serial Interruption as INT_RDA, so we will put a function for it.

#int_rda 
void serial_isr() 
{ 
   if(buffer_counter!=3) {             //If it didnt read the three bytes
      buffer[buffer_counter]=getc();   //Read the byte and store on buffer
      buffer_counter++;
      if(buffer_counter==3) {          //If three bytes read
         buffer_counter = 0;           //Reset the counter
         buffer_loaded = 1;            //Set the buffer as done
      }else{                           //If not
         buffer_loaded = 0;            //Stay with buffer state as 0
      }
   }else                               //If receive some byte when buffer
      getc();                          //is loaded, discard the byte.
}

With that we are receiving the bytes and storing at buffer. Now we will made a interruption for TIMER1. In that interruption we will only made it set the correct value when timer overflows. The interruption is called every time that TIMER1 reaches the end. That means when it reaches 65535 us in our case.

#INT_TIMER1
void resetTimer1() {
   set_timer1(period);
}

Now we will made the hard work. We need to initialize the PIC things, that we will do in void main().

We will define a int16 var for getting the runtime TIMER1 position in the code.

void main()
{
   int16 pos;
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
   setup_timer_2(T2_DISABLED,0,1);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   enable_interrupts(global);
   enable_interrupts(INT_TIMER1); 
   enable_interrupts(INT_RDA); 

With that we initialized everything we need. Now we will go to the "checks". We will use the Pin A0 for the output of interrupter, A1 as Enable, A2 as Busy Flag Output.

 

   while(true) {                    //Loop forever
      pos=get_timer1()-period;      //Get the timer1 value and subtract the period
                                    //We will use that for tOn
      if((pos<=tOn)&noteOn)         //If the position is below than tOn 
         OUTPUT_HIGH(PIN_A0);       //Turn on output A0
      else                          //If not
         OUTPUT_LOW(PIN_A0);        //Turn off output A0
         
    if(buffer_loaded) {             //Here we will made the buffer process
                                    //If the byte1 is 0x90, and there is no
                                    //turned note, and the A1 pin is on 
       if((buffer[0] == 0x90) & !(noteOn) & INPUT(PIN_A1)) {
         period = notas[buffer[1]]; //Load the note period
         tON = (0xFFFF-period)*0.1; //Made the tON be 10% of total period
         noteOn = 1;                //Set the noteOn to 1
         OUTPUT_HIGH(PIN_A2);       //Put the A2 Output on, for BUSY Flag
         loadedNote = buffer[1];    //Store the Note number
       }else if(buffer[0] == 0x80) {//If it is 0x80 the note should turn off
       if(buffer[1] == loadedNote) {//Verify if the note that should be turned off
                                    //is the same that is playing
            period = 0xFFFF;        //Reset the period
            noteOn = 0;             //Turn off note
            OUTPUT_LOW(PIN_A2);     //Turn off the A2 Output, PIC is not Busy
            loadedNote = 0x00;      //Zero the loaded Note
       }
       }else if(buffer[0] == 0xB0) { //Shutdown all notes
         period = 0xFFFF;           
         noteOn = 0;                
         OUTPUT_LOW(PIN_A2);        
         loadedNote = 0x00;
         buffer_counter = 0;
         buffer_loaded = 0;
       }
       buffer_loaded = 0;           //Release the buffer for loading
    }
   }
}

With that we have the complete code!

The interface is pretty simple too, lets see.

PIC Interface para MIDI

For a monophonic interrupter we can put the Enable at Vcc and ignore the Busy. For a Polifonic Interrupter we can put the First PIC Enable at Vcc, and for others, we put the Enable on the last Busy Output. The MIDI inputs it will put togheterand the ouputs will be or-ed:

PIC Interface para MIDI Polifonica

The OR Gate, you can use diodes for do that:

OR Gate com Diodos

 

Full Source: MIDI INT.rar ou Project Source

Credits for the serialzing microprocessors to polyphonic is to uzzors2k - http://uzzors2k.4hv.org/index.php?page=midiinterrupter