; This code may not be used for commercial gain without prior agreement
; Copyright March 2000, Colin Fraser
;
; Modifications for Boss DR-110 by Benjamin Riggs 2004... cheers Colin :)
;
; revision history:
;
; 28/03/2008 - modified the code to use interupt on GP2 instead of polling.
;
; 27/03/2004 - transfered the code from the 12c508 to 12f675 to get a larger queue.
;              queue is now 60 bytes long in the 12f675 compared to 21 in the 12c508.
;              12f675 is pin for pin compatible with the 12c508 so no changes to the
;              circuit or PCB is required.
;
;              now also repsonds to midi reset... just for the hell of it.
;
; 26/03/2004 - optimised register usage to increase the size of the queue from
;              16 to 21 bytes. my pc sequencer is giving me a headache.
;
; 19/03/2004 - modified the midi to sync code to ensure no midibytes are lost
;              and output pulses are longer than 2.2 ms. previously sync was lost if
;              consecutive midi clocks were recieved in a period shorter than 2.2ms.
;              to do this, they are now queued on arrival before execution.
;
; 08/03/2004 - modified code to sync a BOSS-DR110 to midi.
;              now outputs 12PPM sync on pin 3(GP4),
;              pin 2(GP5) goes high / pin6(GP1) goes high Z on start for 4ms,
;              pin 6(GP1) goes high / pin2(GP5) goes high Z on stop for 4ms,
;
;              modified code to send midi sync from Boss DR-110 internal clock
;              now outputs 24 midi clocks per metronome from 12ppm internal DR-110 clock
;              send midi start on rising edge of pulse on pin 2 (GP5)
;              send midi stop on rising edge of pulse on pin 6 (GP1)
;
; 15/05/2003 - fixed config option for MCLRE - oops!
;
; 26/11/2001 - added sync to midi conversion
;
; 6/3/2000 - modified midi bit timing to fix rare sync problem when consecutive
;            bytes were received with no gap
;
;
;
;   PIC12f675 Midi to Boss DR-110 Sync
;
;   This program uses Microchip assembler mnemonics and the Microchip
; MPASM assembler (http://www.microchip.com/).  Default config options
; are set in the __CONFIG line below: MCLRE on, CP off, WDT off, OSC=INTRC.
;
;                  _______  _______
;                 |       \/       |
;     (+5V) Vdd --+ 1            8 +-- Vss (GND)
;                 |                |
;   X1/CLKI/GP5 --+ 2            7 +-- GP0
;    (Sync Start) |                |  (Midi output) 
;   X2/CLKO/GP4 --+ 3            6 +-- GP1 
;    (Sync Clock) |                |  (Sync Stop)
;     GP3/!MCLR --+ 4            5 +-- GP2
;    (mode sel)   |  (PIC12F675)   |  (Midi input)
;                 +----------------+
;
;   This program converts incoming midi clock bytes
;   to Boss DR-110 compatible sync pulses
;
;

	list	p=12f675
	radix	dec
	include	"p12f675.inc"

	__CONFIG   _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT

; Variables
; bytes 0x20 to 0x27 make up special function register block
	cblock	0x20
	delaytemp				;	0x20	used for delay in midirx and miditx
	bitcounter				;	0x21	used to count bits in midirx and miditx
	temppointer				;	0x22	used to re-organise queue
	midiqueuepointer		;	0x23	pointer to byte at the end of the queue
	w_temp					;	0x24	w temp for isr
	status_temp				;	0x25	status tmep for isr
	midi					;	0x26	next midi byte to process
	dinstat					;	0x27	din input status
	endc
; bytes 0x26 to 0x5F make up midibyte queue block, midinext being at the first in queue

; Macros
; macros to change memory banks
bank0	macro						;SET UP MACRO TO SWITCH TO BANK 0
		bcf	STATUS,RP0
		endm
bank1	macro						;SET UP MACRO TO SWITCH TO BANK 1
		bsf	STATUS,RP0
		endm
	
;program code

	org		0X000				;PROCESSOR RESET VECTOR
	goto	main				;GO TO BEGINNING OF PROGRAM
	org		0X004
	goto	isr					;GO TO INTERUPT SERVICE ROUTINE           

main
	clrwdt
	bank1
	movlw	b'10000111'
	movwf	OPTION_REG			;set Timer0 and prescale, no pullups
	call	0x3FF
	movwf	OSCCAL				;calibrate oscillator
	clrf	ANSEL				;turn off ADC
	bank0
	movfw	CMCON
	iorlw	b'00000111'
	movwf	CMCON				;turn comparitors off

;	wait a second while powering up	
	movlw	0x010	;15 * 65536us= 992ms
	movwf	delaytemp
poweronloop ;1sec
	clrf	TMR0
	clrwdt
	bank1
	movlw	b'10000111'
	movwf	OPTION_REG			;set Timer0 and prescale, no pullups
	bank0
	bcf		INTCON,T0IF
poweronloop1 ;32ms
	btfss	INTCON,T0IF
	goto	poweronloop1
	decfsz	delaytemp,F		
	goto	poweronloop

; ====================
; midi to sync section
; ====================

m2s
	bank1
	movlw	b'00101110'			;Set the I/O direction, start and stop pins high Z
	movwf	TRISIO
	bank0
	movlw	b'00000001'			;Preset outputs
	movwf	GPIO				;/
	movlw	0x26
	movwf	midiqueuepointer	;points to last byte in queue
	clrf	midi				;initialise first midi register.

;	set up interrupt for GP2
	movlw	b'10010000'
	movwf	INTCON

;\ midibyte execution code

m2sloop
	btfss	GPIO,3		; check mode select	
	goto	s2m			; go to sync to midi if required

;  Check for Clock pulse
	movfw	midi
	xorlw	0xF8
	btfsc	STATUS,Z
	goto	clockpulse

;  Check for Start
	movfw	midi
	xorlw	0xFA
	btfsc	STATUS,Z
	goto	clockstart

;  Check for Stop
	movfw	midi
	xorlw	0xFC
	btfsc	STATUS,Z
	goto	clockstop

;  Check for Reset
	movfw	midi
	xorlw	0xFF
	btfsc	STATUS,Z
	goto	0x3FF
	goto	reorganisequeue

;  Set start pin high / stop pin high Z

clockstart
	bcf		GPIO,4				; clear clock pin
	bank1
	bcf		TRISIO,5
	bank0
	bsf		GPIO,5
	goto	wait

;	Set stop pin high / start pin high Z

clockstop
	bcf		GPIO,4				; clear clock pin
	bank1
	bcf		TRISIO,1
	bank0
	bsf		GPIO,1
	goto	wait

;	Toggle clock pin

clockpulse
	movlw	0x10
	xorwf	GPIO,F				; toggle clock pin

;	ensure that there is more than 2.2ms between start / stop / clock toggle
wait
	movlw	.100
	movwf	TMR0
	bank1
	movlw	b'10000011'
	movwf	OPTION_REG			;set Timer0 and prescale, no pullups
	bank0
	bcf		INTCON,T0IF
	clrwdt
waitloop			;2.560ms
	btfss	INTCON,T0IF	
	goto	waitloop
	bank1
	bsf		TRISIO,GP1
	bsf		TRISIO,GP5
	bank0

reorganisequeue
	clrf	midi
;moves all valid data up one address in queue
	movlw	0x26
	movwf	temppointer			;initialise pointer to first byte in queue

	movlw	0x26				;test for empty queue
	xorwf	midiqueuepointer,W
	btfsc	STATUS,Z
	goto	m2sloop

reorganiseloop
	incf	temppointer
	movfw	temppointer
	movwf	FSR
	movfw	INDF
	decf	FSR
	movwf	INDF
	movfw	temppointer			; test for end of queue
	xorwf	midiqueuepointer,W
	btfss	STATUS,Z
	goto	reorganiseloop
	decf	midiqueuepointer
	goto	m2sloop

;/end of midi execution code

;/Interupt Service Routine
isr
	movwf	w_temp				; copy W to tmep register, could be in either bank
	swapf	STATUS,W			; swap status to be saved into W
	bcf		STATUS,RP0			; change to bank0 regardless of current bank
	movwf	status_temp

	btfsc	INTCON,INTF
	call	midirx

	swapf	status_temp,W		; swap status_temp register into W, sets bank to original state
	movwf	STATUS				; move W into staus register
	swapf	w_temp,F			; swap w_temp
	swapf	w_temp,W			; swap w_temp into W
	retfie
;/end ISR

;
;   rx - receive midi byte at 31250 bps.
;

midirx
	movlw	0x5F				; (1)
	xorwf	midiqueuepointer,W	; (1) test to see if queue is full
	btfsc	STATUS,Z			; (1/2)
	goto	rxexit				; (1) discard byte if queue is full

	incf	midiqueuepointer	; (1) increment pointer
	movfw	midiqueuepointer	; (1) initialise pointer	
	movwf	FSR					; (1) to next byte in queue
;
;   Delay one bit-width (32 cycles) to get to center of LSB.
;   Gather the bits into midi.
;
	movlw	0x08				; Eight bits of data to get
	movwf	bitcounter		
	clrf	INDF		
mbin
	movlw	0x06				;(1)
	movwf	delaytemp			;(1)
del1
	nop							;\
	decfsz	delaytemp,F			;((6 x 4) + 3) = 26
	goto	del1				;/

	bcf		STATUS,C			;(1) Default 0
	rrf		INDF,F				;(1) Put 0 in MSB of recv, then
	btfsc	GPIO,2				;(1/2) sample the serial data and
	bsf		INDF,7				;(1) set the bit if sample was=1
	decfsz	bitcounter,F		;((7 x 3) + 2) = 23, Do all bits
	goto	mbin				;/ [total cycles = 32, MSB = 31]
;
;   Time to center of stop bit.  (32 cycles)
;
	movlw	9					;(1)
	movwf	delaytemp			;(1)
del3
	decfsz	delaytemp,F			;((9 x 3) + 2) = 29
	goto	del3				;/
	btfss	GPIO,2				;(2)Stop bit showed up? (stop bit consists of a '1')
	decf	midiqueuepointer
	bcf		INTCON,INTF			;clear GP2 interrupt flag
	return						;(2)keep processing midibytes in queue if not full

;
;	extra cycles to retain timing in event
;	of the queue being full
;
rxexit
	movlw	.101				;(1)
	movwf	delaytemp			;(1)
del2
	decfsz	delaytemp,F			;((103 x 3) + 4) = 313
	goto	del2
	bcf		INTCON,INTF			;clear GP2 interrupt flag
	return

; ====================
; sync to midi section
;
; dinstat
;  b0 previous status of clock pin
;  b1 previous status of start pin
;  b2 previous status of start pin
;  b4 current run status. set on start, reset on stop
;  b5 initial clock status. used to ensure first midi clock on first rising edge only.
;  b6 unused
;  b7 unused
; ====================

s2m
	clrf	INTCON
	bank1
	movlw	b'00111110'	;Set the I/O direction
	movwf	TRISIO
	bank0
	movlw	b'00000001'	;Preset outputs
	movwf	GPIO		;/

	clrw
	movwf	dinstat

starttest
	btfsc	GPIO,3		; check mode select	
	goto	m2s			; go back to midi to sync if required

	btfss	GPIO,5		; Check for DIN start = 1
	goto	lowstart	; branch if start = 0
	goto	highstart	; branch if start = 1

clktest	
	btfss	GPIO,4		; Check DIN Clock
	goto	lowclock	; branch if clock = 0
	goto	highclock	; branch if clock = 1

stoptest
	btfss	GPIO,1		; Check for DIN stop = 1
	goto	lowstop		; branch if stop = 0
	goto	highstop	; branch if stop = 1

lowstart
	btfss	dinstat,1	; was it already 0 ?
	goto	clktest		; branch if start pin not changed

	bcf		dinstat,1	; start status = 0

	goto	clktest

highstart
	btfsc	dinstat,1	; was it already 1 ?
	goto	clktest		; branch if startpin not changed

	bsf		dinstat,1	; start status = 1

	btfsc	dinstat,3	; are we already running?
	goto	clktest		; skip midi tx if yes

	bsf		dinstat,3	; run status = 1

	movlw	0xFA		; tx midi start byte
	call	miditx
	
	goto	clktest
	
lowstop
	btfss	dinstat,2	; was it already 0 ?
	goto	starttest	; branch if stop pin not changed

	bcf		dinstat,2	; stop status = 0

	goto	starttest

highstop
	btfsc	dinstat,2	; was it already 0 ?
	goto	starttest	; branch if stop pin not changed

	bsf		dinstat,2	; stop status = 1

	btfss	dinstat,3	; are we already stopped?
	goto	starttest	; skip midi tx if yes

	bcf		dinstat,3	; run status = 0

	btfsc	dinstat,4
	bcf		dinstat,4	; clear initial clock status

	movlw	0xFC		; tx midi start byte
	call	miditx

	goto	starttest

lowclock

	btfss	dinstat,0	; was previous clock 1 ?
	goto	stoptest	; don't send midi if no
	bcf		dinstat,0	; clock status = 0

	btfss	dinstat,3	; check run status
	goto	stoptest	; don't send midi if not running

	btfss	dinstat,4	; have we had an initial rising edge clock ?
	goto	stoptest	; don't send midi if no

	movlw	0xF8		; tx midi clock byte
	call	miditx

	goto	stoptest

highclock

	btfsc	dinstat,0	; was previous clock 0 ?
	goto	stoptest	; don't send midi if no
	bsf		dinstat,0	; clock status = 1

	btfss	dinstat,3	; check run status
	goto	stoptest	; don't send midi if not running

	btfss	dinstat,4
	bsf		dinstat,4	; set initial clock status

	movlw	0xF8		; tx midi clock byte
	call	miditx

	goto	starttest


miditx
	movwf	midi		; store tx byte
	clrf	GPIO		; start bit

;pause for one bit
	movlw	8		;(1)
	movwf	delaytemp		;(1)
pause1
	decfsz	delaytemp		;(1/2) \
	goto	pause1		;(2)   / * z = 8 * 3 + 1 = 25
	
	movlw	0x8		;(1)
	movwf	bitcounter		;(1)
	nop			;(1)

mtxloop
	movfw	midi		;(1)
	movwf	GPIO		;(1) lsb of tx byte to midi out

;pause for one bit
	movlw	7		;(1)
	movwf	delaytemp		;(1)
pause2
	decfsz	delaytemp		;(1/2) \
	goto	pause2		;(2)   / * z = 7 * 3 + 1 = 22 
	
	bsf	STATUS,C	;(1)
	rrf	midi,1		;(1)
	nop			;(1)
	nop			;(1)
	decfsz	bitcounter		;(1)
	goto	mtxloop		;(2)
	
	nop
	return

	end

