;*******************************************************************
;
;  Altair Cassette Interface (1200 Baud)
;
;    Data 1 is one cycle of 1200hz. Data 0 is two cycles of 2400hz.
;    This differs from the CUTS board (1200hz=1, 1/2 cycle 600hz=0)
;    because the CUTS format requires hardware that can process
;    the full input audio cycle, and this cassette board chops off
;    the bottom half of the audio cycle. 1 and 0 were swapped so
;    that idle tone (ones) are always on a full bit boundary and
;    the first zero bit following idles ones won't be out of sync.
;
;    Author: Mike Douglas
;
;  Revision: 1.0 (11/14/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)
MIN_PERIOD	equ	416	;minimum valid input period in us * 2 (208us)
MID_PERIOD	equ	1248	;midpoint input period in us * 2 (624us)
test	equ 	high MID_PERIOD

MAX_PERIOD	equ	2080	;maximum valid input period in us * 2 (1040us)

;  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
	endc

;  Flags variable equates

F_IS_ZERO	equ	0	;1 = current bit is zero

;************************************************************************
;
;  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
;   Generate 2400Hz (RS232 in is 1) or 1200Hz (RS232 in is 0) waveforms.
;   Sync waveform timing anytime a transition occurs in the RS232 input
;   data. If audio is detected coming in from cassette, jump to the
;   cassette data input loop.
;----------------------------------------------------------------------------
audioOut	bsf	PORTC,RS232_OUT	;idle RS-232 output at 1

; RS-232 input is one

nowOne	movfw	periodCount		;end of old or start of new half cycle?
	andlw	0xf8		;not zero if >=8 (1st half of period)
	bnz	set1Period		;already in new half cycle
	movlw	AUDIO_MASK		;else, toggle audio output
	xorwf	PORTC,F		;  to start new cycle early

set1Period	movlw	DATA1_PER		;set count of 13us tics per half cycle
	movwf	periodCount

isOne	btfss	PIR1,TMR2IF		;13us tic occur?
	goto	isOne		;no

	bcf	PIR1,TMR2IF		;clear the tic flag
	btfss	PORTC,RS232_IN	;still receiving one?
	goto	nowZero		;no, now receiving zero

	btfsc	PIR1,CCP1IF		;did an audio input capture occur?
	goto	havePulse		;yes

	decfsz	periodCount,F	;time out the half cycle
	goto	isOne		;loop if not done yet

	movlw	AUDIO_MASK		;half cycle done, toggle audio output
	xorwf	PORTC,F
	goto	set1Period		;repeat

; RS-232 input is zero

nowZero	movfw	periodCount		;end of old or start of new half cycle?
	andlw	0xf0		;not zero if >= 16 (1st half of period)
	bnz	set0Period		;already in new half cycle
	movlw	AUDIO_MASK		;else, toggle audio output
	xorwf	PORTC,F		;  to start new cycle early

set0Period	movlw	DATA0_PER		;set count of 13us tics per half cycle
	movwf	periodCount

isZero	btfss	PIR1,TMR2IF		;13us tic occur
	goto	isZero		;no

	bcf	PIR1,TMR2IF		;clear the tic flag
	btfsc	PORTC,RS232_IN	;still recieving zero?
	goto	nowOne		;no, now receiving one

	btfsc	PIR1,CCP1IF		;did an audio input capture occur?
	goto	havePulse		;yes

	decfsz	periodCount,F	;time out the half cycle
	goto	isZero		;loop if not done yet

	movlw	AUDIO_MASK		;half cycle done, toggle audio output
	xorwf	PORTC,F
	goto	set0Period		;repeat

;---------------------------------------------------------------------------
; 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.
;----------------------------------------------------------------------------
audioIn	btfsc	TMR0,7		;8.2ms when bit 7 becomes 1
	goto	audioOut		;back to audio output generation
	btfss	PIR1,CCP1IF		;capture occur?
	goto	audioIn		;no, keep looking

; Capture occured, save the value capture in captHi/captLo

havePulse	clrf	TMR0		;reset "no pulse" timer
	banksel	CCPR1L		;save the capture value
	movfw	CCPR1H
	movwf	captHi
	movfw	CCPR1L
	movwf	captLo
	
; Ignore captures for 50us to let possible comparator oscillations cease

	movlw	33		;33 loops = 50us
delay50	decfsz	WREG,F
	goto	delay50

; Compute the time since last capture in deltaHi/deltaLo

	banksel	PORTA
	bcf	PIR1,CCP1IF		;clear the capture flag
	movfw	prevLo		;subtract previous value
	subwf	captLo,W		;lsb first
	movwf	deltaLo
	movfw	prevHi		;ms byte	
	subwfb	captHi,W	
	movwf	deltaHi

; If period is shorter than MIN_PERIOD, ignore the pulse

	movlw	low(MIN_PERIOD)
	subwf	deltaLo,W
	movlw	high(MIN_PERIOD)
	subwfb	deltaHi,W
	btfss	STATUS,C		;carry set if >= MIN_PERIOD
	goto	audioIn		;too short, ignore the pulse

; See if period is shorter than MID_PERIOD. If so, treat as 2400hz (zero) and
;   set the F_IS_ZERO flag. Do NOT save the capture so that the next 2400hz
;   capture will be > MID_PERIOD and capture the bit in that routine.

	movlw	low(MID_PERIOD)
	subwf	deltaLo,W
	movlw	high(MID_PERIOD)
	subwfb	deltaHi,W
	btfsc	STATUS,C		;carry clear if < MID_PERIOD
	goto	chkMax

	bsf	flags,F_IS_ZERO	;this bit will be a zero
	goto	audioIn

; See if period is shorter than MAX_PERIOD. If so, treat as a valid pulse
;   data 0 if F_IS_ZERO is true, else data 1.

chkMax	movlw	low(MAX_PERIOD)
	subwf	deltaLo,W
	movlw	high(MAX_PERIOD)
	subwfb	deltaHi,W
	btfsc	STATUS,C		;carry clear if < MAX_PERIOD
	goto	savePrev		;too long, ignore the pulse

	btfsc	flags,F_IS_ZERO	;is bit zero or one?
	goto	set0		;bit is zero

	bsf	PORTC,RS232_OUT	;RS-232 out is one
	goto	savePrev

set0	bcf	PORTC,RS232_OUT	;RS-232 out is zero	

; Save current capture time as the previous capture time

savePrev	bcf	flags,F_IS_ZERO	;start new bit
	movfw	captLo		;save current timer as previous
	movwf	prevLo
	movfw	captHi
	movwf	prevHi
	goto	audioIn		;repeat


	end