;*******************************************************************
;
;  Altair Cassette Interface (2400 Baud)
;
;    MFM encoding is used recording a positive going 1/2 cycle
;    of 2400 Hz to generate a pulse and then a negative going
;    1/2 cycle of 2400, 1200, or 800 Hz for the idle period
;    between pulses.
;
;    Author: Mike Douglas
;
;  Revision: 1.0 (11/22/24),  Initial release 
;	 Checksum: 
;
;*******************************************************************
;
;  Processor is a 16F1823, Internal Oscillator w/o PLL at 8 Mhz
;
	list p=16f1823,b=12,r=dec
	include <p16f1823.inc>

_BORV_25	EQU	H'FBFF'	;Brown-out Reset Voltage (Vbor), high trip point selected.

	__config _CONFIG1, _FCMEN_OFF & _IESO_OFF & _CLKOUTEN_OFF & _BOREN_ON & _CPD_OFF & _CP_OFF & _MCLRE_ON & _PWRTE_ON & _WDTE_OFF & _FOSC_INTOSC
	__config _CONFIG2, _LVP_OFF & _BORV_25 & _STVREN_OFF & _PLLEN_OFF & _WRT_ALL
	
;  Misc Equates 

DATA1_PER	equ	32		;32*13us=416us (1/2 cycle of 1200h)
DATA0_PER	equ	16		;16*13us=208us (1/2 cycle of 2400hz)

BIT_TIME	equ	416		;2400 baud bit time in us
IC_BITx1p25	equ	(BIT_TIME+BIT_TIME/4)*2	;1.25 bit times
IC_BITx0p5	equ	(BIT_TIME/2)*2	;0.5 bit times

T2_BITx1p5	equ	(BIT_TIME+BIT_TIME/2)/8	;1.5 bit times (8us clock period)
T2_BITx0p5	equ	(BIT_TIME/2)/8	;0.5 bit times

; Port A bit definitions

FROM_ALTAIR	equ	4		;serial data from Altair board
TO_ALTAIR	equ	5		;serial data to Altair board

;  Port C bit definitions

AUDIO_OUT	equ	0		;audio waveform out to cassette
AUDIO_MASK	equ	(1<<AUDIO_OUT)   	;AUDIO_OUT bit as a bit mask
RS232_OUT	equ	3		;serial data out to the RS-232 port
RS232_IN	equ	1		;seria data in from the RS-232 port

;  Data reception variables

	cblock	0x70	;common RAM all pages
captLo			;this capture low byte
captHi			;this capture high byte	
prevLo			;previous counter low byte
prevHi			;previous counter high byte
deltaLo			;difference low byte
deltaHi			;difference high byte
periodCount			;13us tic counter for waveform period
flags			;flags byte
outByte
outByteH
xmitData
bitCnt
	endc

;  Flags variable equates

F_IS_ZERO	equ	0	;1 = current bit is zero
F_PREV_BIT	equ	1

;************************************************************************
;
;  Program Space
;
;************************************************************************

;  Reset and interrupt vectors

	org	0		;page zero for jump sbrs
	goto	reset_pwr		;reset vector location zero
	goto	$		;loop here and die
	goto	$
	goto	$
	goto	$		;interrupt vector location 0x04

;  Start of code. Reset entry point

reset_pwr	equ	$
	banksel	INTCON
	clrf	INTCON		;disable all interrupts just in case
	banksel	OSCCON
	movlw	b'01110000'		;8 Mhz internal clock
	movwf	OSCCON
	banksel	OPTION_REG
	movlw	b'01010110'		;weak pullups enabled, /128 on TMR0
	movwf	OPTION_REG
	
; Initialize PORT A

	banksel	PORTA
	movlw	b'00100000'		;serial output to Altair idle at 1
	movwf	PORTA
	banksel	LATA
	movwf	LATA		;init Port A latch = Port A
	banksel	ANSELA
	clrf	ANSELA		;all digital - no analog
	banksel	TRISA
	movlw	b'00011011'		;Pgmming bits input, bit 4 in from Altair
	movwf	TRISA
	banksel	WPUA
	movlw	b'00011011'		;pullup on all input bits
	movwf	WPUA

; Initialize PORT C

	banksel	PORTC
	movlw	b'00001000'		;TX out to DB25 idle at 1
	movwf	PORTC		;no output bits really matter
	banksel	LATC
	movwf	LATC		;init Port C latch = Port C
	banksel	ANSELC
	movlw	b'00000100'		;RC2 used as analog comparator input
	movwf	ANSELC
	banksel	TRISC
	movlw	b'00100110'		;RC5 IC, RC2 audio in, RC1 RX DB25 in 
	movwf	TRISC
	banksel	WPUC
	movlw	b'00000000'		;no pullups used
	movwf	WPUC

; Initialize voltage reference, DAC and comparator for processing the audio input. 
;    The audio input comes in on C2- where it is compared to 0.25v on the + input.
;    The output of the comparator goes is connected externally to CCP1 (the input
;    capture pin). The comparator output is inverted so it goes high when audio
;    in is > 0.25 volts and goes low when audio input is < 0.25 volts.

	banksel	FVRCON		;fixed voltage reference
	movlw	b'10000100'		;FVR on, 1.024v output
	movwf	FVRCON

	banksel	DACCON0		;DAC control register
	movlw	16		;DAC 16/32 = .5v 
	movwf	DACCON1
	movlw	b'10001000'		;DAC on, FVR provides 1.024v
	movwf	DACCON0

	banksel	CM2CON0		;comparator 2 control register
	movlw	b'00010010'		;IN+ is DAC, IN- is C12IN2
	movwf	CM2CON1
	movlw	b'10110110'		;cmp on, output enabled, hysteresis on, inverted
	movwf	CM2CON0

;  Setup Timer 1 and input capture to measure the cycle length of the audio input.
;     The timer free-runs at 2 MHz.

	banksel	T1CON		;timer 1 for input capture
	movlw	b'00000101'		;FOSC/4, no prescale, timer on
	movwf	T1CON

	banksel	CCP1CON		;capture control register
	movlw	b'00000101'		;capture mode, every rising edge
	movwf	CCP1CON

;  Setup Timer 2 to generate a 13us tic used for the audio tone generator loop

	banksel	TMR2		;timer 2 for audio out generation
	movlw	b'00000100'		;timer 2 on, 2MHz
	movwf	T2CON
	clrf	TMR2		;start timer at zero
	movlw	13*2-1		;13us wakeup
	movwf	PR2
	clrf	PIR1		;clear any pending interrupt flags
	clrf	flags		;our flags false
				;fall into audio output loop

;---------------------------------------------------------------------------
; Audio output loop
;----------------------------------------------------------------------------
audioOut	movlw	b'00000110'		;timer 2 on, 125KHz
	movwf	T2CON
	clrf	TMR2		;init TMR2 to time 0.5 bit periods
	movlw	T2_BITx0p5
	movwf	PR2
	clrf	TMR0		;8.2ms timer
	bsf	PORTC,RS232_OUT	;set RS232 output to 1
	bsf	flags,F_PREV_BIT	;idle 1's

; Send idle 1's for 8.2ms

idle	btfss	PIR1,TMR2IF		;sync to 1/2 bit cell
	goto	idle
	bcf	PIR1,TMR2IF
	bcf	PORTC,AUDIO_OUT
	
idle2	btfss	PIR1,TMR2IF		;sync to 1/2 bit cell
	goto	idle2
	bcf	PIR1,TMR2IF
	bsf	PORTC,AUDIO_OUT

	btfss	TMR0,7		;idle 8.2ms when bit 7 becomes 1
	goto	idle

; Send A-Y

	movlw	'A'
	movwf	xmitData

sendLoop	call	sendByte

	btfsc	PIR1,CCP1IF		;did an audio input capture occur?
	goto	audioInput		;yes

	incf	xmitData,F
	movfw	xmitData
	sublw	'Z'
	bnz	sendLoop

	clrf	TMR0
	goto	idle

;---------------------------------------------------------------------
;  sendByte from outByte
;---------------------------------------------------------------------
sendByte	movfw	xmitData
	movwf	outByte
	movlw	10		;byte plus start & stop bit
	movwf	bitCnt
	bsf	outByteH,0		;stop bit of 1
	rlf	outByte,F
	rlf	outByteH,F
	bcf	outByte,0		;start bit is zero
	
sbLoop	btfss	PIR1,TMR2IF		;sync to 1/2 bit cell
	goto	sbLoop
	bcf	PIR1,TMR2IF

	btfsc	outByte,0		;data bit 0 or 1?
	goto	sbOne		;one

; Data is zero

	btfsc	flags,F_PREV_BIT	;prev bit a 1?
	bcf	PORTC,AUDIO_OUT	;yes, go low now
	btfss	flags,F_PREV_BIT
	bsf	PORTC,AUDIO_OUT	;no, set high now

	bcf	flags,F_PREV_BIT

wait1	btfss	PIR1,TMR2IF		;sync to 1/2 bit cell
	goto	wait1
	bcf	PIR1,TMR2IF

	bcf	PORTC,AUDIO_OUT
	goto	nextBit

; Data is one

sbOne	bcf	PORTC,AUDIO_OUT	;1 starts with 1/2 cycle low
	bsf	flags,F_PREV_BIT

wait2	btfss	PIR1,TMR2IF		;sync to 1/2 bit cell
	goto	wait2
	bcf	PIR1,TMR2IF

	bsf	PORTC,AUDIO_OUT	;1 has pulse in 2nd 1/2

nextBit	rrf	outByteH,F
	rrf	outByte,F
	decfsz	bitCnt,F
	goto	sbLoop

	return

;---------------------------------------------------------------------------
; Audio input loop
;   Compute time between input captures (audio input zero crossings) and
;   generate the RS-232 data to output. If no input captures occur for
;   8.2ms, then jump back to the audio output loop above.
;----------------------------------------------------------------------------
audioInput	clrf	TMR0		;restart the idle timeout
	movlw	b'00000110'		;timer 2 on, 125KHz
	movwf	T2CON
	clrf	TMR2		;init TMR2 to time 1.5 bit times
	movlw	T2_BITx1p5		;period for 1.5 bit times
	movwf	PR2

;---------------------------------------------------------------------------
; Loop while audio data is one
;---------------------------------------------------------------------------
aiNowOne	bsf	PORTC,RS232_OUT	;set RS232 output to 1
	bcf	PIR1,TMR2IF		;clear the timer match flag

aiIsOne	btfsc	TMR0,7		;idle 8.2ms when bit 7 becomes 1
	goto	audioOut		;exit back to audio output generation
 	btfsc	PIR1,TMR2IF		;gone 1.5 bit times without pulse?
	goto	aiNowZero		;yes, transition to zero
 	btfss	PIR1,CCP1IF		;capture occur?
	goto	aiIsOne		;not yet, keep looping

; Capture occured. If less than 1.25 bit times, we are still at one. Otherwise,
;   transition to zero.

	clrf	TMR2		;reset 1.5 bit timer
	bcf	PIR1,TMR2IF		;make sure timer match flag is clear

	call	getIcData		;get time between pulses
	movlw	low(IC_BITx1p25)	;compare to 1.25 bit times
	subwf	deltaLo,W
	movlw	high(IC_BITx1p25)
	subwfb	deltaHi,W
	btfss	STATUS,C		;carry set if >= 1.25 bit times
	goto	aiIsOne		;shorter, so still one
				;else, fall into nowZero

;---------------------------------------------------------------------------
; Loop while audio data is Zero
;---------------------------------------------------------------------------
aiNowZero	bcf	PORTC,RS232_OUT	;RS-232 out is zero

	movlw	low(IC_BITx0p5)	;move previous capture time to bit boundary
	addwf	prevLo,F		;  to make it look like a consecutive zero
	movlw	high(IC_BITx0p5)
	addwfc	prevHi,F

aiIsZero	btfsc	TMR0,7		;idle 8.2ms when bit 7 becomes 1
	goto	audioOut		;exit back to audio output generation
 	btfss	PIR1,CCP1IF		;capture occur?
	goto	aiIsZero		;not yet, keep looping

; Capture occured. If less than 1.25 bit times, we are still at zero. Otherwise,
;   transition to one after a 0.5 bit delay to get back to the bit edge.

	clrf	TMR2		;sync timer 2 to pulse edge

	call	getIcData		;get time between pulses
	movlw	low(IC_BITx1p25)	;compare to 1.25 bit times
	subwf	deltaLo,W
	movlw	high(IC_BITx1p25)
	subwfb	deltaHi,W
	btfss	STATUS,C		;carry set if >= 1.25 bit times
	goto	aiIsZero		;shorter, so still zero

aiWaitBit	movfw	TMR2		;delay 1/2 bit time to bit edge
	sublw	T2_BITx0p5
	bnz	aiWaitBit

	goto	aiNowOne		;now transition to one

;---------------------------------------------------------------------------
; getIcData - Get Input Capture Data
;   Compute time between new capture and previous capture and save in
;   deltaHi:deltaLo. Save new capture in prevHi:prevLo
;---------------------------------------------------------------------------
getIcData	clrf	TMR0		;reset "no pulse" timer
	banksel	CCPR1L		;save the capture value
	movfw	CCPR1H
	movwf	captHi
	movfw	CCPR1L
	movwf	captLo

	movlw	33		;ignore captures for 50us to get past
delay50	decfsz	WREG,F		;  transition crossing noise
	goto	delay50

; Compute the time since last capture in deltaHi/deltaLo, then save the new
;    capture time as the previous capture time

	banksel	PORTA
	bcf	PIR1,CCP1IF		;clear the capture flag
	movfw	prevLo		;subtract previous value
	subwf	captLo,W		;lsb first
	movwf	deltaLo
	movfw	prevHi		;msb second	
	subwfb	captHi,W	
	movwf	deltaHi

	movfw	captLo		;save current capture as previous
	movwf	prevLo
	movfw	captHi
	movwf	prevHi
	return

	end