Site hosted by Angelfire.com: Build your free website today!
Spider RooM Labs

MIDI
 
 
 
 

MIDI protocol

Kind: Serial
Frequency: 31.25 kHz
Logic levels: "1" = 5V ; "0" = 0V
High on idle, positive logic

MIDI Byte structure:
1 start bit (low)
8 message bits (least significant bit is transmitted first)
1 stop bit (high)
- - - - - - - - -
Bits total: 10
Time used per byte: 320 us


 

Information on MIDI messages can be found here...
 
 

PIC microcontrollers algorithms for MIDI receive

Since low end PIC micros don't have onboard UARTs, handling the asynchronous serial ports/MIDI have to be done with software implemented shift register. The shift register has to shift its data on each bit; for MIDI that means every 32 us. Getting the timing right can be done with three different methods, where each has its own advantages and disadvantages:

1.) Continuous looping on 32us
    This method is the most time efficient of all three, but it is quite demanding to implement since you have to trace every program branch and calculate the time that is used in it. Further on, on MIDI receive there has to be synchronization procedure at start bit to prevent the program of faulty bit reads due to MIDI input's optocoupler rise/fall times. Still, it is the most useful in less demanding systems (check out my DINer project's source code; that project could not run from 4MHz if I'd use some other method).

2.) Interrupt on TMR0 overflow
    This method is similar to the one above; the difftakes some more time due to various register reinitialization and the fact that in this case you always start from the same point and you have to figure out what ierence is that PIC in this case handles the timing by itself, so you don't have to worry about it. On the other hand this method s the current status of the MIDI lines (idle, start bit, one of message bits, stop bit). It also suffers from start bit synchronization problem, which in this case can't be corrected. It is useful for high demanding systems, with simultaneous receving and transmiting.

3.) Interrupt on RB0 pin change
    This method is suitable for receiving only. MIDI input has to be connected to RB0 (pin 0 of PortB i.e. pin 6 on DIL chip). It has best synchronization, while it is least time effective (microcontroller can return from interrupt only when it has red the whole byte). Like the first method, this is useful in less demanding systems.

Code examples

Note: Below are just procedures for MIDI byte receiving/transmiting without any variable declarations and register initialization. To make procedures work you'll have to add that yourself. Also, instead of setting the correct timing in the code I have just put CALL DELAY commands in the code. This routines are to be considered not to be the same hence the same name. Important lines (commands) have "[number]" in their comments; I'll refer to those numbers in additional explanations.
    If you are not familiar with commands, please download a datasheet for PIC16F84 from Microchip's homepage and check the Instruction set chapter.

This chunks of code are free for use in non-commercial projects. If you'll use them, please mail me so I'll know writing this wasn't just a waste of time. Thanks!
 

MIDI receiving examples

Continuous looping example (1. variation)

;----------------
;START OF CODE
;----------------
 

MIDI_BYTE_RECEIVE
IDLE                       ; Program loops here when MIDI is at rest
        BTFSS    MIDI_INPUT  ; [1] Check for low state of MIDI line, which means start bit
        GOTO     START_BIT ; Start bit in progress, let's move to label START_BIT
        CALL     DELAY     ; Basically you can do some tasks instead of delay here,
                           ;   just make sure you don't exceed the 32us with them.
                           ; If you don't have any tasks to perform, you can also
                           ;    delete the delay call and use the code as is,
                           ;    which will give better start bit synchronization
        GOTO     IDLE      ; Return to IDLE 32us after the previous reading

;
START_BIT
        MOVLW    8         ; Initialize MIDI_BIT variable which represents
        MOVWF    MIDI_BIT  ;   the number of bits that you have to receive
        CALL     DELAY     ; [2] Delay to get the timing correct

READ_MIDI                  ; This is the fun part. Shift register kicks in here
        BSF      CARRY     ; Assume the MIDI line will be high for next command
        BTFSS    MIDI_INPUT ; [3] Is it really high?
        BCF      CARRY     ; If the MIDI line was low we correct our assumption
        RRF      MIDI_BYTE,F ; This line represents shift register. It moves the
                           ;    carry bit to current MSB of MIDI_BYTE and shifts
                           ;    all bits to lower place; LSB shifts to carry and
                           ;    gets lost with next READ_MIDI cycle
                           ; The first bit which gets to MIDI_BYTE becomes LSB
                           ;    after 8 READ_MIDI cycles, which is correct, since
                           ;    MIDI sends LSB first.
        DECF     MIDI_BIT,F ; Decrement MIDI_BIT variable
        BTFSC    ZERO      ; if MIDI_BIT is zero then the stop bit will follow
        GOTO     BYTE_END  ; So the MIDI_BIT is zero; jump from READ_MIDI follows
        CALL     DELAY     ; [4] Another delay or task for timing adjustment
        GOTO     READ_MIDI ; Go back again

BYTE_END                   ; Stop bit is in progress and this is ideal time to
        CALL     TASK      ; do something with MIDI byte which we red
        GOTO     IDLE      ; Return to IDLE
                           ; It isn't necessary to have correct timing here, since
                           ; MIDI line should be high anyway, just make sure that
                           ; task doesn't exceed 32us
;------------
;END OF CODE
;------------

Notes:
    Make sure that there is exactly 32us between [1] and [3] (adjust timing with lenght of [2])and also that READ_MIDI section is 32us long (adjust with [4]).

Timing calculation equation (universal for any asynchronous serial protocol):
    Number_of_machine_cycles = XTAL_frequency [Hz] / (4*Baud_rate [bit/s])        where Baud_rate=31250

Common quartz frequencies vs. number of machine cycles:
    4 MHz -> 32
    8 MHz -> 64
   10 MHz -> 80 etc.

Continuous looping example (2. variation)
    This variation is used in my DINer project. The code is similar to the one above, there is just a bit of alteration to gain some machine cycles for the BYTE_END section, which allows more time consuming tasks there. I will only write a READ_MIDI section and MIDI_BIT variable initialization in START_BIT section here, other things are the same:

;----------------
;START OF CODE
;----------------

START_BIT
        MOVLW    9         ; The value is increased since MIDI_BIT is decremented
        MOVWF    MIDI_BIT  ;   on start of READ_MIDI section

;... see 1. variation

READ_MIDI
        DECF    MIDI_BIT,F ; Decrement MIDI_BIT
        GOTO    BYTE_END   ; Go to BYTE_END section
        BSF      CARRY     ; Assume the MIDI line will be high for next command
        BTFSS    MIDI_INPUT ; Is it really high?
        BCF      CARRY     ; If the MIDI line was low we correct our assumption
        RRF      MIDI_BYTE,F ; This line represents shift register. It moves the
                           ;    carry bit to current MSB of MIDI_BYTE and shifts
                           ;    all bits to lower place; LSB shifts to carry and
                           ;    gets lost with next READ_MIDI cycle
                           ; The first bit which gets to MIDI_BYTE becomes LSB
                           ;    after 8 READ_MIDI cycles, which is correct, since
                           ;    MIDI sends LSB first.
        CALL    DELAY      ; Adjust timing
        GOTO    READ_MIDI  ; Return

;------------
;END OF CODE
;------------

TMR0 interrupt example
    I'll assume this is the only enabled interrupt (if not, check the datasheets for all adjustments).

;----------------
;START OF CODE
;----------------
        ORG    H'0004'
INTERRUPT_VECTOR           ; Program jumps here when the interrupt occurs
        BCF      INTCON,T0IF ; Clear TMR0 interrupt flag
        MOVLW    TMR0_OFFSET ; [1] Add TMR0_OFFSET (constant) to
        ADDWF    TMR0,F    ;      TMR0 value (see note below)

MIDI_BIT_RECEIVE           ; Here we read one MIDI bit on each interrupt
        MOVF     MIDI_BIT,F ; Check if MIDI_BIT is zero; if it is, that
        BTFSC    ZERO      ;    means that MIDI line is idle (high)
        GOTO     MIDI_IN_START? ;  Check if that is true...
;
                           ; We get here if there is a byte reading in progress
        DECF     MIDI_BIT,F  ; Decrement bit counter
        BTFSC    ZERO      ; Check for byte end
        GOTO     BYTE_END  ; MIDI_BIT was zero, so jump to BYTE_END
;
        BCF      CARRY     ; This part is the same as the one described at
                           ;    continuous loop method
        BTFSC    MIDI_INPUT ; [2]...
        BSF      CARRY     ;  ...
        RLF      MIDI_BYTE,F ;
        RETFIE             ; Return from interrupt

MIDI_IN_START?             ; Check for start bit
        CLRW               ; Assume MIDI line is high - try to leave MIDI_BIT on zero
        BTFSS    MIDI_INPUT ; [3] Check if MIDI is low
        MOVLW    9         ; There is start bit, now set MIDI_BIT to 9
        MOVWF    MIDI_BIT  ;
        RETFIE             ; Return from interrupt

BYTE_END
        CALL    TASK       ; Do something with MIDI byte, or just make indication
                           ; that MIDI byte was received (e.g. set some specific bit)
                           ; The task must not exceed 32us
        RETFIE             ; Return from interrupt

;------------
;END OF CODE
;------------

Notes:
    Make sure that MIDI line is red at the exactly the same time in both cases - in the MIDI_BIT_RECEIVE section (line [2]) or in MIDI_IN_START? section (line [3]) - depeneding on the start of the interrupt.

TMR0_OFFSET constant calculation:
    TMR_OFFSET = 258 - Number_of_machine_clocks         (see continuous looping example,1. variation)

You may wonder why 258 is there, it should be 256. But if you check the timer write chart in datasheets, notice that the PIC takes two machine clocks to actually write the new value into timer.

You can also use command 'MOVLW -Number_of_machine_clocks+2' (note the minus in front!) instead of 'MOVLW TMR0_OFFSET'.

RB0 change interrupt example
    I'll assume this is the only enabled interrupt (if not, check the datasheets for all adjustments).

;----------------
;START OF CODE
;----------------

        ORG    H'0004'
INTERRUPT_VECTOR           ; Program jumps here when the interrupt occurs
        BCF      INTCON,INTF ; Clear RB0 interrupt flag
        MOVLW    8         ; Initialize MIDI_BIT variable which represents
        MOVWF    MIDI_BIT  ;   the number of bits that you have to receive
        CALL     DELAY     ; Delay for a bit more than 32us, to make sure
                           ;   you won't get misreading because of MIDI line
                           ;   rise/fall times

; Further code is the same as at continuous looping (with all the notes that go
; with it), just instead of "GOTO IDLE" at the BYTE_END section is 'RETFIE'.
;Also both variation from there are possible to implement. Here is only first one shown...

READ_MIDI                  ; This is the fun part. Shift register kicks in here
        BSF      CARRY     ; Assume the MIDI line will be high for next command
        BTFSS    MIDI_INPUT ; Is it really high?
        BCF      CARRY     ; If the MIDI line was low we correct our assumption
        RRF      MIDI_BYTE,F ; This line represents shift register. It moves the
                           ;    carry bit to current MSB of MIDI_BYTE and shifts
                           ;    all bits to lower place; LSB shifts to carry and
                           ;    gets lost with next READ_MIDI cycle
                           ; The first bit which gets to MIDI_BYTE becomes LSB
                           ;    after 8 READ_MIDI cycles, which is correct, since
                           ;    MIDI sends LSB first.
        DECF     MIDI_BIT,F ; Decrement MIDI_BIT variable
        BTFSC    ZERO      ; if MIDI_BIT is zero then the stop bit will follow
        GOTO     BYTE_END  ; So the MIDI_BIT is zero; jump from READ_MIDI follows
        CALL     DELAY     ; Another delay or task for timing adjustment
        GOTO     READ_MIDI ; Go back again

BYTE_END                   ; Stop bit is in progress and this is ideal time to
        CALL     TASK      ; do something with MIDI byte which we red
        RETFIE             ; Return from interrupt (NOTE THIS!!!)
                           ; It isn't necessary to have correct timing here, since
                           ; MIDI line should be high anyway, just make sure that
                           ; task doesn't exceed 32us

MIDI transmiting examples

Coming soon...


(C) 1999 Bojan Burkeljc
Last update: 18. October 1999