I've played with sending MIDI from PICs a lot - that being the essence of my Virtual Organ Project; both in generating Note On / Note off messages from my MIDI bass pedals and various Control Change messages from organ drawbars, switches and other controls. But MIDI Tx is easy. It turns out that receiving MIDI is only slightly more tricky but interpreting and acting on MIDI messages is a whole lot tougher!
In the aerial photograph above, you can see the new MIDI elements at top right. There's another Breadboard Module with the obligatory 5-pin 180 degree DIN socket. There's also a little bit of electronics to squeeze MIDI into the PIC. MIDI is a current loop interface and needs an opto-isolator to terminate the Rx end correctly. I had some 4N25s in the junk box, but couldn't get these to work sweetly. Fortunately, I also had some CNW 136s and these worked very well.
Here's my MIDI Rx schematic...
After this simple circuit, the MIDI signal goes to the Rx pin on the 16F887 at the heart of my Digital TS. Of course, masochists can use bit-banging methods to receive the serial MIDI data (just as I continue to use Ross Bencina's code for sending MIDI in the Virtual Organ). However, for MIDI reception, you really need to use the PIC's UART (or EUSART, as Microchip insist on calling it).
There are any number of experts out there who will explain how to set up the UART for MIDI reception (standard 8-bit at 31250 baud). They will have you looking at the RCIF flag bit of the PIR1 register to see when data has arrived. You'll soon appreciate that you need to do this in an interrupt service routine, such that you can get on and do other things in the meantime (such as servicing the local control interface with its buttons and display). After some messing around, you'll get a MIDI stream into your controller - but what do you do with it?
The hard part isn't the reception of MIDI data (despite what you may think if you're struggling to get it going). The hard part is making your system react to it. Here's how I went about it...
Currently I have four "controls" in the digital tube screamer, matching exactly the user controls of the original guitar effect pedal. These controls are 'Drive', 'Tone', 'Level' and 'Bypass'. I want to be able to send values for each of these controls via MIDI from a control device.
MIDI has a feature exactly matched to this requirement (no surprise, since this is exactly what MIDI was conceived for), called "Control Change Messages". These messages identify a particular control on a particular MIDI channel (together constituting an "address" for that control) and pass it a numerical value (an integer between 0 and 127). It is the job of the receiving hardware to understand the message and to apply the value to the control (equivalent in this case to setting the position of one of the knobs on the Tube Screamer or operating its Bypass switch).
Here's my interrupt service routine which does the job...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;===================================================================================== | |
; PIC 16f887 (etc) Interrupt Service Routine | |
; to parse a MIDI stream for control change messages of format... | |
; b'1011nnnn', b'0ccccccc', b'0vvvvvvv' | |
; where | |
; nnnn is the MIDI channel number-1 (0:15) | |
; ccccccc is the control number (0:127) | |
; vvvvvvv is the control value (0:127) | |
; | |
; MIDIST is a byte of flags indicating MIDI State | |
; MIDI_Ch is the MIDI channel-1 (nnnn), stored as a variable | |
; LvlCtl, TonCtl, DrvCtl & BypCtl are control numbers (ccccccc), defined as constants | |
; | |
; These controls were specific to my MIDI Tube Screamer Application - | |
; you can modify / add to suit your own project | |
; | |
; m0xpd | |
; shack.nasties "at gee male dot" com | |
; http://www.m0xpd.blogspot.com | |
; January 2013 | |
; | |
; Terms of use: | |
; Tell me if you like it, | |
; Mention me if you use it, | |
; Thank me if you sell it, | |
; Don't blame me if it doesn't work. | |
;===================================================================================== | |
IntSR | |
movwf _w ; Save the Current Values | |
swapf STATUS, w | |
movwf _status | |
bcf STATUS, RP0 ; Make Sure We're in Bank 0 | |
; | |
call MIDIRx ; get MIDI byte (saved in RX_TEMP) | |
; | |
;================================================================== | |
; Parse MIDI Stream for valid Control Change message... | |
;================================================================== | |
; Check the MIDIST Byte... | |
btfsc MIDIST,7 ; was last MIDI byte a Control Change? | |
goto Control ; YES - so go deal with it | |
; NO - but is THIS byte Control Change? | |
movf MIDI_Ch,w ; get the MIDI Channel | |
addlw 0xB0 ; add the Control Change prefix | |
subwf RX_TEMP ; compare to the current MIDI byte... | |
btfss STATUS,Z ; is it CC on the correct Channel? | |
goto NoCC ; No it isn't | |
clrf MIDIST ; Yes, so put 0x80 into MIDIST | |
bsf MIDIST,7 ; to set the "CC" Flag | |
goto Idone ; and wait 'til next byte | |
NoCC | |
clrf MIDIST ; Not a CC - so reset MIDI state | |
goto Idone ; and wait for next byte | |
Control | |
movf MIDIST,w ; If we get here, CC has been set... | |
andlw 0x7F ; is any other flag set? | |
btfss STATUS,Z | |
goto setMIDIControl ; Yes - so this byte is the | |
; control value - go deal with it | |
movf RX_TEMP,w ; No - see if byte is a valid Control... | |
sublw LvlCtl | |
btfss STATUS,Z ; is it Level ? | |
goto Control2 ; No - try next... | |
bsf MIDIST,6 ; Yes - set Level Flag | |
goto Idone ; and wait til next MIDI byte | |
Control2 | |
movf RX_TEMP,w | |
sublw DrvCtl | |
btfss STATUS,Z ; is it Drive ? | |
goto Control3 ; No - try next... | |
bsf MIDIST,5 ; Yes - set Drive Flag | |
goto Idone ; and wait til next MIDI byte | |
Control3 | |
movf RX_TEMP,w | |
sublw TonCtl | |
btfss STATUS,Z ; is it Tone ? | |
goto Control4 ; No - try next... | |
bsf MIDIST,4 ; Yes - set Tone Flag | |
goto Idone ; and wait til next MIDI byte | |
Control4 | |
movf RX_TEMP,w | |
sublw BypCtl | |
btfss STATUS,Z ; is it Bypass ? | |
goto ControlFail ; No - give up... | |
bsf MIDIST,3 ; Yes - set Tone Flag | |
goto Idone ; and wait til next MIDI byte | |
; | |
; add in code to handle three more Flags in MIDIST here, if needed | |
; or use one of them to handle a System Exclusive message | |
; | |
ControlFail | |
clrf MIDIST ; No valid control received | |
goto Idone ; so reset and wait... | |
setMIDIControl | |
; We have a Control Flag set - | |
; find which one it is... | |
btfsc MIDIST,6 ; it is Level? | |
goto setMIDILevel ; | |
btfsc MIDIST,5 ; it is Drive? | |
goto setMIDIDrive | |
btfsc MIDIST,4 ; it is Tone? | |
goto setMIDITone | |
btfsc MIDIST,3 ; it is Bypass? | |
goto setMIDIBypass | |
setMIDIControlFail | |
clrf MIDIST ; No valid control received | |
goto Idone ; so reset and wait... | |
setMIDILevel | |
; Set the Level control | |
; Change these lines to do | |
; something different in your application | |
; but make sure it doesn't take too long | |
; or you may miss the next MIDI byte | |
movf RX_TEMP,w | |
movwf M3_Opt | |
call SetLevl | |
; If you changed stuff, pick up here... | |
clrf MIDIST ; clear MIDI State | |
goto Idone ; and exit | |
setMIDIDrive | |
; Set the Drive control | |
movf RX_TEMP,w | |
movwf M1_Opt | |
call SetDrive | |
clrf MIDIST ; clear MIDI State | |
goto Idone ; and exit | |
setMIDITone | |
; Set the Tone control | |
movf RX_TEMP,w | |
movwf M2_Opt | |
call SetTone | |
clrf MIDIST ; clear MIDI State | |
goto Idone ; and exit | |
setMIDIBypass | |
; Set the Bypass State | |
movf RX_TEMP,w | |
btfsc RX_TEMP,6 ; If control value >63 | |
goto Turn_On ; branch | |
; otherwise | |
bsd 0x03 ; turn effect off | |
; 'bsd' and 'bcd' are MACROS which set and clear | |
; PORTD bits safely | |
; (DON'T use bsf or bcf on a PORT!!!) | |
goto setMIDIBypass_Done | |
Turn_On | |
bcd 0x03 ; turn effect on | |
setMIDIBypass_Done | |
clrf MIDIST ; clear MIDI State | |
goto Idone ; and exit | |
Idone | |
swapf _status, w ; Return from Interrupt with Valid Regs | |
movwf STATUS | |
swapf _w | |
swapf _w, w | |
retfie | |
; | |
; End of Interrupt Service Routine | |
;======================================================================== |
I tested my MIDIfied Tube Screamer by generating MIDI using the superb MIDI-OX software. I guess the next step is to make a little controller to replace the computer and MIDI-OX.
Wait a minute - wouldn't it have been easier to just put knobs on the Tube Screamer? Now there's a thought...
...-.- de m0xpd
No comments:
Post a Comment