;MIDI Program Change to Analog Switch Trigger Box on PIC 16F84 ;Important Note: This is the 4 Mhz Version ;Written by Jimkim on 01/2001 ; ;Version 1.00 ; ;F_osc=4MHz list p=16f84 __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF include "p16f84.inc" ;----------------------------------------------------------------------------- ; PIC16C84 PRIVATE REGISTERS ;----------------------------------------------------------------------------- status equ 03 ; status: flags, page register, ... porta equ 05 ; status of the port A's pins trisa equ 05 ; (page 1) port A's IN/OUT direction portb equ 06 ; status of the port B's pins trisb equ 06 ; (page 1) port B's IN/OUT direction eecon1 equ 08 eecon2 equ 09 eedata equ 08 eeadr equ 09 ;----------------------------------------------------------------------------- ; REGISTERS USED IN ROUTINES/PROCEDURES ;----------------------------------------------------------------------------- recv equ 0C ; Receive Data holding register flags equ 0D ; Register holding Flagbits indicating framing error and memory info pgm_chg equ 0E ; Register containing Program Change value i_count equ 0F ; Counter variable used in delay routine y_count equ 10 ; Counter variable used in delay routine z_count equ 11 ; Counter variable used in delay routine byte_length equ 12 ; Length of byte to be received bit_count equ 13 ; Bit counter for receive procedure time equ 14 ; Counter variable used in receiver delay routine time_rs equ 15 ; Counter variable used in serial data transmit routine out_data equ 16 ; Register containing the output switch values midi_ch equ 17 ; Register containing Midi Channel setting data_rs equ 18 ; Register containing Data for serial communication data_rs_1 equ 19 ; Register containing Data1 for serial communication data_rs_2 equ 1A ; Register containing Data2 for serial communication ;----------------------------------------------------------------------------- ; SIGNIFICATIVE BITS OF PIC16F84 PRIVATE REGISTERS ;----------------------------------------------------------------------------- carry_bit equ 0 ; (status) carry/borrow bit zero_bit equ 2 ; (status) zero bit page_bit equ 5 ; (status) selector of register page ;----------------------------------------------------------------------------- ; CONSTANTS ;----------------------------------------------------------------------------- W equ 0 ;indicates Work Register as destination for the instruction F equ 1 ;indicates File Register as destination for the instruction RP0 equ 5 ;status selector of register page ;----------------------------------------------------------------------------- ;Instructions start here ;----------------------------------------------------------------------------- org 0 goto start org 4 goto int_serv ;on interrupt goto interrupt service routine ;----------------------------------------------------------------------------- ;Support routines ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;Routine for transmitting serial data via PortB, 2 ;Data rate is 9600 baud ;this routine can be used to transmit serial data to a computer for test purposes ;Note that logic is inverted ;----------------------------------------------------------------------------- send_rs movlw 08h ;load counter for 8 bits movwf z_count ;into counter bsf portb, 2 ;send start bit call wait_rs ;call delay for serial routine loop_rs btfsc data_rs, 0 ;check serial data register goto clear_rs goto set_rs set_rs bsf portb, 2 ;send low bit (logic inversion) call wait_rs ;call delay goto continue_rs ;and go on clear_rs bcf portb, 2 ;send high bit (logic inversion) call wait_rs ;call delay goto continue_rs ;and go on continue_rs rrf data_rs, 1 ;rotate serial data register by one decfsz z_count, 1 ;check bit counter goto loop_rs ;if > 0 then go on bcf portb, 2 ;else send stop bit call wait_rs ;wait some cycles call wait_rs ;wait some more cycles return ;----------------------------------------------------------------------------- ;delay routine for serial output ;----------------------------------------------------------------------------- wait_rs movlw 1Dh ;load delay for serial data transmit movwf time_rs ;load into counter wloop_rs decfsz time_rs, 1 ;decrease and check goto wloop_rs ;if > 0 then go on return ;else return ;----------------------------------------------------------------------------- ;delay -- entry with number of ms in w (1 to 255) ;1 cycle = 1 us (MicroSeconds) with F_osc = 4 MHz ;----------------------------------------------------------------------------- delay200 movlw .200 ;Enter here for a 200ms delay movwf y_count ;/ delay200_1 movlw .124 ;load counter z movwf z_count delay200_2 nop ;((N - 1) x 8) + 7 nop nop nop nop decfsz z_count,F goto delay200_2 nop nop decfsz y_count,F goto delay200_1 retlw 0 ;return and set Work Register = 0 ;----------------------------------------------------------------------------- ;delay routine for midi receive procedure (19 cycles) ;----------------------------------------------------------------------------- midi_delay movlw 5 ;load delay time = 1cycle movwf time ;into counter = 1cycle loop_m_dly decfsz time,1 ;decrease time (4 * 3) + 2 = 14 cycles goto loop_m_dly ;loop nop ;nop = 1cycle return ;and return = 2cycles, 19 cycles altogether ;----------------------------------------------------------------------------- ;general purpose delay routine (25 cycles) ;----------------------------------------------------------------------------- delay25 movlw 7 ;load delay time = 1cycle movwf time ;into counter = 1cycle loop_dly25 decfsz time,1 ;decrease time (6 * 3) + 2 = 20 cycles goto loop_dly25 ;loop nop ;nop = 1cycle return ;and return = 2cycles, 19 cycles altogether ;----------------------------------------------------------------------------- ;midi_in -- receive a midi byte at 31250 bps. Detects framing errors ;(i.e. if stop bit was not received) with flag(4) set on error ;----------------------------------------------------------------------------- midi_in bcf flags, 4 ;clear error flag movlw 8 ;load bit counter for 8 bits movwf bit_count ;into counter bsf INTCON, GIE ;enable interrupts midi_check btfsc porta,4 ;Detect start bit goto midi_check ;----------------------------------------------------------------------------- ;Found start...could be as much as 4 cycles late if start happened ;just after the btfsc. Wait 16 cycles (16us) to get ;to center of the start bit. ;----------------------------------------------------------------------------- midi_receive bcf INTCON, GIE ;disable interrupts movlw 3 ;load 1/2 delay time (1cycle) movwf time ;into W (1cycle) midi_loop1 decfsz time, 1 ;decrease counter = ((2 * 3)+2)cycles goto midi_loop1 ;/ nop nop btfsc porta,4 ;still start bit? = 16cycles up to here goto midi_in ;else return and check midi port for next start bit ;----------------------------------------------------------------------------- ;the next procedure takes 10 cycles ;incl. wait procedure in read_p it takes 32 cycles to the ;first data bit of the midi stream ;----------------------------------------------------------------------------- movlw 3 ;decimal 3 into work register movwf time ;load counter midi_loop2 decfsz time, 1 ;decrease counter = ((2 * 3)+2)cycles goto midi_loop2 ; ;----------------------------------------------------------------------------- ;read first data bit of midi stream ;this procedure takes 32cycles (1 bit length) ;incl. midi_delay-procedure which is 19 cycles ;----------------------------------------------------------------------------- read_data call midi_delay ;delay until read of next bit bsf status,carry_bit ;set carry bit btfss porta,4 ;read midi port bcf status,carry_bit ;clear carry bit if midi bit = 0 goto load_recv ; load_recv rrf recv,1 ;rotate carry bit into receiver nop ;the following 2 nops make this routine a length nop ;of 32 cycles which is one bit length of the midi stream decfsz bit_count ;already 8 bits? goto read_data ;if not, go on and read next bit nop ;12 nops/ then check for stop bit of midi stream nop ;the stop bit will not be checked exactly in its middle nop ;but some cycles earlier nop ;this leaves more time for the following instructions nop ;and makes sure that the next midi byte will be received in time nop nop nop nop nop nop nop btfss porta,4 ;Stop bit showed up? bsf flags,4 ;If not, indicate framing error (set Bit4 of flags register) return ;----------------------------------------------------------------------------- ;Main program loop ;----------------------------------------------------------------------------- org 0x0100 start movlw b'00000' ;Preset port A: triggers off (low) movwf porta movlw b'00000000' ;Preset port B: triggers off (low) movwf portb bsf status,page_bit ;Shift to Bank1 of register page movlw b'10000' ;PortA 0-3 outputs, 4 input movwf trisa movlw b'11110011' ;PortB 0,1 inputs, 2,3 outputs, 4-7 inputs movwf trisb bcf status,page_bit ;Shift to Bank0 of register page ;----------------------------------------------------------------------------- ;Step 1: Wait about one second; switch on transistor on PortB3 ;this switches LED and sets DIP Switch input to high level ;Midi Channel settings can now be read on PortB via Dip Switch ;----------------------------------------------------------------------------- movlw b'00000000' ;Trigger Output Port (Transistor on) movwf portb ;/ ;----------------------------------------------------------------------------- ;Check Dip Switch status on PortB 4-7 to determine Midi Channel settings ;Set bits in midi_ch register according to settings ;----------------------------------------------------------------------------- movlw b'11000000' ;load Program Change mask into register (11000000 for Pgm Chg on Midi Channel 0) movwf pgm_chg btfsc portb, 4 ;check state of input RB4 bsf pgm_chg, 0 ;if set then set appropriate bit in pgm_chg register btfsc portb, 5 ;check state of input RB5 bsf pgm_chg, 1 ;if set then set appropriate bit in pgm_chg register btfsc portb, 6 ;check state of input RB6 bsf pgm_chg, 2 ;if set then set appropriate bit in pgm_chg register btfsc portb, 7 ;check state of input RB7 bsf pgm_chg, 3 ;if set then set appropriate bit in pgm_chg register one_sec movlw .5 ;5 x 200ms movwf i_count one_sec_1 call delay200 decfsz i_count,F goto one_sec_1 movlw b'00001000' ;Trigger Output Port (Transistor off) movwf portb ;----------------------------------------------------------------------------- ;initialize interrupt on RB0 ;----------------------------------------------------------------------------- bsf OPTION_REG, INTEDG ;interrupt on positive bcf INTCON, INTF ;clear interrupt flag bsf INTCON, INTE ;mask for external interrupts (Pin RB0) ;----------------------------------------------------------------------------- ;Step 2: Read MIDI data and find Program_Change commands ;----------------------------------------------------------------------------- read_status_byte call midi_in ;Look for status byte btfsc flags,4 ;Continue if the byte received is ok goto read_status_byte ;else check midi port for next status byte movf pgm_chg, W ;Program Change (1100) on selected Midi Channel subwf recv, W ;Pgm_chg command if (recv - Work Reg. = 0) btfss status, zero_bit ;Check zero bit in STATUS Register /if zero_bit=1, goto step3 goto read_status_byte ;else read next staus byte ;----------------------------------------------------------------------------- ;Step 3: Read 2nd MIDI byte and see which key to trigger ;----------------------------------------------------------------------------- read_data_byte movf recv, W ;move received value to data_rs_1 movwf data_rs_1 ;data_rs_1 will later be transmitted via rs232 for test purpose call midi_in ;Look for midi data byte btfsc flags, 4 ;Check error flag, if = 0 (ok) then proceed goto read_status_byte movf recv, W ;move received value to dat2 movwf data_rs_2 ;data_rs_2 will later be transmitted via rs232 for test purpose ;----------------------------------------------------------------------------- ;send received data via serial routine ;data can thus be controlled on a computer, e.g. by using Hyper Terminal ;----------------------------------------------------------------------------- send_data_rs_1 movlw 08h ;load counter for 8 bits movwf y_count loop_data_rs_1 btfsc data_rs_1, 0 ;check data_rs_1 goto send_high_bit_1 ;if 1, then send high bit goto send_low_bit_1 ;if 0 then sned low bit send_low_bit_1 movlw .48 ;decimal 48 = ASCI-Character "0" movwf data_rs ;into serial data register call send_rs ;send character goto continue_data1 send_high_bit_1 movlw .49 ;decimal 49 = ASCI-Character "1" movwf data_rs ;into serial data register call send_rs ;send character goto continue_data1 continue_data1 rrf data_rs_1, 1 decfsz y_count, 1 goto loop_data_rs_1 movlw .1 ;send special ASCI-Character movwf data_rs ;move into serial data register call send_rs ;and send send_data_rs_2 movlw 08h ;load counter for 8 bits movwf y_count loop_data_rs_2 btfsc data_rs_2, 0 ;check data_rs_2 goto send_high_bit_2 ;if 1, then send high bit goto send_low_bit_2 ;if 0 then sned low bit send_low_bit_2 movlw .48 ;decimal 48 = ASCI-Character "0" movwf data_rs ;into serial data register call send_rs ;send character goto continue_data2 send_high_bit_2 movlw .49 ;decimal 49 = ASCI-Character "1" movwf data_rs ;into serial data register call send_rs ;send character goto continue_data2 continue_data2 rrf data_rs_2, 1 decfsz y_count, 1 goto loop_data_rs_2 movlw .20 ;send special ASCI-Character movwf data_rs ;move into serial data register call send_rs ;and send ;----------------------------------------------------------------------------- ;Next procedure checks if Midi Program number is > 63 ;PIC EEPROM Memory has only 64 bytes/locations EEPROM, but 128 4bit Program Changes must be saved ;0 - 63 goes into lower bytes (0-3) of Memory, 64 - 127 goes into higher bytes (4-7) ;so byte must be swapped if program number > 63 ;a flag bit is set if Program number is > 63 (needed for some procedures) ;----------------------------------------------------------------------------- bcf flags, 1 ;reset flag1 that indicates which EEPROM bits are currently used movlw .64 ;move decimal 64 into W bsf status, carry_bit ;clear carry bit subwf recv, W ;recv - W, store result in work register btfsc status, carry_bit ;check carry/ if carry bit clear (Midi Program < 64) skip next instruction goto swap_byte ;go and swap byte goto readee ;go directly to 'readee' procedure (don't swap byte) ;----------------------------------------------------------------------------- ;A flag is set to indicate that Program number is > 63 and that the high bytes of EEPROM ;will be used/ after reading the EEPROM, the byte must be swapped to read and write ;the correct bits ;----------------------------------------------------------------------------- swap_byte bsf flags, 1 ;set flag1 to indicate that high bytes (4-7) of EEPROM will be used movlw .64 ;decimal 64 into receiver subwf recv, F ;recv - W, store result in file register (recv) ;----------------------------------------------------------------------------- ;Read EEPROM at address which is stored in recv (Midi Data Receive Register) ;----------------------------------------------------------------------------- readee movf recv, W ;move recv value into W movwf eeadr ;and set up eeprom address bsf status, RP0 ;shift to bank 1 bsf eecon1, RD ;set the read bit bcf status, RP0 ;shift back to bank 0 movf eedata, W ;return value in w movwf out_data ;save value in 'out_data' register btfss flags, 1 ;check flag1 /if set, swap byte read from EEPROM goto trigger ;else go and trigger outputs now swapf out_data, F ;swap byte and store result in out_data ;----------------------------------------------------------------------------- ;Trigger Outputs ;----------------------------------------------------------------------------- trigger movf out_data, W ;load 'out_data' value into work register movwf porta ;Trigger outputs according to value read from EEPROM goto read_status_byte ;return to midi data receive ;----------------------------------------------------------------------------- ;Interrupt Service Routine; enter Programming Mode ;----------------------------------------------------------------------------- int_serv nop ;do nothing/ placeholder for future functions ;----------------------------------------------------------------------------- ;Turn on LED on PortB2 to indicate Programming Mode ;----------------------------------------------------------------------------- bsf portb, 2 ;----------------------------------------------------------------------------- ;wait until edit button has been released ;----------------------------------------------------------------------------- key_rel call delay25 ;short delay btfsc portb, 0 ;button released? goto key_rel ;if not, check again call delay25 ;short delay btfsc portb, 0 ;still released? goto key_rel ;if not, check again call delay200 ;longer delay to avoid leaving edit mode again call delay200 ;because key may still be pressed ;----------------------------------------------------------------------------- ;Check Buttons on PortB; set Outputs according to selection ;----------------------------------------------------------------------------- key_00 btfss portb, 4 ;if key pressed continue goto key_01 ;else check next key call delay25 ;short delay btfss portb, 4 ;check key again to be sure goto key_01 ;no key press/ check next key goto set_00 ;switch output key_01 btfss portb, 5 ;if key pressed continue goto key_02 ;else check next key call delay25 ;short delay btfss portb, 5 ;check key again to be sure goto key_02 ;no key press/ check next key goto set_01 ;switch output key_02 btfss portb, 6 ;if key pressed continue goto key_03 ;else check next key call delay25 ;short delay btfss portb, 6 ;check key again to be sure goto key_03 ;no key press/ check next key goto set_02 ;switch output key_03 btfss portb, 7 ;if key pressed continue goto key_04 ;else check next key call delay25 ;short delay btfss portb, 7 ;check key again to be sure goto key_04 ;no key press/ check next key goto set_03 ;switch output key_04 btfss portb, 0 ;if key pressed continue goto key_00 ;else check all keys again call delay25 ;short delay btfss portb, 0 ;check key again to be sure goto key_00 ;no key press/ check all keys again goto writee ;write new data into EEPROM ;----------------------------------------------------------------------------- ;switch Outputs according to key press ;----------------------------------------------------------------------------- set_00 btfsc porta, 0 ;check state of output goto off_00 ;if set then clear output goto on_00 ;else set output off_00 bcf porta, 0 ;clear output bcf out_data, 0 ;also store value in out_data goto rel_00 ;go and wait until button has been released on_00 bsf porta, 0 ;set output bsf out_data, 0 ;also store value in out_data goto rel_00 ;go and wait until button has been released rel_00 call delay25 ;short delay btfsc portb, 4 ;button released? goto rel_00 ;if not, check again call delay200 ;slightly longer delay (for debouncing of keys) btfsc portb, 4 ;still released? goto rel_00 ;if not, check again goto key_00 ;return and check keys set_01 btfsc porta, 1 ;check state of output goto off_01 ;if set then clear output goto on_01 ;else set output off_01 bcf porta, 1 ;clear output bcf out_data, 1 ;also store value in out_data goto rel_01 ;go and wait until button has been released on_01 bsf porta, 1 ;set output bsf out_data, 1 ;also store value in out_data goto rel_01 ;go and wait until button has been released rel_01 call delay25 ;short delay btfsc portb, 5 ;button released? goto rel_01 ;if not, check again call delay200 ;slightly longer delay (for debouncing of keys) btfsc portb, 5 ;still released? goto rel_01 ;if not, check again goto key_00 ;return and check keys set_02 btfsc porta, 2 ;check state of output goto off_02 ;if set then clear output goto on_02 ;else set output off_02 bcf porta, 2 ;clear output bcf out_data, 2 ;also store value in out_data goto rel_02 ;go and wait until button has been released on_02 bsf porta, 2 ;set output bsf out_data, 2 ;also store value in out_data goto rel_02 ;go and wait until button has been released rel_02 call delay25 ;short delay btfsc portb, 6 ;button released? goto rel_02 ;if not, check again call delay200 ;slightly longer delay (for debouncing of keys) btfsc portb, 6 ;still released? goto rel_02 ;if not, check again goto key_00 ;return and check keys set_03 btfsc porta, 3 ;check state of output goto off_03 ;if set then clear output goto on_03 ;else set output off_03 bcf porta, 3 ;clear output bcf out_data, 3 ;also store value in out_data goto rel_03 ;go and wait until button has been released on_03 bsf porta, 3 ;set output bsf out_data, 3 ;also store value in out_data goto rel_03 ;go and wait until button has been released rel_03 call delay25 ;short delay btfsc portb, 7 ;button released? goto rel_03 ;if not, check again call delay200 ;slightly longer delay (for debouncing of keys) btfsc portb, 7 ;still released? goto rel_03 ;if not, check again goto key_00 ;return and check keys ;----------------------------------------------------------------------------- ;write new data to EEPROM/ first check if button has been released ;----------------------------------------------------------------------------- writee nop rel_04 call delay25 ;short delay btfsc portb, 0 ;button released? goto rel_04 ;if not, check again call delay200 ;slightly longer delay (for debouncing of keys) btfsc portb, 0 ;still released? goto rel_04 ;if not, check again btfsc flags, 1 ;check flag1/ if set (Program number > 63), swap byte so that ;bits of memory byte are changed back to correct order swapf out_data, F ;swap byte and store result in 'out_data' register movf recv, W ;move recv value into W (last midi program change) movwf eeadr ;and set up eeprom address movf out_data, W ;move out_data value into W (output switch settings) movwf eedata ;and set up eedata value bsf status, RP0 ;shift to bank 1 bsf eecon1, WREN ;enable write to EEPROM movlw 55h ;sequence which initializes write process movwf eecon2 movlw 0AAh movwf eecon2 bsf eecon1, WR call delay25 ;short delay (about 25 us) to complete write process eeloop btfsc eecon1, WR ;wait for WR to go low (write process completed) goto eeloop bcf eecon1, WREN ;disable write to EEPROM bcf status, RP0 ;shift back to bank 0 ;----------------------------------------------------------------------------- ;Turn off LED that indicates Programming mode ;----------------------------------------------------------------------------- bcf portb, 2 bcf INTCON, INTF ;clear the appropriate flag retfie ;return to point where interrupt happened ;----------------------------------------------------------------------------- ;That's it, pal! ;----------------------------------------------------------------------------- end