
'-----------------------------------------------------
'PUBLIC DOMAIN
'-----------------------------------------------------
'
'MIDIBEATCLKIN
'dr.mabuse
'
'*****************************************************
'*  MIDI-In interrupt or polled (set by conditional) *
'*  MIDI-Out polled                                  *
'*  Timer interrupts provide 1mS timestamp           *
'*****************************************************
'
' PSIM-1 (Programmable Synthesizer Interface Module)
' Module: PSIM-1 REV1b
' Processor Type: Basic Micro - Basic Atom Pro24M
'
'DESCRPTION (by dr.mabuse)
' this is a very preliminary '1st experiment' with MIDI IN system realtime messages
' it may, perhaps be useful for synchronizing analog sequencers or ADSR's to a MIDI system
'Converts MIDI beat clock (system realtime F8) to pulses at 4 subdivisions of the bar - tested at tempo 20 thru 255BPM
'all pulses are one 32nd note wide
'CVout1 = one pulse per 16th note
'CVout2 = one pulse per 8th note
'CVout3 = one pulse per quarter note
'CVout4 = one pulse per Bar of 4/4
'no MIDI out is transmitted
'Be sure that you enable MIDI beat clock (NOT MID time code)on your sequencer/synthesizer/drum box's MIDI output
'Pulses are set to appx 10V (ARP compatible) in this code 
'see code comments to adjust pulse amplitude to 5V
'
'
' MIDI INTERFACE Program Developed by Dave Brown
' Built with code pieces from Grant Richter & Brice Hornback
' Date: April 18, 2004 
' History:	0.1 Added DAC & led initialization, 1 mS timer
'				functionality, stop led blinks when MIDI data
'				received, run led blink at 0.5 seconds for
'				health indicator
'  			0.0 Initial release of MIDI input and output routines
'
'
'
'
'*****************************************************
'
' Basic Micro Atom Pro-24M Configuration
' Note: P0 is I/O 0 and NOT pin 0 on the microprocessor
' P0 -  Analog IN-1 (0-5 VDC)
' P1 -  Analog IN-2 (0-5 VDC)
' P2 -  Analog IN-3 (0-5 VDC)
' P3 -  Analog IN-4 (0-5 VDC)
' P4 -  START button (momentary normally open switch)
' P5 -  STOP  button (momentary normally open switch)
' P6 -  I2C/SDA (reserved) - J3 Pin 1
' P7 -  I2C/SDL (reserved) - J3 Pin 2
' P8 -  AUX (Digital I/O - no buffering)
' P9 -  STOP LED
' P10 - RUN LED (line 50)
' P11 - DAC - LOADDACS
' P12 - DAC - SERDATA
' P13 - DAC - CLOCK
' P14 - RXD (reserved) - J5 Pin 1 (MIDI)
' P15 - TXD (reserved) - J5 Pin 2 (MIDI)
'
'*****************************************************
'
'  ###   CONDITIONAL COMPILING OPTION    ###
'  Set POLLED or INTERRUPT driven MIDI input
'
'Comment out next line for polled mode
	IM_FLAG	CON 1	'Defined for interrupt mode
					'Commented for polled mode
'
'*****************************************************
'
'Defined Variables
	LOADDACS	CON 11					'Pin OUT to DAC LOADDACS
	SERDATA		CON 12					'Pin OUT Serial Data to DAC (16-bit)
	CLOCK		CON 13					'Pin OUT to Clock DAC
	STOPLED		CON  9					'Red LED
	RUNLED		CON 10					'Green LED
	BSTART		CON  5					'Start Button
	BSTOP		CON  4					'Stop  Button
	AUX			CON  8					'AUX Jack (unbuffered)
'
	NOTEOFF		CON	$80					'MIDI note off command
	NOTEON		CON	$90					'MIDI note on command
	MIDICHAN	VAR	NIB
	MIDINOTE	VAR BYTE
	MIDIVEL		VAR	BYTE
'	
	MIDIDATA	VAR	BYTE				'MIDI data variable for subroutines
	I_DATA		VAR	BYTE				'MIDI data variable for interrupt routine
	STATUS		VAR BYTE				'MIDI status data
	RPTR_S		VAR BYTE				'Received data start pointer (first data in buffer)
	RPTR_E		VAR BYTE				'Received data end pointer (last data in buffer+1)
	RBUFNUM		VAR	BYTE				'Number of bytes in received data buffer
	RDATAF		VAR BYTE				'Received data flag 1=data, 0=no data
	RBUFL		CON	32					'Receive buffer length
	RBUF		VAR BYTE(RBUFL)			'Receive buffer data
'
    PPQ         VAR BYTE 'counter for 24 pulses per quarter note
    
	TIMECOUNT	VAR LONG				'1 mS timer count
	TURNOFF		VAR LONG				'Time to turn off stop led
'
	RAWDAC1  	VAR WORD				'Raw DAC data 1 
	RAWDAC2  	VAR WORD				'Raw DAC data 2 
	RAWDAC3  	VAR WORD				'Raw DAC data 3 
	RAWDAC4  	VAR WORD				'Raw DAC data 4 
'
	DAC1V  		VAR WORD				'DAC value to be sent to DAC channel
	DAC2V  		VAR WORD				'DAC value to be sent to DAC channel
	DAC3V  		VAR WORD				'DAC value to be sent to DAC channel
	DAC4V  		VAR WORD				'DAC value to be sent to DAC channel
'	
	ADC1		CON 0
	ADC2		CON	1
	ADC3		CON 2
	ADC4		CON 3
'
	ADC1V		VAR WORD				'Input A/D buffer channel 1
	ADC2V		VAR WORD				'Input A/D buffer channel 2
	ADC3V		VAR WORD				'Input A/D buffer channel 3
	ADC4V		VAR WORD				'Input A/D buffer channel 4
'	
'*****************************************************
'
'Initialize PSIM
	DIRS = %0111110000000000			'Configure Pins: 1=output, 0=input
	OUTS = %0111111111111111			'Configure state: 1=high, 0=low
'
'Initialize serial hardware and ports
'These next 6 commands need to be in this order!
	LET SCR3=%00000000					'Reset Serial Control Register
	LET SMR=%00000000					'Set Serial Mode Register
	 									' Asynchronous
	 									' 8 bits
	 									' Parity disabled
		 								' Even parity (disabled)
	 									' 1 stop bit
	 									' Multiprocessor mode disabled
	 									' BRR clock source direct
	LET BRR=15							'Set Bit Rate Register for 31500 baud
	PAUSEUS 100							'Let BRR settle for 50 uS
	LET SCR3=%00110000					'Set Serial Control Register
	 									' Transmit or receive interrupts disabled
	 									' Transmit and receive enabled
	 									' Multiprocessor interrupt disabled
	 									' Transmit end interrupt disabled
	 									' Internal baud rate generator
	LET PMR1=%00001110					'Set Port Mode Register
	 									' P17 general I/O port
	 									' P16 general I/O port
	 									' P15 general I/O port
	 									' P14 general I/O port
	 									' TXD output
	 									' P10 general I/O port
'
'setup timerw for 1 millisecond interupts
	 LET TMRW=%10001000 				'Set Timer Mode Register to enable count
	 LET TCRW=%10110000 				'Set Timer Control Register
	 									' TCNT cleared by compare match
	 									' /8 internal clock
	 LET TIERW=%01110000				'Set Timer Interrupt register to disable overflow interrupt
	 LET TSRW=%01110000					'Set Timer Status Register to default
	 LET TIOR0=%10001000				'Set Timer I/O Regiseter 0 to default
	 LET TIOR1=%10001000				'Set Timer I/O Register 1 to default
	 LET GRA=2000 						'Set General Register A
	 									' 16 MHz clock /8 = 2 MHz
	 									' 2000 counts = 1 millisecond interrupt
'
'Initialize variables
	LET RPTR_S=0						'Initialize receive start pointer
	LET RPTR_E=0						'Initialize receive end pointer
	LET RBUFNUM=0						'Set receive buffer to empty
	LET MIDICHAN=0						'Set MIDI channel to 0
	LET MIDIVEL=$40						'Set default velocity
	LET TIMECOUNT=0						'Set real time counter to 0
	LET TURNOFF=0						'Set time to turn off to TIMECOUNT
'
	ONINTERRUPT TIMERWINT_IMIEA, CALCTIME 
	#IFDEF IM_FLAG
		ONINTERRUPT SCI3INT_RDRF, MIDI_IN_ISR
		ENABLE SCI3INT_RDRF				'Enable receive interrupt
	#ENDIF
	ENABLE TIMERWINT_IMIEA				'Enable timer interrupt
'
'*****************************************************
'Main program goes here
'Test with simple loop-through program
'Flash stop led when MIDI received
	LET DAC1V=0							'Set outputs to 0
	LET DAC2V=0
	LET DAC3V=0
	LET DAC4V=0
	GOSUB LOADALLDACS
	LOW STOPLED							'Initialize leds
	HIGH RUNLED
	PPQ=0
MAIN:
	#IFNDEF IM_FLAG
		GOSUB CHECK_MIDI				'Poll MIDI input if not interrupt driven
	#ENDIF
	IF RBUFNUM<>0 THEN					'Check to see if any MIDI data received
		GOSUB GET_RBUFFER				'Get data from received buffer
		HIGH STOPLED                    ' tell the people we got MIDI!
        IF MIDIDATA = 0xF8 THEN 'hex F8 is MIDI beat clock, SYSTEM REALTIME MESSAGE. 24 of these critters go by during every QUARTER NOTE 
               IF PPQ = 96 THEN '96 = 24 x 4, PPQ is cleared every BAR of 4/4; 4 quater notes
                  DAC4V = 4095  'send the leading edge of a pulse out of CVout 4; TO CHANGE ALL PULSES TO 5v, SET ALL DAC OUTPUTS TO 2048 INSTEAD OF 4095
                  GOSUB LOADALLDACS
                  PPQ = 0       'then clear PPQ in order to start over on another bar
                 ENDIF
                 
                PPQ = PPQ + 1 'increment PPQ every time hex F8 goes by
                
                IF (PPQ // 3) = 0 THEN 'SETS PULSE WIDTH... to 3 occurences of hex F8 - thus pulses are one 32nd note wide
                   IF DAC1V <> 0 THEN 'if a pulse is already on on any CV out then turn it off (trailing edge of pulse)
                      DAC1V = 0
                     ENDIF
                   IF DAC2V <> 0 THEN 
                      DAC2V = 0
                     ENDIF
                   IF DAC3V <> 0 THEN 
                      DAC3V = 0
                     ENDIF
                   IF DAC4V <> 0 THEN 
                      DAC4V = 0
                     ENDIF
                   GOSUB LOADALLDACS
                  ENDIF
                IF (PPQ // 6) = 0 THEN 'PPQ is divisible evenly by 6 (16th note)send the leading edge of a pulse out of CVout1 
                   DAC1V = 4095
                   GOSUB LOADALLDACS                  
                  ENDIF  
                IF (PPQ // 12) = 0 THEN 'PPQ is divisible evenly by 12 (8th note)send the leading edge of a pulse out of CVout1 
                    DAC2V = 4095
                    GOSUB LOADALLDACS                   
                  ENDIF                 
                IF (PPQ // 24) = 0  THEN 'PPQ is divisible evenly by 24 (quarter note)send the leading edge of a pulse out of CVout1 
                   DAC3V = 4095
                   GOSUB LOADALLDACS                   
                  ENDIF 
          ENDIF				
		DISABLE TIMERWINT_IMIEA 
		TURNOFF=TIMECOUNT+20			'Set time to turn off as current + 20 mS
		ENABLE TIMERWINT_IMIEA 
	ENDIF
	GOTO MAIN
'End of simple loop through program
'*****************************************************
'
'
'*****************************************************
'******************* SUBROUTINES *********************
'*****************************************************
'
#IFNDEF IM_FLAG
	'Poll subroutine for received MIDI
	'Puts data into input buffer and sets RBUFNUM to number of entries
	CHECK_MIDI:
	LET STATUS=SSR
	IF STATUS & %01000000 THEN
		'Data has been received	
		LET MIDIDATA=RDR				'Read Receiver Data Register
		IF MIDIDATA<>$FE THEN			'Ignore if active status
			'Put MIDIDATA into receive buffer
			'Check to see if buffer full
			IF RBUFNUM=RBUFL THEN
				'Buffer is full
				'Simply loose data
			ELSE
				'Buffer has space
				LET RBUF(RPTR_E)=MIDIDATA
				LET RBUFNUM=RBUFNUM+1	'Set RBUFNUM to number of entries
				LET RPTR_E=RPTR_E+1		'Increment pointer for next entry
				IF RPTR_E=RBUFL THEN
					RPTR_E=0			'Wrap pointer at max value
				ENDIF
			ENDIF
		ENDIF
	ELSE
		'No data received
		'Check if error
		IF STATUS & %00111000 THEN
			'Error condition
			LET STATUS=STATUS & %10000111
			LET SSR=STATUS				'Reset error bits
		ENDIF
	ENDIF
	RETURN
#ENDIF
'
'Get MIDIDATA from receive buffer
'RDATAF=1 if successful, RDATAF=0 if no data in buffer
GET_RBUFFER:
	#IFDEF IM_FLAG
		'The interrupt service routine uses the same variables
		'Need to ensure they do not change during this routine
		DISABLE SCI3INT_RDRF
	#ENDIF
	'Check to see if buffer empty
	IF RBUFNUM=0 THEN
		'Buffer is empty
		RDATAF=0						'Set empty flag
	ELSE
		'Buffer has data
		LET MIDIDATA=RBUF(RPTR_S)
		LET RBUFNUM=RBUFNUM-1
		LET RPTR_S=RPTR_S+1
		IF RPTR_S=RBUFL THEN
			LET RPTR_S=0				'Wrap pointer at max value
		ENDIF
		LET RDATAF=1					'Set data returned flag
	ENDIF
	#IFDEF IM_FLAG
		ENABLE SCI3INT_RDRF
	#ENDIF
	RETURN
'
'Subroutine to send MIDIDATA
'Waits until transmitter ready
SEND_MIDI:
	LET STATUS=SSR
	IF STATUS & %10000000 THEN			'Check transmitter ready
		'Ready to transmit	
		LET TDR=MIDIDATA				'Send data to TDR
		RETURN
	ELSE
		GOTO SEND_MIDI
	ENDIF
'
'Output 3 byte note on sequence	
NOTE_ON:
	LET MIDIDATA=NOTEON+MIDICHAN
	GOSUB SEND_MIDI
	LET MIDIDATA=MIDINOTE
	GOSUB SEND_MIDI
	LET MIDIDATA=MIDIVEL
	GOSUB SEND_MIDI
	RETURN
'
'Output 3 byte note off sequence 	
NOTE_OFF:
	LET MIDIDATA=NOTEOFF+MIDICHAN
	GOSUB SEND_MIDI
	LET MIDIDATA=MIDINOTE
	GOSUB SEND_MIDI
	LET MIDIDATA=MIDIVEL
	GOSUB SEND_MIDI
	RETURN
'
'Add addresses to values (No speed improvement with OR over +)
LOADALLDACS:
	LET RAWDAC1=DAC1V+49152
	LET RAWDAC2=DAC2V+32768
	LET RAWDAC3=DAC3V+16384
	LET RAWDAC4=DAC4V
	'Shift out 16 bits mode 4 gotta bang loaddacs pin for each channel
	'Skew from channels 1 to 4 = 400 usecs
	'Approximately 1 msec execution time for subroutine
	SHIFTOUT SERDATA,CLOCK,4,[RAWDAC1\16]
 	PULSOUT LOADDACS,1 
 	SHIFTOUT SERDATA,CLOCK,4,[RAWDAC2\16]
 	PULSOUT LOADDACS,1 
 	SHIFTOUT SERDATA,CLOCK,4,[RAWDAC3\16]
 	PULSOUT LOADDACS,1
 	SHIFTOUT SERDATA,CLOCK,4,[RAWDAC4\16]
 	PULSOUT LOADDACS,1
 	RETURN
'
'Load buffers with actual A/D values
SCANADC:
	ADIN ADC1, ADC1V
	ADIN ADC2, ADC2V
	ADIN ADC3, ADC3V
	ADIN ADC4, ADC4V
	RETURN
'
'
'*****************************************************
'************ INTERRUPT SERVICE ROUTINES *************
'*****************************************************
'
'Interrupt service routine for timer
CALCTIME:
	LET TIMECOUNT=TIMECOUNT+1			'Increment real time count
	IF TIMECOUNT=TURNOFF THEN			'Value to turn off stopled
		LOW STOPLED
	ENDIF
	IF (TIMECOUNT&$000001FF)=$00000100 THEN
		TOGGLE RUNLED					'Blink run led at 0.512 second intervals
	ENDIF
RESUME
'
'
#IFDEF IM_FLAG
	'Interrupt service routine for received MIDI data
	'Puts data into input buffer and sets RBUFNUM to number of entries
	MIDI_IN_ISR:
	'Do not check SSR
	'Does not function in interrupt mode
	LET I_DATA=RDR						'Read Receiver Data Register
	IF I_DATA<>$FE THEN					'Ignore if active status
	'Check to see if buffer full
		IF RBUFNUM=RBUFL THEN
			'Buffer is full
			'Simply loose data
		ELSE
			'Put data into buffer
			LET RBUF(RPTR_E)=I_DATA
			LET RBUFNUM=RBUFNUM+1		'Set RBUFNUM to number of entries
			LET RPTR_E=RPTR_E+1			'Increment pointer for next entry
			IF RPTR_E=RBUFL THEN
				RPTR_E=0				'Wrap pointer at max value
			ENDIF
		ENDIF
	ENDIF
	RESUME
#ENDIF
'
'End of program
