;*********************************************************************
;																							;
;        FILE: fiat.asm																;
;      AUTHOR: jonh																	;
; DESCRIPTION: paper-manipulating robot (fiat) peripheral controller	;
;																							;
;*********************************************************************
	list
	processor 16c74a

	#include <p16c74a.inc>

	#define   MACRO_DEFINITION
	include <c:\usr\lib\pic\type.inc>
	include <C:\usr\lib\pic\bank.inc>
	include <C:\usr\lib\pic\int.inc>
	include <C:\usr\lib\pic\mne.inc>
	include <C:\usr\lib\pic\mne2.inc>
	#undefine MACRO_DEFINITION

	list

	mTypeDeclare	fSystemState,BYTE,1	; system status register
	#define			ADDR_WAIT	H'10'		; waiting for address
	#define			bADDR_WAIT		4		; bit number for same value
	#define			RDHI_RDWAIT	H'20'		; hi nybble sent to host; waiting for a read
	#define			bRDHI_RDWAIT	5
	#define 			RDLO_WAIT	H'40'		; lo nybble sent to host; waiting for it to be read
													; or for a write (meaning it was ignored)
	#define			bRDLO_WAIT	6
	mTypeDeclare	fIOAddress,BYTE,1		; host indirection pointer
	mTypeDeclare	fDataCopy,BYTE,1		; the complement of this will be sent on second read
	mTypeDeclare	fServoPhase,BYTE,1	; in milliseconds; ranges 0..19
	mTypeDeclare	fServoDuty,BYTE,1		; H'FF' - Timer0 units. FF=0 duty, 05 = 100% duty
	#define			SERVODUTY	H'F0'		; initial servo duty
	#define			SERVOPORT	PORTA
	#define			SERVOBIT		5
	#define			SERVOPERIOD	H'14'		; in milliseconds; the reset value for fServoPhase
	mTypeDeclare	fTemp,BYTE,2			; temp var (big enough to hold a 16-bit value)

	mTypeDeclare	fSENew,BYTE,1			; current state of SE
	mTypeDeclare	fSEOld,BYTE,1			; old state of SE (last read)
	mTypeDeclare	fSECount,BYTE,2		; result of counting shaft encoders
	mTypeDeclare	fSEError,BYTE,1		; error accumulator
	mTypeDeclare	fSETable,BYTE,16		; lookup table for shaft encoder state machine
	#define			SEINC		0				; bit 0,
	#define			SET_INC	H'01'			; value 1.
	#define			SEDEC		1				; bit 1,
	#define			SET_DEC	H'02'			; value 2.
	#define			SEERR		2				; bit 2,
	#define			SET_ERR	H'04'			; value 4.
	#define			SENONE	3				; bit 3,
	#define			SET_NONE	H'08'			; value 8.

	mTypeDeclare	fTimer2High,BYTE,1	; high bits of a 16-bit sampling of Timer2
	mTypeDeclare	fTimer2Low,BYTE,1		; low bits of same.
	mTypeDeclare	fSEOldTimer2,BYTE,2
	mTypeDeclare	fSENewTimer2,BYTE,2
	mTypeDeclare	fSEPeriod,BYTE,2		; shaft encoder log-average period between clicks

	mTypeDeclare	fDummy1,BYTE,2
	mTypeDeclare	fDummy2,BYTE,2
	mTypeDeclare	fDummy3,BYTE,2

;macro is now defined in mne2.inc
;sub2_fff macro f1,f2,f3
;	local   Done																		;
;	sub_fff f1,f2,f3																	; add high byte
;	sub_fff (f1 + 1),(f2 + 1),(f3 + 1)											; add low byte
;	btsc_f  STATUS,0																	; carry clear (=>borrow)?
;	goto    Done																		; no - skip borrow
;	dec_f   f3																			; yes, decrement high byte
;Done
;	endm	

; Interrupt vectors:

	org			0
	goto			Start											; reset vector
	
	org			4
	goto			Interrupt

	org			10
	dw			'f','i','a','t',' ','v','0','.','2'

	org 20
; Startup: initialize ports
Start
	mBank0

	; set up host I/O protocol
	; the host I/O protocol has three states. At the end of each state, the new
	; state is stored in fSystemState, and sent in the high nybble back to the
	; host. In some states, the low nybble includes data from a read operation.
	;
	; ADDR_WAIT
	; * If a write is received in this state, its value is stored as
	;		an indirect pointer (fIOAddress), and the pointer address is read;
	;		stashed in fDataCopy. The high nybble bit-or RDHI_RDWAIT is sent to
	;		the PSP.
	;		new state = RDHI_RDWAIT
	; * If a read is received: we ignore it (host sees value 'ADDR_WAIT' on psp)
	;		new state = ADDR_WAIT
	; This is the initial protocol state.
	;
	; RDHI_RDWAIT
	; * If a write is received in this state, it is ignored and the
	;		protocol resets to...
	;		new state = ADDR_WAIT
	; * If a read is received in this state, the low nybble of fDataCopy
	;		bit-or RDLO_WAIT is sent to the PSP.
	;		new state = RDLO_WAIT
	;
	; RDLO_WAIT
	; * If a write is received in this state, its value is written to fIOAddress
	;		(the addr value written at the beginning of the protocol), and the
	; 		protocol is complete.
	;		new state = ADDR_WAIT
	; * If a read is received in this state, the host has completely
	;		retrieved the desired value, so the protocol is complete.
	;		new state = ADDR_WAIT
	;
	; The resulting protocol can be used as follows from the host:
	;
	; * To write a register on the PIC:
	;	read until PSP==ADDR_WAIT
	;	write ADDR to PSP
	;	read until PSP==RDHI_RDWAIT (PIC is now in RDLO_WAIT state, since we read.)
	;	write DATA to PSP
	;	read until PSP==ADDR_WAIT (if you want to know when it's done.)
	;
	; * To read a register on the PIC:
	;	read until PSP==ADDR_WAIT
	;	write ADDR to PSP
	;	read until PSP==RDHI_RDWAIT; low nybble is DATA_HI
	;	read until PSP==RDLO_WAIT; low nybble is DATA_LO
	;	read until PSP==ADDR_WAIT (if you want to know when it's done.)
	;
	; * To reset the protocol to ensure we're in addrWait:
	;	read until PSP==ADDR_WAIT
	;
	mov_lf		ADDR_WAIT,fSystemState
	mov_lf		fIOAddress,fIOAddress	; set up next read to read from indir pointer.
													; no reason; seemed slightly less meaningless than
													; defaulting to 0 = INDF.

	; port A
	mov_lf		H'07',ADCON1		; turn off A/D inputs (make pins digital)
	mov_lf		H'00',TRISA			; configure port A as an output

	; port B
	mov_lf		H'C0',TRISB			; 7:6 inputs (shaft encoder), 5:0 outputs (motor directions)

	; port C
	mov_lf		H'F8',TRISC			; 7:6 unused (inputs), 5:3 optical sensors (inputs)
											; 2:0 pwm (outputs)

	; ports D & E
	; configure parallel slave port
	; (page 54)
	; ADCON1 PCFG2:PCFG2 must be set (done above) so that RE2:RE0 are digital
	; set TRISE: PSPMODE=1, data dir=111
	mov_lf		H'17',TRISE

	; enable PSP interrupt
	bc_f			PIR1,PSPIF			; clear interrupt state to avoid spurious interrupts
	bs_f			PIE1,PSPIE

	; Timer 2: period for PWM.
	; freq = 1kHz. period = 1ms = [(PR2)+1]*4*62.5ns (16MHz osc)*prescale
	; => prescale = 16:1, PR2 = 250
	mov_lf		H'07',T2CON			; Timer 2 on, prescaler = 16:1
	mov_lf		H'FA',PR2				; => period = 1ms

	; set duty cycle to zero
	mov_lf		H'00',CCPR1L		; duty cycle = 0%
	mov_lf		H'00',CCPR2L		; duty cycle = 0%

	; turn on PWM
	mov_lf		H'0F',CCP1CON		; 0 nybble = usused PWM LSBits. F nybble = PWM mode
	mov_lf		H'0F',CCP2CON		; 0 nybble = usused PWM LSBits. F nybble = PWM mode

	; turn on Timer2 interrupt (used to count ms for servo PWM)
	bc_f			PIR1,TMR2IF			; clear spurious interrupt
	bs_f			PIE1,TMR2IE			; enable interrupt

	; initialize fServoPhase to SERVOPERIOD. (not crucial; handy for simulation)
	mov_lf		SERVOPERIOD,fServoPhase

	; set up Timer0 interrupt (used to count us for servo duty cycle)
	bc_f			INTCON,T0IE					; disabled at first (enabled each time it is reset)
	and_flf		OPTION_REG,H'C0',fTemp	; clear timer0-related bits
	ior_flf		fTemp,H'03',OPTION_REG	; T0CS,T0SE stay 0; PSA=0 => prescaler used for Timer0
													; PS<2:0> = 3 => prescaler value of 16
	mov_lf		SERVODUTY,fServoDuty			; reset servo duty time to reasonable default

	; enable interrupts for shaft encoder inputs, to make sure SE state machine runs
	; whenever there is shaft encoder activity.
	bs_f			INTCON,RBIE

	; load shaft encoder state machine lookup table
	mov_lf		SET_NONE,fSETable+H'00'				; 00->00 = no change
	mov_lf		 SET_INC,fSETable+H'01'				; 00->01 = INC
	mov_lf		 SET_DEC,fSETable+H'02'				; 00->10 = DEC
	mov_lf		 SET_ERR,fSETable+H'03'				; 00->11 = ERR
	mov_lf		 SET_DEC,fSETable+H'04'				; 01->00 = DEC
	mov_lf		SET_NONE,fSETable+H'05'				; 01->01 = no change
	mov_lf		 SET_ERR,fSETable+H'06'				; 01->10 = ERR
	mov_lf		 SET_INC,fSETable+H'07'				; 01->11 = INC
	mov_lf		 SET_INC,fSETable+H'08'				; 10->00 = INC
	mov_lf		 SET_ERR,fSETable+H'09'				; 10->01 = ERR
	mov_lf		SET_NONE,fSETable+H'0A'				; 10->10 = no change
	mov_lf		 SET_DEC,fSETable+H'0B'				; 10->11 = DEC
	mov_lf		 SET_ERR,fSETable+H'0C'				; 11->00 = ERR
	mov_lf		 SET_DEC,fSETable+H'0D'				; 11->01 = DEC
	mov_lf		 SET_INC,fSETable+H'0E'				; 11->10 = INC
	mov_lf		SET_NONE,fSETable+H'0F'				; 11->11 = no change

	clr2_f		fSECount					; clear the accumulators
	clr_f			fSEError
	mov_ff		PORTB,fSENew					; initialize SE state registers to match actual
	swap_f		fSENew							; SE readings. (avoids a spurious initital inc/dec/err)
	and_flf		fSENew,H'0C',fSENew

	clr2_f		fTimer2High
	clr2_f		fSEOldTimer2
	clr2_f		fSENewTimer2
	mov2_lf		H'FFFF',fSEPeriod				; looks like SE has not moved in a while.

	; enable all interrupts
	bs_f			INTCON,PEIE
	bs_f			INTCON,GIE

loop
	nop
	sub2_fff		fDummy1,fDummy2,fDummy3
	goto			loop

; Interrupt: interrupt handler
Interrupt:
	mIntSave

	;;;; Parallel Slave Port

ih_psp_start						; Parallel Slave Port interrupt handler
	btss_f		PIR1,PSPIF		; test Parallel Slave Port interrupt
	goto			ih_psp_end

ih_psp_checkinput
	btss_f		TRISE,IBF			; if IBF is set, host wrote to our port
	goto			ih_psp_noinput

	; handle write from host to our PSP
	btss_f		fSystemState,bADDR_WAIT
	goto			ih_psp_wr_next
ih_psp_wr_ADDR_WAIT					; write occured in ADDR_WAIT
	mov_ff		PORTD,fIOAddress	; store address for next write or next read
	movwf			FSR					; (address was left in w after mov_ff; avoid keith's macros
											; here because we're messing with FSR/INDF)
	movf			INDF,w				; read requested address
	movwf			fDataCopy			; stash for later read of lo nybble
	and_flf		fDataCopy,H'f0',fTemp		; extract hi nybble
	swap_f		fTemp								; move hi nybble to lo
	ior_flf		fTemp,RDHI_RDWAIT,PORTD		; write new state + data lo nybble to PSP
	mov_lf		RDHI_RDWAIT,fSystemState
	goto			ih_psp_done

ih_psp_wr_next
	btss_f		fSystemState,bRDHI_RDWAIT
	goto			ih_psp_wr_RDLO_WAIT	; write occurred in RDLO_WAIT state
ih_psp_wr_RDHI_RDWAIT					; write occurred in RDHI_RDWAIT state
	movf			PORTD,w					; read value to clear input buffer, but ignore it.
	goto			ih_psp_wr_reset		; reset protocol

ih_psp_wr_RDLO_WAIT					; write from host was data to store at *fIOAddress.
	movf			fIOAddress,w		; set up INDF to route the data to *fIOAddress
	movwf			FSR
	movf			PORTD,w				; grab host data
	movwf			INDF					; store it in *fIOAddress
											; protocol is complete; drop through to reset
ih_psp_wr_reset
	mov_lf		ADDR_WAIT,PORTD	; indicate new state to host
	mov_lf		ADDR_WAIT,fSystemState	; keep track of state locally
	goto			ih_psp_done
ih_psp_noinput

ih_psp_checkoutput					; did host read our port?
	btsc_f		TRISE,OBF			; yes -- output buffer is empty
	goto			ih_psp_nooutput
ih_psp_rd
	btss_f		fSystemState,bADDR_WAIT
	goto			ih_psp_rd_next
ih_psp_rd_ADDR_WAIT
	mov_lf		ADDR_WAIT,PORTD	; protocol violation -- ignore it; refill PSP buffer with state
	goto			ih_psp_done
ih_psp_rd_next
	btss_f		fSystemState,bRDHI_RDWAIT
	goto			ih_psp_rd_RDLO_WAIT
ih_psp_rd_RDHI_RDWAIT
	and_flf		fDataCopy,H'0f',fTemp	; get lo nybble of previously captured read
	ior_flf		fTemp,RDLO_WAIT,PORTD	; write data nybble + new state to PSP
	mov_lf		RDLO_WAIT,fSystemState
	goto			ih_psp_done
ih_psp_rd_RDLO_WAIT
	mov_lf		ADDR_WAIT,PORTD
	mov_lf		ADDR_WAIT,fSystemState
	goto			ih_psp_done
ih_psp_nooutput

ih_psp_done
	bc_f			PIR1,PSPIF			; clear interrupt pin
ih_psp_end

	;;;; Timer 2

ih_tmr2_start							; timer2 (servo period) interrupt handler
	mov_ff		TMR2,fTimer2Low	; sample Timer2 (regardless of whether its interrupt has fired)
	btss_f		PIR1,TMR2IF			; test timer2 interrupt
	goto			ih_tmr2_end

	; timer2 has rolled recently, possibly after when we sampled it above, so
	; we should sample it again to make sure the low bits are consistent (since
	; we're incrementing the high bits here).
	mov_ff		TMR2,fTimer2Low	; resample low bits
	inc_f			fTimer2High			; inc high bits

	decsz_f		fServoPhase			; count to 20 ms
	goto			ih_tmr2_check1

	; at phase zero, PWM has been on 1ms. Start timer0, which should vary from
	; 0 - 1 ms.
	mov_ff		fServoDuty,TMR0	; fServoDuty should be 255-desired counts
	bc_f			INTCON,T0IF			; clear interrupt flag for good measure
	bs_f			INTCON,T0IE			; turn Timer 0 back on for this duty-cycle count.
	mov_lf		SERVOPERIOD,fServoPhase	; reset 20-millisecond period counter
	goto			ih_tmr2_done		; skip phase==1 test, since we can only get here when phase=0.
ih_tmr2_check1
	decsz_f		fServoPhase			; is fServoPhase == 1?
	goto			ih_tmr2_not1
	bs_f			SERVOPORT,SERVOBIT	; begin PWM 1 ms early, so timer0 has full range from 1ms-2ms
ih_tmr2_not1
	inc_f			fServoPhase			; undo the extra dec from _check1

ih_tmr2_done
	bc_f			PIR1,TMR2IF			; clear timer2 interrupt
ih_tmr2_end

ih_tmr0_start
	btss_f		INTCON,T0IE			; timer0 int enabled? (we might be here for some other
	goto			ih_tmr0_end			; reason; only honor interrupt flag when ints enabled.)
	btss_f		INTCON,T0IF			; timer0 (servo duty cycle) interrupt set?
	goto			ih_tmr0_end
	bc_f			SERVOPORT,SERVOBIT	; servo PWM time is over
	bc_f			INTCON,T0IE			; turn off timer0 interrupts until next time (save cycles)
	bc_f			INTCON,T0IF			; clear the interrupt (so it doesn't show up later)
ih_tmr0_end

ih_se_start		; count shaft-encoder clicks
	; the shaft encoder code is a simple state machine; so we could
	; run it any old time. Check interrupts anyway to avoid slowing down
	; other interrupt handlers needlessly
	btss_f		INTCON,RBIF
	goto			ih_se_end
	mov_ff		PORTB,fSENew
	swap_f		fSENew				; store values in f{New,Old}SE in bits <3:2> for speed.
	and_flf		fSENew,H'0C',fSENew	; That way we only swap and AND once per individual value
	mov_ff		fSENew,fTemp
	sr_f			fTemp
	sr_f			fTemp						; bits <1:0> of fTemp are now fSENew, and...
	ior_fff		fSEOld,fTemp,fTemp	; ...bits <3:2> are fSEOld.
	add_flf		fTemp,fSETable,fTemp	; look up what to do based on (old,new) state in a table
	mov_ff		fTemp,FSR
	mov_ff		INDF,fTemp			; fTemp now contains a word saying what to do

ih_se_chknone
	btsc_f		fTemp,SENONE		; nothing happened? (common case)
	goto			ih_se_chkdone		; skip period measurement -- no click happened.
ih_se_chkinc
	btss_f		fTemp,SEINC			; increment count?
	goto			ih_se_chkdec
	inc2_f		fSECount
	goto			ih_se_period
ih_se_chkdec
	btss_f		fTemp,SEDEC			; decrement count?
	goto			ih_se_chkerr
	dec2_f		fSECount
	goto			ih_se_period
ih_se_chkerr
	inc_f			fSEError				; only option left -- an SE sampling error

ih_se_period
	; compute time since last click
	mov2_ff		fTimer2High,fSENewTimer2
	sub2_fff		fSENewTimer2,fSEOldTimer2,fSEPeriod
	mov2_ff		fSENewTimer2,fSEOldTimer2	; stash old value
ih_se_perioddone

ih_se_chkdone
	mov_ff		fSENew,fSEOld		; stash current SE value as old, for next compare
	bc_f			INTCON,RBIF			; clear interrupt bit so we don't run again until next change
ih_se_end

	mIntRestore
	ret_int


;*********************************************************************
	end																					;
;*********************************************************************
