		DEVICE 	PIC16C57,XT_OSC,WDT_ON,PROTECT_OFF

; GENERAL VALUES
INIT_CHAN	equ	72		; channel number to start on (102.3)
DEBOUNCE_DELAY	equ	50		; delay before key considered down
REPEAT_DELAY	equ	3000		; delay before autorepeat
REPEAT_INTERVAL	equ	250		; interval between repeats
RELEASE_DELAY	equ	1000		; delay: key release to display update
POWER_UP_TIME	equ	40000		; time to let power settle

; PLL VALUES
C_REGISTER	equ	01110000b	; enbl: PDout, LD, OSCin/8
R_MODULUS	equ	160		; 16 mhz/160 = 100 khz ref.
MOD_OFFSET	equ	879		; modulus for channel 0

; DISPLAY VALUES
DISP_DELAY	equ	100		; spins between disp commands
SYSTEM_SET_8	equ	00111000b	; sets font, 8bit mode
SYSTEM_SET_4	equ	00101000b	; sets font, 4bit mode
ENTRY_MODE_SET	equ	00000110b	; sets autoincrement on write
DISPLAY_CONTROL	equ	00001100b	; no cursor, no blink, display on
SET_DD_LEFT	equ	10000000b	; goto column 0
SET_DD_RIGHT	equ	11000000b	; goto column 8
RETURN_HOME	equ	00000010b	; undo any shifts
CLEAR_DISPLAY	equ	00000001b	; clear display
SHIFT_RIGHT	equ	00011100b	; shift right
SHIFT_LEFT	equ	00011000b	; shift left

; I/O PINS
dpy_7		equ	RC.5
dpy_6		equ	RC.4
dpy_5		equ	RC.3
dpy_4		equ	RC.2
dpy_rselect	equ	RC.0
dpy_enable	equ	RC.1
up_button	equ	RB.4
down_button	equ	RB.5

pll_locked	equ	RA.0
pll_clock	equ	RA.1
pll_data		equ	RA.3
pll_enable	equ	RA.2


; REGISTERS
p0		equ	09h
p1		equ	0ah
button_state	equ	0bh
t0		equ	10h
t1		equ	11h
lock_duty	equ	12h
lock_state	equ	15h
chan		equ	16h
lastchan	equ	17h
modlo		equ	18h
modhi		equ	19h
divlo		equ	1ah
divhi		equ	1bh
timelo		equ	1ch
timehi		equ	1dh

		RESET	Start

; ***************
; * SUBROUTINES *
; ***************

; Message -- return a char in the initial message
Message
		jmp	PC+W
		retw 'UP THE FCC'

; Show_message -- display initial message
Show_message
		mov	t0,#SET_DD_LEFT
		call	Disp_8
		mov	p0,#0
		mov	p1,#16
		setb	dpy_rselect
:next
		cjne	p1,#8,:normal
		clrb	dpy_rselect
		mov	t0,#SET_DD_RIGHT
		call	Disp_8
		setb	dpy_rselect
:normal
		mov	W,p0
		call	Message
		inc	p0
		mov	t0,W
		call	Disp_8
		djnz	p1,:next
		clrb	dpy_rselect
		ret

; Delay -- kill amount of time specified by p1:p0
Delay
		djnz	p0,Delay
		djnz	p1,Delay
		ret

Init_display
		mov	t0,#SYSTEM_SET_4	; Select 4 bit mode
		call	Disp_4
		mov	t0,#SYSTEM_SET_4	; Now we can send 2 nibbles
		call	Disp_8			; at a time
		mov	t0,#ENTRY_MODE_SET
		call	Disp_8
		mov	t0,#DISPLAY_CONTROL
		call	Disp_8
		ret

; Disp_4 -- send only upper nibble of t0 to display
Disp_4
		movb	dpy_7,t0.7
		movb	dpy_6,t0.6
		movb	dpy_5,t0.5
		movb	dpy_4,t0.4
		mov	t1,#DISP_DELAY
:lag1		djnz	t1,:lag1
		setb	dpy_enable
		clrb	dpy_enable
		ret

; Disp_8 -- send command stored in t0 (both nibbles) to display
Disp_8
		movb	dpy_7,t0.7
		movb	dpy_6,t0.6
		movb	dpy_5,t0.5
		movb	dpy_4,t0.4
		mov	t1,#DISP_DELAY
:lag1		djnz	t1,:lag1
		setb	dpy_enable
		clrb	dpy_enable
		movb	dpy_7,t0.3
		movb	dpy_6,t0.2
		movb	dpy_5,t0.1
		movb	dpy_4,t0.0
		mov	t1,#DISP_DELAY
:lag2		djnz	t1,:lag2
		setb	dpy_enable
		clrb	dpy_enable
		ret

; Disp_clear_right -- erase rightmost 8 characters on display
Disp_clear_right
		mov	t0,#SET_DD_RIGHT
		call	Disp_8
		setb	dpy_rselect
		mov	t0,#' '
		call	Disp_8
		call	Disp_8
		call	Disp_8
		call	Disp_8
		call	Disp_8
		call	Disp_8
		call	Disp_8
		call	Disp_8
		clrb	dpy_rselect
		ret

Disp_unlocked
		mov	t0,#SET_DD_RIGHT
		call	Disp_8
		setb	dpy_rselect

		mov	t0,#'U'
		call	Disp_8
		mov	t0,#'n'
		call	Disp_8
:locked
		mov	t0,#'l'
		call	Disp_8
		mov	t0,#'o'
		call	Disp_8
		mov	t0,#'c'
		call	Disp_8
		mov	t0,#'k'
		call	Disp_8
		mov	t0,#'e'
		call	Disp_8
		mov	t0,#'d'
		call	Disp_8
		clrb	dpy_rselect	; done writing
		ret

Disp_locked
		mov	t0,#SET_DD_RIGHT
		call	Disp_8
		setb	dpy_rselect

		mov	t0,#' '
		call	Disp_8
		call	Disp_8
		jmp	Disp_unlocked:locked

Prog_C
		mov	t0,#8
		clrb	pll_enable	; start to program
:next
		movb	pll_data,p0.7
		setb	pll_clock
		clrb	pll_clock
		rl	p0
		djnz	t0,:next
		setb	pll_enable
		ret

Prog_N
		mov	t0,#16
		clrb	pll_enable
:next
		movb	pll_data,p1.7
		setb	pll_clock
		clrb	pll_clock
		rl	p0
		rl	p1
		djnz	t0,:next
		setb	pll_enable
		ret

Prog_R
		mov	t0,#15
		clrb	pll_enable
:next
		movb	pll_data,p1.6
		setb	pll_clock
		clrb	pll_clock
		rl	p0
		rl	p1
		djnz	t0,:next
		setb	pll_enable
		ret

; Do_button -- do "the right thing" if a button is down
Do_button
		jb	t0.0,:tunedown
		; tune up
		cjne	chan,#100,:straightup
		; up from 100 wraps to 0
		clr	chan
		ret
:straightup
		inc	chan
		ret
:tunedown
		test	chan
		jnz	:straightdown
		;down from 0 wraps to 100
		mov	chan,#100
		ret
:straightdown
		dec	chan
		ret


;****************
;* MAIN PROGRAM *
;****************

		org	256 ; save first 256 locations for subroutines
Start
		mov	!RA,#0001b	; RA.0 input
		mov	!RB,#00000011b	; RB.0, RB.1 inputs
		mov	OPTION,#0

		setb	pll_enable	; active low
		mov	p0,#POWER_UP_TIME>
		mov	p1,#POWER_UP_TIME<
		call	Delay		; let everything power up

		call	Init_display	; get display going
		call	Show_message
		clr	p0
		clr	p1
		call	Delay
		mov	t0,#CLEAR_DISPLAY
		call	Disp_8

		mov	p0,#C_REGISTER	; set up PLL
		call	Prog_C
		mov	p1,#R_MODULUS<
		mov	p0,#R_MODULUS>
		call	Prog_R

		; initialize state of machine

		mov	lastchan,#255	; force initial tune-up
		mov	chan,#INIT_CHAN
		clr	timelo
		clr	timehi
		clr	button_state
		mov	lock_state,#255	; force lock status report

Main_loop
		cjne	chan,lastchan,Tune_up	; tune if channel changed

; Check_buttons
; This is a little screwy.  Basically there are five states,
; each with associated timing and logic.
;
;	button_state	meaning				timing till next
;========================================================================
;	0		buttons both up			?
;	1		button down, not yet debounced	DEBOUNCE_DELAY
;	2		button down			REPEAT_DELAY
;	3		button held down		REPEAT_INTERVAL
;		STATE 3 LOOPS UNTIL BUTTON RELEASED
;	4		buttons both up			RELEASE_DELAY
;
; State 4 is different from state 0 only in that the display is not
; allowed to report the lock status.  This prevents annoying lags
; updating the display while operating the buttons.
; Only after the buttons have been up for a while is the
; display allowed to indicate the lock status.

Check_buttons
		; check status of up/down buttons

		clr	W
		snb	up_button
		or	W,#2
		snb	down_button
		or	W,#1
		xor	W,#3	; buttons are active low
		jnz	:button_zdown

		; no buttons are down
		test	button_state
		jz	Check_lock	; state 0 is easy -- bye!

		; buttons are up but we're not in state 0
		; if we're in state 4, we just wait.
		; otherwise we need to enter state 4.

		cje	button_state,#4,:button_timer
		mov	timehi,#RELEASE_DELAY<
		mov	timelo,#RELEASE_DELAY>
		mov	button_state,#4
		jmp	Check_lock

:button_zdown
		; a button is down
		mov	t0,W		; save bit pattern for Do_button call
		test	button_state	; is this a new keypress?
		jz	:newkey
		cse	button_state,#4
		jmp	:button_timer	; noop -- just makin' time

:newkey
		; new keypress -- set up for timing and clear lock status
		mov	button_state,#1
		mov	timelo,#DEBOUNCE_DELAY>
		mov	timehi,#DEBOUNCE_DELAY<
		call	Disp_clear_right
		mov	lock_state,#255	; force refresh on next state 0
		jmp	Check_lock

:button_timer
		sub	timelo,#1
		subb	timehi,C
		mov	W,timehi
		or	W,timelo
		jnz	Check_lock

		; timer ran out -- time for an action
		;
		; state		meaning of timeout
		; ================================
		; 0		? (doesn't happen)
		; 1		keypress debounced, goto 2
		; 2		first autorepeat, goto 3
		; 3		subsequent autorepeats, stay in 3
		; 4		okay to display lock status, goto 0

		cjne	button_state,#1,:not1

		; key is down long enough for first action
		call	Do_button	; t0 still valid
		inc	button_state
		mov	timelo,#REPEAT_DELAY>	; set delay for 2nd action
		mov	timehi,#REPEAT_DELAY<
		jmp	Check_lock

:not1
		cjne	button_state,#2,:not2

		; key is down long enough for second action
		call	Do_button
		inc	button_state
		mov	timelo,#REPEAT_INTERVAL>; set delay for 3rd action
		mov	timehi,#REPEAT_INTERVAL<
		jmp	Check_lock

:not2
		cjne	button_state,#3,:not3

		; key is down long enough for third (or greater) action
		call	Do_button
		mov	timelo,#REPEAT_INTERVAL>
		mov	timehi,#REPEAT_INTERVAL<
		jmp	Check_lock

:not3
		; key has been up long enough to return to state 0
		clr	button_state
		; fallthru

Check_lock
		; if button state is nonzero, leave alone
		test	button_state
		jnz	Loop_bottom

		jnb	pll_locked,:notlocked

		; pin is high -- increase duty count if < 100
		cse	lock_duty,#100
		inc	lock_duty
		jmp	Duty_check
:notlocked
		; pin is low -- decrease duty count if > 0
		test	lock_duty
		sz
		dec	lock_duty

Duty_check
		; if lock_duty is 90 or higher, let display show "locked"
		cjae	lock_duty,#90,:on

		; currently we're considered unlocked
		test	lock_state
		jz	Loop_bottom	; display already says Unlocked
		clr	lock_state
		call	Disp_unlocked
		jmp	Loop_bottom
:on
		; currently we're considered locked
		cje	lock_state,#1,Loop_bottom	; disp sez locked
		mov	lock_state,#1
		call	Disp_locked
		; fallthru

Loop_bottom
		jmp	Main_loop

Tune_up
		; New channel!  First of all, compute PLL modulus
		; modulus = MOD_OFFSET + 2 * chan
		clr	modhi
		mov	modlo,chan
		clrb	C
		rl	modlo
		rl	modhi
		add	modlo,#MOD_OFFSET>
		addb	modhi,C
		add	modhi,#MOD_OFFSET<
		mov	p0,modlo	; send it to the PLL
		mov	p1,modhi
		call	Prog_N

		; now we just have to display it!  sheesh!
		; for 102.3, modulus is 1023, so the 1000's digit is 1, etc.

		mov	t0,#SET_DD_LEFT
		call	Disp_8
		setb	dpy_rselect	; write mode

		mov	t0,#' '		; skip a space
		call	Disp_8

		; do the 1000's digit -- both modulus and divisor are 16-bit
		mov	divhi,#1000<
		mov	divlo,#1000>
		mov	t0,#'0'-1
:kloop
		inc	t0
		sub	modlo,divlo
		subb	modhi,C
		sub	modhi,divhi
		jnb	modhi.7,:kloop	; loop til negative

		add	modlo,divlo	; correct over subtraction
		addb	modhi,C
		add	modhi,divhi

		cjne	t0,#'0',:showk

		mov	t0,#' '		; print leading 0 as space
:showk
		call	Disp_8

		; now the 100's digit -- modulus is 16-bit, divisor is 8-bit
		mov	divlo,#100
		mov	t0,#'0'-1
:hloop
		inc	t0
		sub	modlo,divlo
		subb	modhi,C
		jnb	modhi.7,:hloop

		add	modlo,divlo	; correct over subtraction
		addb	modhi,C
		call	Disp_8		; show digit

		; now the 10's digit -- modulus and divisor 8-bit
		mov	divlo,#10
		mov	t0,#'0'-1
:dloop
		inc	t0
		sub	modlo,divlo
		jc	:dloop		; jc after sub means jump if NO carry

		add	modlo,divlo	; correct over subtraction
		call	Disp_8		; show digit

		; the 1's digit is left over in modlo
		mov	t0,#'.'		; display decimal point
		call	Disp_8
		mov	t0,modlo	; and last digit
		add	t0,#'0'
		call	Disp_8
		clrb	dpy_rselect	; back to command mode

		mov	lastchan,chan
		jmp	Check_buttons

