7: UART and MIDI

MIDI or Musical Instrument Digital Interface, is the standard to send control messages between electronic instruments to the extent that almost every modern keyboard comes with a set of MIDI ports. MIDI messages can contain note values, on/off times, control changes, instrument identifiers, timing info, and more but never the actual sounds themselves. Hence why it might not have worked out so well when you tried to play that MIDI file through iTunes…

Slideshow:
Fullscreen:
Download:

/*  ==================================================================
 *  UBW32 Tutorial 7 - MIDI Record/Playback: UART & MIDI
 *  ==================================================================
 *
 *      Patrick Estabrook and Sean Klaiber
 *      March/April 2011
 *  updated August 2011 for UBW32 with no external power source
 *
 *  Objective:
 *      - Record MIDI data through UART2's RX pin (RF4) when a switch is
 *              HIGH on pin RE4. Then play back the MIDI data through UART2's
 *              TX pin (RF5) when a different switch is HIGH on pin RE5.
 *              Note also that since many MIDI devices expect 5V, we'll
 *              use 'open drain mode' for a 5V output.
 *
 *  Tutorial covers:
 *      - Sending and receiving with UART
 *      - Using the ODC SFR's for 5V output
 *      - MIDI hardware
 *
 *  ================= NOTES on MIDI Record/Playback =================
 *      This is lab D from ECE353 using the UBW32.
 *
 *      MIDI (Musical Instrument Digital Interface) is a standard most
 *      often used facilitate communication between electronic instruments.
 *      It's designed so any functionality on an electonic instrument can
 *      be performed with a MIDI message. The simplest of which is a 'note
 *      on' and 'note off' message. Note that MIDI data doesn't actually
 *      have any predefined sound - the instrument itself must make the sound.
 *
 *      MIDI Technical:
 *       - Baud rate: 31.25k
 *       - idles HIGH, one LOW start bit, 8 data bits, one LOW stop bit,
 *         least significant bit first
 *              - this is standard 8N1
 *       - first data bit in each byte describes message type
 *              - if '1', STATUS byte
 *                      - these describe how to deal with DATA bytes
 *                      - ex. Note on, Note off, pitch bend, etc.
 *              - if '0', DATA byte
 *                      - these send data expected after STATUS byte
 *                      - ex. note value, volume, pitch bend amount, etc.
 *  
 *      When pin RE4 goes HIGH, the device enters "record" mode and records
 *      the incoming MIDI bytes (UART2's RX pin RF4) and the amount of time
 *      elapsed between them. Once pin RE4 goes back to LOW, "record" mode ends.
 *
 *  When pin RE5 goes HIGH, any MIDI data recorded will be transmitted
 *      through UART2's TX pin (RF5) with the same timing as when recorded.
 *      Since many MIDI devices don't work well with 3.3V, the TX pin is set
 *      to open-drain mode using the ODCx control registers (ODCF for PortF
 *      in our case) and connected to a pull-up resistor and 5V supply from
 *      the UBW32. A 20k pull-up resistor is used but other values work fine.
 *     
 *      The MIDI circuits used are fairly standard and can be found at many sources.
 *
 *      This code actually stores any bytes received at 31.25kbaud and can easily
 *      be modified to store and playback any data at any other allowable baud rate.
 *
 *  Note that the power rails in the MIDI circuit are all 5V and not 3.3V.     
 */
 
#include <p32xxxx.h>
#include <plib.h>
 
// set the values of the fuses, FPB=80MHz
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_2, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config POSCMOD = XT, FNOSC = PRIPLL, FPBDIV = DIV_1
 
// This is the value (in Hz) of the system clock, in this case, 80MHz
#define SYS_FREQ                                (80000000L)                    
 
// Let compile time pre-processor calculate the PR1 (period)
#define PB_DIV                  8
#define PRESCALE                256
#define TOGGLES_PER_SEC         1
#define T1_TICK                 (SYS_FREQ/PB_DIV/PRESCALE/TOGGLES_PER_SEC)
 
// Set smallest unit (us) sampling/playback can be
// Note: greater = less memory and less accuracy
#define DELAY_COEF                      5
 
#define DESIRED_BAUDRATE        (31250)      // The desired BaudRate
 
 
// This array holds each byte received during "record" mode
// NOTE: For every variable array, the MPLAB C32 linker stores the
//  initialization value in flash memory AND reserves the same number
//  of bytes in RAM. For this array, a total of 6000 bytes are used!
char MIDI_data[3000] = { 0 };
 
// The time between each byte received is recorded in an integer
//   which is then stored in this array.  To record the time elapsed
//   between bytes received, a 32-bit timer is set to zero initially.
//   Then, after counting up to a certain small value of time, the
//   32-bit timer is reset again.  The number of times the 32-bit timer
//   is reset is counted by the variable timeElapsed, which is then
//   stored in the Timing_data array.  Thus each integer in this
//   array corresponds to the number of times the 32-bit timer is reset
//   while waiting for the next byte.
unsigned int Timing_data[3000] = { 0 };
 
int main()
{
    int     pbClk;                                                  // holds the frequency of the Peripheral Bus clock
    unsigned char incomingMIDIbyte;             // holds the value of the incoming MIDI byte
    unsigned int MIDI_data_index = 0;   // keeps track of the current location in the MIDI_data array
    unsigned int Timing_data_index = 0; // keeps track of the current location in the Timing_data array
    unsigned int timeElapsed = 0;               // holds the value of the time elapsed between bytes received
    int pbClk_us;
    int i,j;
    int playback_complete_flag = 0;
    int playback_timing_index;
    int reset_enable_flag = 0;
 
    // this function returns the frequency of the Peripheral Bus clock (used for baud rate calculation)
    pbClk=SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
 
    // Open UART2 with Receive and Transmitter enable.
    OpenUART2(UART_EN | UART_1STOPBIT | UART_NO_PAR_8BIT, UART_RX_ENABLE | UART_TX_ENABLE, pbClk/16/DESIRED_BAUDRATE-1); 
 
    // Set delay times
    pbClk_us = pbClk/1000000; // wait for 'delay' clock cycles to equal 1 us
 
    // Open Timer 23 with period 0xffff ffff
    OpenTimer23( T23_ON | T23_SOURCE_INT | T2_32BIT_MODE_ON | T23_PS_1_1 , 0xFFFFFFFF);
 
    // Configure indicator LED pins as outputs and switch pins as inputs
    LATE = 0xFFFC; TRISE = 0xFFFC;
 
    // Put F5 in open drain mode so 5V output can be driven
    // Make sure to connect pull-up resistor to 5V supply
    ODCFbits.ODCF5 = 1;
 
    // Infinite while loop, analogous to the loop() method that Arduino users are famliar with
    while ( 1)
    {
        // ==================================
        // RECORD mode
        // ==================================
 
        // The device enters record mode when pin RE4 goes high
        // Every incoming MIDI byte is recorded, along with the amount of time elapsed
        //       between them.
 
        while (PORTEbits.RE4) // PORTEbits.RE4 is equal to 1 if RE4 is HIGH, 0 if RE4 is LOW
        {
            LATEbits.LATE0 = 1; // turns on the red "record" mode LED connected to RE0
 
            if( reset_enable_flag==1 )
            {
                // Reset MIDI array
                for( i=0 ; i<MIDI_data_index ; i++ )
                {
                    MIDI_data[i] = 0;
                    Timing_data[i] = 0;
                }
 
                // start recording from the beginning of the array
                MIDI_data_index=0;
                Timing_data_index=0;
                reset_enable_flag=0;
            }
 
            // Wait until incoming MIDI data is received
            while( !DataRdyUART2() && PORTEbits.RE4 );
 
            // store incoming MIDI data to MIDI_data[]
            if (DataRdyUART2())
            {
                incomingMIDIbyte = (char)ReadUART2(); // Read data from Rx.
 
                MIDI_data[MIDI_data_index] = incomingMIDIbyte;
                MIDI_data_index++;
            }      
 
            // wait until the next MIDI message
            timeElapsed = 1;
            while(!DataRdyUART2() && PORTEbits.RE4)
            {
                WriteTimer23(0);
                while( ReadTimer23() < (DELAY_COEF * pbClk_us) ){};
                timeElapsed++;
            };
 
            Timing_data[Timing_data_index] = timeElapsed;
            Timing_data_index++;
 
        }// END while( record switch is HIGH )
 
        // set reset_enable flag so data can be erased next time record switch is HIGH
        reset_enable_flag=1;
 
        // turn off red LED to indicate no longer in RECORD mode
        LATEbits.LATE0 = 0;    
 
        // ==================================
        // PLAYBACK mode
        // ==================================
        if( PORTEbits.RE5 && playback_complete_flag==0 ) // while
        {
            // turn on the green LED
            LATEbits.LATE1 = 1;
 
            // place-keeping variable for the timing data array
            playback_timing_index=0;
 
            // set 'i<MIDI_data_index' and playback will loop
            for (i = 0; i<=MIDI_data_index; i++)
            {
 
                // break if all MIDI notes played
                if( i >= MIDI_data_index )
                {
                    playback_complete_flag = 1;
                    break;
                }
 
                // wait for the UART to be free
                while( BusyUART2() && PORTEbits.RE5 );                 
 
                // Send MIDI message
                putcUART2(MIDI_data[i]);
 
                // Pause between MIDI messages
                for( j=0;j<Timing_data[playback_timing_index];j++ )
                {
                    WriteTimer23(0);
                    while( ReadTimer23() < (DELAY_COEF * pbClk_us) );
 
                    // 'break' for loop when switch LOW
                    if( !PORTEbits.RE5 )
                        break;                         
                }
 
                // 'break' larger for loop when switch LOW
                if( !PORTEbits.RE5 )
                    break;
 
                playback_timing_index++;
            } // end for loop for all MIDI messages
        } // end if loop for playback
 
        LATEbits.LATE1 = 0;
 
        // if playback is reset by switching the playback switch to 'off'
        if( !PORTEbits.RE5 && playback_complete_flag==1 )
            playback_complete_flag = 0;
 
    }// END infinite loop
 
    return 1;
}// END main()

Down low -> pic32_tut7_uart_midi.c

Leave a Reply

Your email address will not be published. Required fields are marked *