.include "../defs/8535def.inc"


;-------------------------------------------------------------
;	Register allocation
;-------------------------------------------------------------
.def		tz 	=r6		; Time zone information
.equ		tzmask	=$1f		; Negative offset from GMT
.equ		dst	=5		; True if in Daylight Savings' Time

.def		info 	=r28
.equ		master	=0		; True if in master mode
.equ		audible	=1		; True if audible tick/beep turned on
.equ		sync	=2		; True if dial sync in progress
.equ		combo	=3		; True if button combo detected
.equ		dstadj	=4		; True if time adjust in progress
.equ		dial	=5		; Dial drive phase
.equ		intdisp	=6		; True if time display is to be
					;   updated by interrupt
.equ		locdisp	=7		; True if displaying local time

.def		display_state	=r3
.def		menu_state	=r4
.def		menu_select	=r5
.def		ss 	=r7
.def		mm 	=r8
.def		hh 	=r9
.def		day 	=r10
.def		mon 	=r11
.def		year 	=r12

;-------------------------------------------------------------
;	EEPROM allocation
;-------------------------------------------------------------

			.eseg
ee_time_zone:		.db	8	; # of hours after GMT
ee_err_cnt:	.db	0

;-------------------------------------------------------------
;	vectors
;-------------------------------------------------------------

		.cseg
		.org $0000

		rjmp	reset

;		rjmp	ext_int0

		.org	INT1addr
		rjmp	ext_int1

;		rjmp	tim2_comp

		.org	OVF2addr
;		rjmp	tim2_ovf

		.org	ICP1addr
		rjmp	tim1_capt

;		rjmp	tim1_compa

		.org	OC1Baddr
		rjmp	tim1_compb

		.org	OVF1addr
		rjmp	tim1_ovf

		.org	OVF0addr
		rjmp	tim0_ovf

;		rjmp	spi_stc

		.org	URXCaddr
		rjmp	uart_rxc

;		rjmp	uart_dre
;		rjmp	uart_txc

		.org	ADCCaddr
		rjmp	adc_conv_

		.org	ERDYaddr
		rjmp	ee_rdy_

;		rjmp	ana_comp

reset:
;--------------------------------------------------------
;  Clear RAM

		ldi	XL,$60
		ldi	XH,0
		ldi	r16,0
clear:		st	X+,r16
		cpi	XL,LOW(RAMEND)
		brne	clear
		cpi	XH,HIGH(RAMEND)
		brne	clear

;--------------------------------------------------------
;	stackpointer setup

		ldi	r16, high(RAMEND)
		out	SPH, R16
		ldi	r16, low(RAMEND)
		out	SPL,r16

;--------------------------------------------------------
;  Init EEPROM address

;		clr	r16
;		out	EEARL,r16
;		out	EEARH,r16

;-------------------------------------------------------
;
; Port allocation:
;
;     PORTA:
;               0:  Light sense (ADC0)
;             4-7:  LCD data bus (bi-directional)
;
;     PORTB:
;               0:  Speaker
;               1:  Clock dial output 1
;               2:  Clock dial output 2
;
;     PORTC:
;             0-2:  Button inputs (w/pull-ups) L-R
;               5:  LCD register select output
;               6:  LCD R/~W output
;               7:  LCD Enable output
;
;     PORTD:
;               0:  UART receive data
;               1:  UART transmit data
;               3:  INT1 master serial activity sense input
;               4:  Master/~slave select output
;               5:  1PPS out (OC1A)
;               6:  1PPS in (ICP)
;               7:  LCD backlight drive (OC2)
;
;------------------------------------------------------------
;  Initialize ports

		ldi	r16,$00
		out	PORTA,r16	;LCD data bus
		out	PORTB,r16
		ldi	r16,$0F		;pull-ups for buttons
		out	PORTC,r16
		ldi	r16,$f0
		out	DDRA,r16	;set port A (LCD bus) as output
		ldi	r16,$e0
		out	DDRC,r16	;set port C LCD contro lines as output
		ldi	r16,$07
		out	DDRB,r16	;set port B dial lines to output
		ldi	r16,$b0
		out	DDRD,r16	;master/slave to output
		ldi	r16,$00
		out	PORTD,r16	;set to slave


;--------------------------------------------------------------
; Initialize watchdog timer

		wdr
		ldi	r16,(1 << WDE) | (1 << WDP0) | (1 << WDP1) | (1 << WDP2)
;		out	WDTCR,r16	; Watchdog set for 1.9s

;--------------------------------------------------------------
; Initialize counter 2: LCD backlight PWM

		ldi	r16,$c0
		out	OCR2,r16	; To be set by ADC
		ldi	r16,(1<<PWM2)+(2<<COM20)+(4<<CS20)  ; /64 - ~112 Hz flicker
		out	TCCR2,r16
		sbi	PORTD,7		; OC2 to output

;--------------------------------------------------------------
; Initialize counter 0: button scan

		ldi	r16,(3<<CS00)  	; /64
		out	TCCR0,r16
		in	r16,TIMSK	; Enable timer 0 OVF interrupt
		ori	r16,(1 << TOIE0)
		out	TIMSK,r16

;--------------------------------------------------------------
; Initialize counter 1 1-second value.

		ldi	r16,LOW(57600)
		sts	sec_count,r16
		ldi	r16,HIGH(57600)
		sts	sec_count+1,r16

;--------------------------------------------------------------
; LCD display initialization

		ldi	r16,255
		ldi	r17,255		; long delay for LCD power-up
lcdi0:		dec	r16
		brne	lcdi0
		dec	r17
		brne	lcdi0

		ldi	r16,$33		; Force display to 8-bit mode
		rcall	lcd_cmd
		ldi	r16,$33
		rcall	lcd_cmd
		ldi	r16,$33
		rcall	lcd_cmd
		rcall	busywait_
		cbi	PORTC,5
		cbi	PORTC,6
		cbi	PORTC,7
		ldi	r16,$20		; 4-bit (low bits won't get sent)
		out	PORTA,r16
		rcall	strobe_
		ldi	r16,$20		; 4-bit, 1-line, 5x7
		rcall	lcd_cmd
		ldi	r16,$0c		; display on, no cursor, no underline
		rcall	lcd_cmd

		ldi	r16,$40		; set CGRAM address 0
		rcall	lcd_cmd
		ldi	r17,64		; # of bytes of character data
		ldi	ZL,LOW(hands<<1)
		ldi	ZH,HIGH(hands<<1)
lcdi1:		lpm			; Send char defs to display
		adiw	ZL,1
		mov	r16,r0
		rcall	lcd_data
		dec	r17
		brne	lcdi1
		ldi	r16,$80		; set display address
		rcall	lcd_cmd

		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd

;--------------------------------------------------------------
; Copy initial table of scheduled events to RAM

.dseg

.equ		cbufflen=64
capt_avg:	.byte	2
capt_sum:	.byte	3
capt_holdoff:	.byte	1
capt_ptr:	.byte	2
capt_buff:	.byte	2*cbufflen
.equ		capt_buff_end=capt_buff+(2*cbufflen)

.cseg

		rcall	sched_copy

		ldi	XL,LOW(sched_table)
		ldi	XH,HIGH(sched_table)
		sts	sched_ptr,XL	; Initialize table pointer
		sts	sched_ptr+1,XH
		ldi	XL,LOW(capt_buff) ; Initialize capture average buffer
		ldi	XH,HIGH(capt_buff)
		sts	capt_ptr,XL
		sts	capt_ptr+1,XH
		ldi	r16,cbufflen/2-1
		sts	capt_sum,r16	; Add .4999 for bias
		ldi	r16,cbufflen+1
		sts	capt_holdoff,r16; Samples to wait until using avg.
		clr	r0
		out	OCR1BH,r0	; Initialize compare
		inc	r0
		out	OCR1BL,r0

;--------------------------------------------------------------
; Initialize counter 1: general tick and clock face pulse timer

;		ldi	r16,(3<<COM1A0)	; Set 1pps on compare
		ldi	r16,(1<<COM1A0)	; Toggle 1pps on compare
		out	TCCR1A,r16
;		ldi	r16,(1<<ICES1)+(1<<CTC1)+(3<<CS10)
		ldi	r16,(1<<ICES1)+(3<<CS10)
		out	TCCR1B,r16	; Capture rising, wrap on A, /64
		lds	r16,sec_count+1	; Approx. 1 second
		out	OCR1AH,r16
		lds	r16,sec_count
		out	OCR1AL,r16
		in	r16,TIMSK	; Enable timer 0 OVF interrupt
		ori	r16,(1<<TICIE1)+(1<<OCIE1B)
		out	TIMSK,r16	; Int. on capture and compare B

;-------------------------------------------------------------
; Get timezone

		ldi	r16,$ff		; clear EEPROM write busy flag
		sts	ee_count,r16
		ldi	ZL,LOW(ee_time_zone)
		ldi	ZH,HIGH(ee_time_zone)
		rcall	EERead
		cpi	r16,$FF
		brne	gtz1
		ldi	r16,8		; EEPROM is blank, use default
		rcall	EEWrite
gtz1:		mov	tz,r16

		ldi	ZL,LOW(ee_err_cnt)
		ldi	ZH,HIGH(ee_err_cnt)
		rcall	EERead
		cpi	r16,$FF
		brne	gtz2
		clr	r16		; clear error count
		rcall	EEWrite

gtz2:		clr	info		; clear various flags

;--------------------------------------------------------------
; Initialize UART

		ldi	r16,23		; 9600 baud
		out	UBRR,r16
		ldi	r16,(1 << RXEN) + (1 << TXEN) + (1 << RXCIE)
		out	UCR,r16		; Enable receive and transmitter

;--------------------------------------------------------------
; Set initial UART receive parser vector

		ldi	ZL,LOW(onc_init)
		ldi	ZH,HIGH(onc_init)
		sts	rxvec,ZL
		sts	rxvec+1,ZH

;--------------------------------------------------------------
; Initialize ADC

		ldi	r16,(1<<ADEN)	; Must be enabled for ADMUX
		out	ADCSR,r16
		ldi	r16,0
		out	ADMUX,r16	; Select ADC0
		ldi	r16,(1<<ADEN)+(1<<ADSC)+(1<<ADFR)+(1<<ADIE)+(7<<ADPS0)
		out	ADCSR,r16	; Free-run 1/64 rate

;--------------------------------------------------------------
; Set up master serial sense interrupt

		ldi	r16,(1<<ISC10)	; Interrupt on any change in sense
		out	MCUCR,r16
		ldi	r16,(1<<INT1)
		out	GIMSK,r16	; Enable interrupt

;--------------------------------------------------------------
; Synchronize counter 1 to Oncore seconds

sync0:		sbic	PIND,6		; Wait for 1pps to go low
		rjmp	sync0

sync1:		sbis	PIND,6		; Wait for 1pps to go high
		rjmp	sync1

		clr	r16
		out	TCNT1H,r16
		out	TCNT1L,r16
		out	OCR1AH,r16
		out	OCR1AL,r16
		lds	XL,sched_ptr	; Skip top of second handler
		lds	XH,sched_ptr+1
		ld	r16,X+
		ld	r17,X+
		out	OCR1BH,r17
		out	OCR1BL,r16
		adiw	XL,2
		sts	sched_ptr,XL
		sts	sched_ptr+1,XH

;--------------------------------------------------------------
; Enable interrupts

		ldi	r16,$ff
		out	TIFR,r16	; Clear pending timer interrupts
		in	r16,UDR		; Clear pending UART interrupts
		sei

;--------------------------------------------------------------
; Determine initial master/slave status

		ldi	r16,$ff		; Reset status/position reception flag
		sts	rxposflag,r16

		ldi	r16,$01	; clear display
		rcall	lcd_cmd

		ldi	r16,$02	; cursor home
		rcall	lcd_cmd

		ldi	ZL,LOW(listen << 1)
		ldi	ZH,HIGH(listen << 1)
		rcall	lcd_flash
li0:		ldi	r17,5		; Listen for 5 seconds
		sts	master_time,r17
		inc	r17		; Extra second for countdown to finish
li1:		lds	r18,eigth_tick
li2:		lds	r16,eigth_tick
		sub	r16,r18
		cpi	r16,8
		brne	li2
		ldi	r16,'.'
		rcall	lcd_data
		dec	r17
		brne	li1

		rcall	lcd_status	; master/slave status set by INT1
		lds	r18,eigth_tick
li9:		lds	r16,eigth_tick
		sub	r16,r18
		cpi	r16,2*8		; display status for 2 seconds
		brne	li9

;--------------------------------------------------------------
; Set the initial time from Oncore

inittime:	lds	r16,rxposflag	; Check for received status during listen
		cpi	r16,$ff
		brne	it1
		sbrc	info,master
		rjmp	it0
		ldi	r16,$01		; No data in slave mode
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(no_data_err<<1)
		ldi	ZH,HIGH(no_data_err<<1)
		rcall	lcd_flash	; Display error
		rjmp	li0		; Listen again
				
it0:		ldi	ZL,LOW(pos_stat << 1)
		ldi	ZH,HIGH(pos_stat << 1)
		ldi	r16,3
		rcall	txcmd		; No status, transmit status cmd
it1:		ldi	r16,$ff
		sts	rxposflag,r16
it2:		lds	r16,rxposflag
		cpi	r16,1		; Loop until new status arrives
		brne	it2
		lds	r16,rxdoptyp	; Check antenna current
		andi	r16,$c0
		breq	it5
		sbrs	r16,6
		rjmp	it4
;
;  Antenna overcurrent indicates a short
;
		ldi	ZL,LOW(short_str<<1)
		ldi	ZH,HIGH(short_str<<1)
it3:		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd		; Display diagnostic
		rcall	lcd_flash
		rjmp	it1		; Try again
;
;  Antenna undercurrent indicates an open or missing antenna
;
it4:		ldi	ZL,LOW(open_str<<1)
		ldi	ZH,HIGH(open_str<<1)
		rjmp	it3
		
it5:		lds	ss,rxsecond	; Load initial time
		lds	mm,rxminute
		lds	hh,rxhour
		lds	day,rxday
		lds	mon,rxmonth
		lds	r16,rxyear	; trim year to 2 digitsopi		lds	r17,rxyear+1
		ldi	r18,100
		ldi	r19,0
		rcall	div16u
		mov	year,r14

;-------------------------------------------------------------
; Determine initial daylight savings' time state

dst_init:	push	hh
		push	day
		push	mon
		rcall	localtime	; Convert to local time (no dst)
		mov	hh,r17		; Temporarily set to local
		mov	day,r18
		mov	mon,r19
		cpi	r19,10
		breq	di4		; October
		brsh	di9		; Nov-Dec, leave clear
		cpi	r19,4
		brlo	di9		; Jan-Mar, leave clear

di1:		cpi	r18,8		; Check for first week in April
		brsh	di8		; After first week, set DST
		rcall	day_of_week	; Find day of week
		cp	r16,r18
		brsh	di9
		rjmp	di8		; First Sunday or later, set DST

di4:		subi	r18,25		; Check for last week in October
		brlo	di8		; Before last week, set DST
		rcall	day_of_week	; Find day of week
		cp	r18,r16
		brsh	di9		; Last Sunday or later, standard time

di8:		mov	r16,tz
		sbr	r16,(1<<dst)	; Set DST flag
		mov	tz,r16
di9:		pop	mon		; restore GMT
		pop	day
		pop	hh

;-------------------------------------------------------------
; Enter GMT display state

		clr	display_state
		clr	menu_state
		clr	menu_select

		rjmp	main1

;--------------------------------------------------------------
; Top of main loop

.dseg

button_event:	.byte	1	; Button event.  Takes on these values:
.equ		ev_none		=0
.equ		ev_display	=1
.equ		ev_menu		=2
.equ		ev_select	=3
.equ		ev_23		=4

; Flag bits indicating changes to be passed in r16 to state handlers
.equ		ch_state	=0
.equ		ch_select	=1
.equ		ch_23		=2

.cseg
.EQU		MAX_DISPLAY_STATE = 5
.EQU		MAX_MENU_STATE = 7
.EQU		MAX_SLAVE_MENU_STATE = 4

main:		cli			; disable interrupts
		ldi	r17,ev_none
		lds	r16,button_event; read & reset button event
		sts	button_event,r17;  as one atomic operation
		sei			; re-enable interrupts

		cpi	r16,ev_none
		breq	main6

		cpi	r16,ev_display
		brne	main2
		tst	menu_state	; Display event, were we in menu?
		breq	main1
		clr	menu_state	; Yes, restore previous display state
		ldi	r16,(1<<ch_state)	;    and flag new state
		rcall	display_dispatch;    go execute display state
		rjmp	main

main1:		ldi	r16,MAX_DISPLAY_STATE
		cp	display_state,r16
		brlo	main1a
		clr	display_state	; (wrap display state to 1)
main1a:		inc	display_state	; No, increment state
		ldi	r16,(1<<ch_state);   and flag new state
		rcall	display_dispatch;   go execute display state
		rjmp	main

main2:		cpi	r16,ev_menu
		brne	main4
		ldi	r16,MAX_MENU_STATE
		sbrs	info,master
		ldi	r16,MAX_SLAVE_MENU_STATE
		cp	menu_state,r16
		brlo	main2a
		clr	menu_state
main2a:		inc	menu_state
		clr	menu_select
		ldi	r16,(1<<ch_state)+(1<<ch_select)
		rcall	menu_dispatch
		rjmp	main

main4:		cpi	r16,ev_select
		brne	main5
		inc	menu_select
		ldi	r16,(1<<ch_select)
		rjmp	main7

main5:		cpi	r16,ev_23
		brne	main6
		ldi	r16,(1<<ch_23)
		rjmp	main7

main6:		clr	r16		; clear change flags
main7:		tst	menu_state
		breq	main8
		rcall	menu_dispatch
		rjmp	main

main8:		rcall	display_dispatch
		rjmp	main

display_dispatch:
		ldi	ZL,LOW(dstate_table << 1)
		ldi	ZH,HIGH(dstate_table << 1)
		mov	r17,display_state
		dec	r17
		lsl	r17		; multiply index by element size
		add	ZL,r17		; Offset into table
		clr	r17
		adc	ZH,r17
		lpm			; Routine address
		mov	r17,r0
		adiw	ZL,1
		lpm
		mov	ZH,r0
		mov	ZL,r17
		icall
		ret


;--------------------------------------------------------------

utc_state:	sbrs	r16,ch_state
		ret			; Same state as before, nothing to do

		cbr	info,(1<<intdisp)+(1<<locdisp)
		clr	r16		; Display it all
		rcall	timedisp
		sbr	info,(1<<intdisp)	; Tell interrupt to update display
		ret

;--------------------------------------------------------------

local_state:	sbrs	r16,ch_state
		ret

		cbr	info,(1<<intdisp)
		clr	r16		; Display it all
		rcall	ltimedisp
		sbr	info,(1<<intdisp)+(1<<locdisp)
		ret

;--------------------------------------------------------------

disp_lat:	ldi	XL,'N'		; Positive indicator
		ldi	XH,'S'		; Negative indicator
		lds	r17,rxlat+3	; Load current lattitude
		lds	r18,rxlat+2
		lds	r19,rxlat+1
		lds	r20,rxlat

disp_lat_lon:	lds	r16,rxposflag
		cpi	r16,1
		breq	dll1
		rjmp	dll9		; New data has arrived, display

dll1:		cbr	info,(1<<intdisp)
		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd
		tst	r20		; See if value is negative
		brpl	dll2
		com	r17		; If so, negate
		com	r18		; (compliment, and add one)
		com	r19
		com	r20
		ldi	r16,1
		add	r17,r16
		clr	r16
		adc	r18,r16
		adc	r19,r16
		adc	r20,r16
		mov	r16,XH		; Display negative value
		rjmp	dll3

dll2:		mov	r16,XL		; else display positive value
dll3:		rcall	lcd_data
		ldi	r16,' '
		rcall	lcd_data
		ldi	r21,LOW(3600000)	; Divide milliarcseconds
		ldi	r22,BYTE2(3600000)	;  by 3,600,000
		ldi	r23,BYTE3(3600000)	;   to yield degrees
		ldi	r24,BYTE4(3600000)
		rcall	div32u
		push	r16
		mov	r16,r17
		rcall	lcd_dec		; Display degrees
		ldi	r16,$DF		; Degree symbol
		rcall	lcd_data
		ldi	r16,' '
		rcall	lcd_data
		pop	r16
		mov	r17,r13		; Get remainder
		mov	r18,r14
		mov	r19,r15
		mov	r20,r16
		ldi	r21,LOW(60000)	; Divide by 60,000
		ldi	r22,BYTE2(60000);  to yield minutes
		ldi	r23,BYTE3(60000)
		ldi	r23,BYTE4(60000)
		rcall	div32u
		push	r16
		mov	r16,r17
		rcall	lcd_dec
		ldi	r16,'\''
		rcall	lcd_data
		ldi	r16,' '
		rcall	lcd_data
		pop	r16
		mov	r17,r13		; Get remainder
		mov	r18,r14
		mov	r19,r15
		mov	r20,r16
		ldi	r21,LOW(1000)	; Divide by 1000
		ldi	r22,BYTE2(1000)	;  to yield seconds
		ldi	r23,BYTE3(1000)
		ldi	r23,BYTE4(1000)
		rcall	div32u
		push	r16
		mov	r16,r17
		rcall	lcd_dec
		ldi	r16,'.'
		rcall	lcd_data
		pop	r16
		ldi	r29,3		; Convert 3 digits
		mov	r16,r13		; Get 16-bits of remainder
		mov	r17,r14
dll4:		ldi	r18,LOW(10)	; Divide by 10
		ldi	r19,HIGH(10)
		rcall	div16u
		push	r14		; Push remainder
		dec	r29
		brne	dll4		; Next digit

		ldi	r29,3		; Pop digits in reverse order
dll5:		pop	r16		; Get most significant digit
		ori	r16,'0'		; Convert to ASCII and display
		rcall	lcd_data
		dec	r29		; Repeat for as many digits as we pushed
		brne	dll5

		ldi	r16,'"'
		rcall	lcd_data
		ldi	r16,' '
		rcall	lcd_data
		lds	r16,rxdoptyp	; Check for survey mode
		andi	r16,(1<<5)
		breq	dll6
		ldi	r16,'s'
		rcall	lcd_data

dll6:		lds	r16,rxstat	; Check RX status for pos hold mode
		andi	r16,(1<<3)
		breq	dll9
		ldi	r16,'h'
		rcall	lcd_data

dll9:		ldi	r16,$ff		; Reset reception flag
		sts	rxposflag,r16
		ret

;--------------------------------------------------------------

disp_lon:	ldi	XL,'E'		; Positive indicator
		ldi	XH,'W'		; Negative indicator
		lds	r17,rxlon+3	; Load current longitude
		lds	r18,rxlon+2
		lds	r19,rxlon+1
		lds	r20,rxlon

		rjmp	disp_lat_lon

;--------------------------------------------------------------


.dseg
old_info:	.byte	1

.cseg
disp_master:	sbrc	r16,ch_state
		rjmp	dm5		; New state, force display

		lds	r16,old_info
		eor	r16,info
		andi	r16,(1<<master)
		breq	dm9		; No new info, skip display
dm5:		rcall	lcd_status
dm9:		sts	old_info,info
		ret

;--------------------------------------------------------------
menu_dispatch:	cbr	info,(1<<intdisp)	; disable time display update
		ldi	ZL,LOW(mstate_table << 1)
		ldi	ZH,HIGH(mstate_table << 1)
		mov	r17,menu_state
		dec	r17
		lsl	r17		; multiply index by element size
		add	ZL,r17		; Offset into table
		clr	r17
		adc	ZH,r17
		lpm			; Routine address
		mov	r17,r0
		adiw	ZL,1
		lpm
		mov	ZH,r0
		mov	ZL,r17
		icall
;
; When dispatched to, each menu routine will have state info available
; as follows:
;
;   r16:   Bits indicating state changes:
;
;     ch_state:  Initial call to this menu state. ch_select=TRUE, menu_select=0
;     ch_select: Menu selection has been made, menu_select incremented
;     ch_23:     2+3 combo button was pressed
;
;   menu_select: Counts through menu selection, starts on 0 on menu entry.
;                It is the responsibility of each menu handler to wrap this
;                counter's value as needed.
;
; Once initialized (ch_state=TRUE), menu routines can be sure that register
; values will be retained except those needed by dispatch. (i.e. r16,r16,Z)
;

		ret

;--------------------------------------------------------------

m_timezone:	mov	r20,r16
		sbrs	r20,ch_state
		rjmp	mtz1
		mov	r16,tz		; First time through, init state with tz
		andi	r16,tzmask
		mov	menu_select,r16
		rjmp	mtz3		; Force display

mtz1:		mov	r17,tz		; Get current timezone
		andi	r17,tzmask
		sbrs	r20,ch_select; See if user has changed it
		rjmp	mtz5		; No, check write command
		mov	r17,menu_select
		cpi	r17,9		; Else check for overflow
		brlo	mtz2
		ldi	r17,5		; Wrap to EST
		mov	menu_select,r17
mtz2:		mov	r18,tz		; Add in DST flag
		andi	r18,~tzmask
		or	r18,r17
		mov	tz,r18
mtz3:		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd
		ldi	ZL,LOW(timezone_str<<1)
		ldi	ZH,HIGH(timezone_str<<1)
		rcall	lcd_flash
		mov	r16,menu_select
		rcall	tzname		; Get name of timezone
		rcall	lcd_flash

;		mov	r16,menu_select
;		rcall	lcd_dec

mtz5:		sbrs	r20,ch_23
		ret

		mov	r16,menu_select	; Write timezone to EEPROM
		ldi	ZL,LOW(ee_time_zone)
		ldi	ZH,HIGH(ee_time_zone)
		rcall	EEWrite
		rcall	updated
		cbr	r20,(1<<ch_23)	; Keep it from happening again
		rjmp	mtz3		; Put the display back

mtz9:		ret

;--------------------------------------------------------------

m_survey:	mov	r20,r16
		sbrs	r20,ch_select
		rjmp	ms1
		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(survey_query<<1)
		ldi	ZH,HIGH(survey_query<<1)
		rcall	lcd_flash

ms1:		sbrs	r20,ch_23
		ret

		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(survey_start<<1)
		ldi	ZH,HIGH(survey_start<<1)
		rcall	lcd_flash
		ldi	ZL,LOW(survey_init<<1)
		ldi	ZH,HIGH(survey_init<<1)
		ldi	r16,3
		rcall	txcmd
		ret

;--------------------------------------------------------------

m_id:		sbrs	r16,ch_select
		rjmp	mi5		; Go shift display
		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	r16,9		; Wrap select at 9
		cp	menu_select,r16
		brlo	mi1
		clr	menu_select
mi1:		mov	r16,menu_select
		inc	r16
		sts	rxidcount,r16
		ldi	ZL,LOW(receiver_id<<1)
		ldi	ZH,HIGH(receiver_id<<1)
		ldi	r16,2
		rcall	txcmd		; Transmid ID command
mi2:		lds	r16,rxidcount
		cpi	r16,$ff
		brne	mi2		; Wait for response to come in
		lds	r19,eigth_tick	; Set time of display
		clr	r20		;  and clear count
		ldi	XL,LOW(rxidline)
		ldi	XH,HIGH(rxidline)
mi3:		ld	r16,X+		; Pick up a character
		tst	r16
		breq	mi4		; Exit if null
		rcall	lcd_data	;  else display it
		rjmp	mi3

mi4:		ld	r16,-X		; find length without trailing whitespace
		cpi	r16,0
		breq	mi4
		cpi	r16,' '
		breq	mi4
		subi	XL,LOW(rxidline)
		inc	XL

.equ		delay_count=8
.equ		delay=2

		subi	XL,20		; This is shift_count
		mov	XH,XL
		ldi	r16,delay_count
		add	XH,r16		; shift_count + delay_count
mi5:		tst	XL
		brmi	mi9		; Negative shift, don't do any
		lds	r16,eigth_tick	; How long since last display change?
		sub	r16,r19
		cpi	r16,delay
		brlo	mi9
mi6:		ldi	r16,$18		; Assume shift to left
		mov	r17,r20		; Get count
		cp	r17,XH
		brlo	mi7
		ldi	r16,$1c		; In second half, shift right
		sub	r17,XH		;  and subtract half
mi7:		cpi	r17,delay_count
		brlo	mi8		; Pause at beginnings
		rcall	lcd_cmd		;  else shift
mi8:		lds	r19,eigth_tick	; reset timer
		inc	r20		; bump counter
		mov	r16,XH
		lsl	r16
		cp	r20,r16		; wrap
		brne	mi9
		clr	r20
mi9:		ret

;--------------------------------------------------------------

m_test:		mov	r20,r16
		sbrs	r20,ch_select
		rjmp	mt1
		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(test_query_str<<1)
		ldi	ZH,HIGH(test_query_str<<1)
		rcall	lcd_flash

mt1:		sbrs	r20,ch_23
		ret

		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(test1_str<<1)
		ldi	ZH,HIGH(test1_str<<1)
		rcall	lcd_flash
		ldi	r16,$ff
		sts	rxdiag,r16
		sts	rxdiag+1,r16
		ldi	ZL,LOW(self_test<<1)
		ldi	ZH,HIGH(self_test<<1)
		ldi	r16,2
		rcall	txcmd		; Transmid ID command
		lds	r19,eigth_tick	; start time
mt2:		lds	r16,eigth_tick
		sub	r16,r19
		cpi	r16,8
		brlo	mt3
		lds	r19,eigth_tick
		ldi	r16,'.'		; Print '.' every second
		rcall	lcd_data
mt3:		lds	r16,rxdiag+1
		cpi	r16,$ff
		breq	mt2		; loop until results come back

		ldi	r16,$01		; show the results
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(test2_str<<1)
		ldi	ZH,HIGH(test2_str<<1)
		rcall	lcd_flash
		lds	r16,rxdiag
		rcall	lcd_hex
		lds	r16,rxdiag+1
		rcall	lcd_hex
		ret

;--------------------------------------------------------------

m_errors:	mov	r20,r16
		sbrs	r20,ch_state
		rjmp	me1
me0:		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	ZL,LOW(err_str<<1)
		ldi	ZH,HIGH(err_str<<1)
		rcall	lcd_flash
		ldi	ZL,LOW(ee_err_cnt)
		ldi	ZH,HIGH(ee_err_cnt)
		rcall	EERead
		rcall	lcd_dec
		ret

me1:		sbrs	r20,ch_select
		rjmp	me2
		mov	r16,menu_select	; Use select as index into error log
		dec	r16
		lsl	r16		; Multiply by 10
		mov	ZL,r16
		lsl	r16
		lsl	r16
		add	ZL,r16
		ldi	ZH,1
		ldi	r16,$01
		rcall	lcd_cmd
		ldi	r16,$02
		rcall	lcd_cmd
		ldi	r17,10		; Display 10 bytes
me1a:		rcall	EERead
		rcall	lcd_decz
		inc	ZL
		dec	r17
		brne	me1a
		ret
		
		
me2:		sbrs	r20,ch_23
		ret
		
		clr	r16		; clear error count
		ldi	ZL,LOW(ee_err_cnt)
		ldi	ZH,HIGH(ee_err_cnt)
		rcall	EEWrite
		rcall	updated
		cbr	r20,(1<<ch_23)
		rjmp	me0
		
;--------------------------------------------------------------

updated:	push	r16
		push	r17
		ldi	r16,$01	; clear display
		rcall	lcd_cmd
		ldi	r16,$02	; cursor home
		rcall	lcd_cmd
		ldi	ZL,LOW(written_str << 1)
		ldi	ZH,HIGH(written_str << 1)
		rcall	lcd_flash
		lds	r16,eigth_tick
up1:		lds	r17,eigth_tick
		sub	r17,r16
		cpi	r17,2*8
		brne	up1
		pop	r17
		pop	r16
		ret

;--------------------------------------------------------------
m_capture:	cli			; Disable interrupts
		lds	r20,capt_avg	; Pick up average capture value
		lds	r21,capt_avg+1	;  (atomic operation)
		sei
		sbrc	r16,ch_state
		rjmp	mcap0		; New state, force display
		cp	r20,r26
		cpc	r21,r27
		breq	mcap9		; No difference, exit
mcap0:		mov	r26,r20
		mov	r27,r21
		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd
		ldi	ZL,LOW(freq_str << 1)
		ldi	ZH,HIGH(freq_str << 1)
		rcall	lcd_flash
		clr	r20		; 32-bit
		clr	r19
		mov	r18,r27
		mov	r17,r26
		ldi	r29,6
mcap1:		clc			; multiply capture by 64
		rol	r17
		rol	r18
		rol	r19
		rol	r20
		dec	r29
		brne	mcap1
		
		ldi	r29,7		; digit count
mcap2:		ldi	r24,$00		; divide by 10
		ldi	r23,$00
		ldi	r22,$00
		ldi	r21,$0a
		rcall	div32u
		push	r13		; save a digit
		dec	r29
		brne	mcap2
		
		ldi	r29,7		; digit count
mcap3:		pop	r16
		ori	r16,'0'
		rcall	lcd_data
		dec	r29
		brne	mcap3
		ldi	r16,'H'
		rcall	lcd_data
		ldi	r16,'z'
		rcall	lcd_data		
mcap9:		ret

;--------------------------------------------------------------

m_track:	tst	r16
		brne	mtr0a
		ret
		
mtr0a:		lds	r16,rxsats	; Wrap menu_select at sat count
		cp	r16,menu_select
		brsh	mtr0
		clr	menu_select

mtr0:		lds	r16,rxposflag
		cpi	r16,1
		brne	mtr0		; Loop until serial processing done

		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd
		
		tst	menu_select
		brne	mtr3
		
; Select 0: Display count and DOP			

		ldi	ZL,LOW(track_str << 1)
		ldi	ZH,HIGH(track_str << 1)
		rcall	lcd_flash
		lds	r16,rxsats
		rcall	lcd_dec
		ldi	r16,$80+13	; Position to DOP
		rcall	lcd_cmd
		ldi	ZL,LOW(dop_str << 1)
		ldi	ZH,HIGH(dop_str << 1)
		rcall	lcd_flash
		ldi	r29,3		; Convert 3 digits
		lds	r16,rxdop+1
		lds	r17,rxdop
mtr1:		ldi	r18,LOW(10)	; Divide by 10
		ldi	r19,HIGH(10)
		rcall	div16u
		push	r14		; Push remainder
		dec	r29
		brne	mtr1		; Next digit

		ldi	r29,3		; Pop digits in reverse order
mtr2:		pop	r16		; Get most significant digit
		ori	r16,'0'		; Convert to ASCII and display
		rcall	lcd_data
		dec	r29		; Repeat for as many digits as we pushed
		brne	mtr2
		ret

; Select N: Display receive channel status for sat N

mtr3:		mov	r16,menu_select	; Get rx channel
		dec	r16		; zero based
		lsl	r16		; multiply by 4
		lsl	r16
		ldi	ZL,LOW(rxchnls)
		ldi	ZH,HIGH(rxchnls)
		add	ZL,r16		; offset by channel
		clr	r16
		adc	ZH,r16
		mov	r16,menu_select	; channel number
		rcall	lcd_dec
		ldi	r16,':'
		rcall	lcd_data
		ldi	r16,' '
		rcall	lcd_data
		ldi	r16,'S'		; Satellite number
		rcall	lcd_data
		ldi	r16,'a'
		rcall	lcd_data
		ldi	r16,'t'
		rcall	lcd_data
		ld	r16,Z+
		rcall	lcd_dec
		ldi	r16,' '
		rcall	lcd_data
		ldi	r16,'m'		; channel tracking mode
		rcall	lcd_data
		ld	r16,Z+
		rcall	lcd_dec
		ldi	r16,' '
		rcall	lcd_data
		ldi	r16,'s'		; signal strength
		rcall	lcd_data
		ld	r16,Z+
		rcall	lcd_dec
		ldi	r16,' '
		rcall	lcd_data
		ldi	r16,'d'
		rcall	lcd_data
		ld	r16,Z+
		rcall	lcd_hex
		ret

;--------------------------------------------------------------
;  Transmit command in FLASH pointed to by Z, length R16
;
txcmd:		push	r17
		push	r16
		ldi	r16,'@'		; Transmit preamble "@@"
		rcall	txchar
		rcall	txchar
		clr	r17		; Clear checksum
		pop	r16
		rcall	txstr		; Transmit command
		mov	r16,r17		; Transmit checksum
		rcall	txchar
		ldi	r16,$0D		; Transmit EOM <CR><LF>
		rcall	txchar
		ldi	r16,$0A
		rcall	txchar
		pop	r17
		ret

;
;  Transmit string in FLASH at Z, length in R16, checksum in R17
;
txstr:		push	r0
txs1:		lpm			; pick up a character
txs2:		sbis	USR,UDRE	; wait for buffer empty
		rjmp	txs2
		out	UDR,r0		; transmit char
		eor	r17,r0
		adiw	ZL,1		; bump Z
		dec	r16		; decrement count
		brne	txs1		; loop until count exhausted
		pop	r0
		ret

;
;  Transmit char in R16
;
txchar:		sbis	USR,UDRE	; wait for buffer available
		rjmp	txchar
		out	UDR,r16
		ret


;---------------------------------------------------------------------
; Display/update current time. Uses Z, r0
; r16 describes number of fields to update:
;
;   0: Clear and display all
;   1: Update seconds
;   2: Update minutes and seconds
;   3: Update hours, ...
;   4: Update days, ...
;   5: Update month, ...
;   6: Update year, ....


timedisp:	push	r0
		dec	r16
		brmi	td1
		ldi	ZL,LOW(disp_table << 1)
		ldi	ZH,HIGH(disp_table << 1)
		lsl	r16		; multiply index by element size
		lsl	r16
		add	ZL,r16		; Offset into table
		clr	r16
		adc	ZH,r16
		lpm			; Cursor position
		mov	r16,r0
		ori	r16,$80		; Write cursor address command
		rcall	lcd_cmd
		adiw	ZL,2
		lpm			; Routine address
		mov	r16,r0
		adiw	ZL,1
		lpm
		mov	ZH,r0
		mov	ZL,r16
		ijmp

td1:		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$80+17	; position to gmt/am/pm
		rcall	lcd_cmd
		clr	r16		; get gmt
		rcall	tzname
		rcall	lcd_flash	; display it
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd

year_disp:
month_disp:	mov	r16,mon
		rcall	monthname
		rcall	lcd_flash	; Display month name
		ldi	r16,' '
		rcall	lcd_data

day_disp:	mov	r16,day		; Get day
		rcall	lcd_decl
		ldi	r16,' '
		rcall	lcd_data
		brtc	hour_disp
		ldi	r16,' '		; Blank fill for single digit
		rcall	lcd_data

hour_disp:	mov	r16,hh		; Get hour
		rcall	lcd_decz
		ldi	r16,':'
		rcall	lcd_data

min_disp:	mov	r16,mm		; Get minute
		rcall	lcd_decz
		ldi	r16,':'
		rcall	lcd_data

sec_disp:	mov	r16,ss		; Get seconds
		rcall	lcd_decz
		
		pop	r0
		ret

;---------------------------------------------------------------------
; Same as timedisp, but for local time

ltimedisp:	push	r0
		dec	r16
		brmi	ltd1
		cpi	r16,2
		brlo	ltd0		; Skip if only updating ss or mm
		push	r17		; else make space for local hour,
		push	r18		;  day,
		push	r19		;  and month.
		rcall	localtIme	; Get local Values
ltd0:		ldi	ZL,LOW(ldisp_table << 1)
		ldi	ZH,HIGH(ldisp_table << 1)
		lsl	r16		; multiply index by
		lsl	r16
		add	ZL,r16		; Offset into table
		clr	r16
		adc	ZH,r16
		lpm			; Cursor position
		mov	r16,r0
		ori	r16,$80		; Write cursor address command
		rcall	lcd_cmd
		adiw	ZL,2
		lpm			; Routine address
		mov	r16,r0
		adiw	ZL,1
		lpm
		mov	ZH,r0
		mov	ZL,r16
		ijmp

ltd1:		ldi	r16,$01		; clear display
		rcall	lcd_cmd
		ldi	r16,$02		; cursor home
		rcall	lcd_cmd
		push	r17		; Make space for local hour,
		push	r18		;  day,
		push	r19		;  and month.
		rcall	localtime	; Get local values

lyear_disp:
lmonth_disp:	mov	r16,r19
		rcall	monthname
		rcall	lcd_flash	; Display month name
		ldi	r16,' '
		rcall	lcd_data

lday_disp:	mov	r16,r18		; Get day
		rcall	lcd_decl
		ldi	r16,' '
		rcall	lcd_data
		brtc	lhour_disp
		ldi	r16,' '		; Blank fill for single digit
		rcall	lcd_data

lhour_disp:	ldi	r16,$80+17	; AM/PM cursor position
		rcall	lcd_cmd
		cpi	r17,12
		brlo	lhd1
		subi	r17,12		; hh > 12, adjust and display "PM"
		ldi	r16,'P'
		rjmp	lhd2

lhd1:		ldi	r16,'A'		; hh <= 12, display "AM"
lhd2:		rcall	lcd_data
		ldi	r16,'M'
		rcall	lcd_data
		ldi	r16,$80+7	; cursor position for hour
		rcall	lcd_cmd
		mov	r16,r17		; Get hour
		tst	r16
		brne	lhd3
		ldi	r16,12		; 00 -> 12
lhd3:		rcall	lcd_decr
		ldi	r16,':'
		rcall	lcd_data

		pop	r19
		pop	r18
		pop	r17
		rjmp	min_disp

;------------------------------------------------------------------
; Point Z to name of month (r16) (1..12).  Destroys r16.

monthname:	ldi	ZL,LOW(months << 1)	; Point Z to start of months
		ldi	ZH,HIGH(months << 1)
		dec	r16		; Zero-based
		lsl	r16		; Multiply month by 4 (size of month string)
		lsl	r16
		add	ZL,r16		; Add to Z index
		clr	r16
		adc	ZH,r16
		ret

;--------------------------------------------------------------------
; Point Z to name of standard timezone (r16).  Destroys r16.
;
; Currently only works for GMT and lower 48 states.

tzname:		tst	r16
		breq	tz9		; GMT
		ldi	ZL,LOW(timezones << 1)	; Point Z to start of timezones
		ldi	ZH,HIGH(timezones << 1)
		subi	r16,5		; Offset for EST
		push	r17		; Multiply tz by 10
		lsl	r16
		mov	r17,r16
		lsl	r16
		lsl	r16
		add	r16,r17
		pop	r17
		add	ZL,r16		; Add to Z index
		clr	r16
		adc	ZH,r16
		ret

tz9:		ldi	ZL,LOW(utc << 1)
		ldi	ZH,HIGH(utc << 1)
		ret
		
;--------------------------------------------------------------
; Increment time in registers by 1 second.
; Returns number of registers incremented in r16.
;

incsec:		ldi	r16,60
		inc	ss		; Increment seconds
		cp	ss,r16
		brcc	is2
		ldi	r16,1
		ret			; return if we haven't reached 60

is2:		clr	ss              ; else reset seconds to 00
incmin:		inc	mm		; Increment minutes
		cp	mm,r16
		brcc	im2
		ldi	r16,2
		ret			; return if we haven't reached 60

im2:		clr	mm            	; else reset minutes to 00
inchour:	ldi	r16,24
		inc	hh		; Increment hours
		cp	hh,r16
		brcc	ih2
		ldi	r16,3
		ret			; return if we haven't reached 24

ih2:		clr	hh		; else reset hours to 00
incday:		inc	day		; Increment days
		mov	r16,mon		; Get month
		rcall	monthlen	; Get # of days in month
		inc	r16
		cp	day,r16
		breq	id2
		ldi	r16,4
		ret                     ; return if we haven't reached end of month

id2:		clr	day
		inc	day		; else reset to 1st of month
incmonth:	inc	mon		; Increment months
		ldi	r16,13
		cp	mon,r16
		brcc	imo2
		ldi	r16,5
		ret			; return if we don't pass new years

imo2:		clr	mon		; else reset to January
		inc	mon
incyear:	inc	year            ; increment year
		ldi	r16,6
		ret

;--------------------------------------------------------------
; Return number of days in month (r16) (1..12) in r16.

monthlen:	push	r0
		push	ZL		; Save Z
		push	ZH
		ldi	ZL,LOW(monthsize << 1)	; Point Z to sizes of months
		ldi	ZH,HIGH(monthsize << 1)
		dec	r16		; Zero-based
		add	ZL,r16		; Add to Z		
		clr	r16
		adc	ZH,r16
		lpm			; Get size of month in days from table
		mov	r16,r0
		pop	ZH		; Restore Z
		pop	ZL
		cpi	r16,28
		brne	ml9		; Not February, continue with overflow check
		rcall	leapyear	; Check February for leap year
		brne	ml9		; Not a leap year
		inc	r16		; Is a leap year, add a day to Feb.
ml9:		pop	r0
		ret

;--------------------------------------------------------------
; Sets Z flag if this is a leap year.
;

leapyear:	push	r16
		mov	r16,year
		andi	r16,3		; Check year for multiple of 4
		pop	r16		; (pop does not disturb status reg.)
		ret

;--------------------------------------------------------------
; Converts current GMT time to local time zone.  Only works
; for negative offsets.  Returns:
;
;   r17: local hour
;   r18: local day
;   r19: local month

localtime:	push	r16
		mov	r16,tz		; Get timezone offset
		sbrc	r16,dst
		dec	r16		; Add in daylight savings time
		andi	r16,tzmask
		mov	r17,hh		; Get GMT time
		mov	r18,day
		mov	r19,mon
		sub	r17,r16		; Adjust hour
		brcs	lt1
		pop	r16
		ret			; No wrap

lt1:		subi	r17,(-24)	; Wrap hours to previous day
		dec	r18		; Decrement day
		breq	lt2		; Wrap to previous month
		pop	r16
		ret

lt2:		dec	r19		; Decrement month
		brne	lt3
		ldi	r19,12		; Wrap month to December
lt3:		mov	r16,r19		; Get # of days in last month
		rcall	monthlen
		mov	r18,r16		; Wrap day to last of month
		pop	r16
		ret


;--------------------------------------------------------------
;  Calculate current day of week and return in r16.
;
;  Sunday = 0, Monday = 1, Tuesday = 2, ..., Saturday = 6


day_of_week:	push	ZL
		push	ZH
		push	r0
		ldi	ZL,LOW(dow_table << 1)
		ldi	ZH,HIGH(dow_table << 1)
		mov	r16,mon		; Index into table by month
		dec	r16
		add	ZL,r16
		clr	r16
		adc	ZH,r16
		lpm			; Get magic number
		mov	r16,year	; Get 1.25 * year
		lsr	r16
		lsr	r16
		add	r16,year
		add	r16,day		; Add day of month
		add	r16,r0		; Add magic number
		rcall	leapyear
		brne	dow1
		mov	r0,r16
		mov	r16,mon
		cpi	r16,3
		mov	r16,r0
		brsh	dow1
		dec	r16		; Add 1 for leapyear Jan or Feb
dow1:		cpi	r16,7		; Modulo 7
		brlo	dow9
		subi	r16,7
		rjmp	dow1

dow9:		pop	r0
		pop	ZH
		pop	ZL
		ret

;--------------------------------------------------------------
;  Display master/slave mode
;
lcd_status:	push	r16
		ldi	r16,$01	; clear display
		rcall	lcd_cmd

		ldi	r16,$02	; cursor home
		rcall	lcd_cmd

		sbrs	info,master
		rjmp	ls1
		ldi	ZL,LOW(master_str << 1)
		ldi	ZH,HIGH(master_str << 1)
		rjmp	ls2

ls1:		ldi	ZL,LOW(slave_str << 1)
		ldi	ZH,HIGH(slave_str << 1)
ls2:		rcall	lcd_flash

		ldi	ZL,LOW(mode_str << 1)
		ldi	ZH,HIGH(mode_str << 1)
		rcall	lcd_flash

		pop	r16
		ret


;----------------------------------------------------------------
;  Display r16 in binary (0-255), zero-suppressed, left-justified.
;
lcd_dec:	push	r15
		push	r17
		push	r18
		push	r29

		clr	r29		; Clear digit counter
		ldi	r17,10		; Divide r16 by 10
ld1:		rcall	div8u
		push	r15		; Push remainder
		inc	r29
		tst	r16
		brne	ld1		; Repeat until result is zero

ld2:		pop	r16		; Get most significant digit
		ori	r16,'0'		; Convert to ASCII and display
		rcall	lcd_data
		dec	r29		; Repeat for as many digits as we pushed
		brne	ld2

		pop	r29
		pop	r18
		pop	r17
		pop	r15
		ret

;--------------------------------------------------------------
;  Display r16 in binary (00-99)
;
lcd_decz:	push	r15
		push	r17
		push	r18
		ldi	r17,10		; Divide r16 by 10
		rcall	div8u
		ori	r16,'0'		; Display 1st digit
		rcall	lcd_data
		mov	r16,r15		; Display 2nd digit
		ori	r16,'0'
		rcall	lcd_data
		pop	r18
		pop	r17
		pop	r15
		ret

;----------------------------------------------------------------
;  Display r16 in binary (0-99), zero-suppressed, left-justified.
;
;  T flag is set if leading zero was suppressed.
;
lcd_decl:	push	r15
		push	r17
		push	r18
		set
		ldi	r17,10		; Divide r16 by 10
		rcall	div8u
		tst	r16
		breq	ldl1
		ori	r16,'0'		; Display 1st digit
		clt
		rcall	lcd_data
ldl1:		mov	r16,r15		; Display 2nd digit
		ori	r16,'0'
		rcall	lcd_data
		pop	r18
		pop	r17
		pop	r15
		ret

;----------------------------------------------------------------
;  Display r16 in binary (0-99), zero-suppressed, right-justified.
;
;  Leading zero is replaced with space.
;
lcd_decr:	push	r15
		push	r17
		push	r18
		ldi	r17,10		; Divide r16 by 10
		rcall	div8u
		tst	r16
		brne	ldr0
		ldi	r16,' '		; Replace 0 with space
		rjmp	ldr1
		
ldr0:		ori	r16,'0'		; Display 1st digit
ldr1:		rcall	lcd_data
		mov	r16,r15		; Display 2nd digit
		ori	r16,'0'
		rcall	lcd_data
		pop	r18
		pop	r17
		pop	r15
		ret

;--------------------------------------------------------------
;  Display r16 in hex
;
lcd_hex:	push	r16

		swap	r16		; get high nibble
		andi	r16,$0f
		rcall	hex_digit
		rcall	lcd_data
		pop	r16
		andi	r16,$0f
		rcall	hex_digit
		rcall	lcd_data
		ret

;--------------------------------------------------------------
;  Convert nibble in r16 to hex ASCII in r16
;
hex_digit:	push	r17
		ldi	r17,'0'	; 0-9 -> ASCII
		add	r16,r17
		cpi	r16,'9'+1	; r16 - '9'
		brmi	hd1
		ldi	r17,'A'-'0'-10	; adjust for > 9
		add	r16,r17
hd1:		pop	r17
		ret

;--------------------------------------------------------------
;  Display r16:r17 in decimal, left-justified, zero-suppressed
;

lcd_dec16:	push	r16
		push	r17
		push	r18
		push	r19
		push	r29
		clr	r29		; Digit count
		ldi	r18,LOW(10)	; Divide by 10
		ldi	r19,HIGH(10)
ld16_1:		rcall	div16u
		push	r14		; Push remainder
		inc	r29
		mov	r20,r16
		or	r20,r17
		brne	ld16_1		; Loop until result is 0
ld16_2:		pop	r16		; Pop digits and display in reverse order
		ori	r16,'0'
		rcall	lcd_data
		dec	r29
		brne	ld16_2
		pop	r29
		pop	r19
		pop	r18
		pop	r17
		pop	r16
		ret
		
;--------------------------------------------------------------
; Send r16 to LCD as command.  Destructive of r16.
;
lcd_cmd:	push	r16
		rcall	busywait
		andi	r16,$f0
		out	PORTA,r16
		rcall	strobe
		pop	r16
		swap	r16
		andi	r16,$f0
		out	PORTA,r16
		rcall	strobe
		ret

;--------------------------------------------------------------
; Send r16 to LCD as data.  Destructive of r16.
;
lcd_data:	push	r16
		rcall	busywait
		sbi	PORTC,5		; RS to data
		andi	r16,$f0
		out	PORTA,r16
		rcall	strobe
		pop	r16
		swap	r16
		andi	r16,$f0
		out	PORTA,r16
		rcall	strobe
		ret

;--------------------------------------------------------------
;  Display string pointed to by Z register in FLASH memory
;

lcd_flash:	lpm			; pick up a character
		adiw	ZL,1		; bump Z
		tst	r0
		breq	lf2		; exit if NULL
		mov	r16,r0
		rcall	lcd_data	; display it
		rjmp	lcd_flash	; get next

lf2:		ret

;***************************************************************************
; Jump extentions

ee_rdy_:	rjmp	ee_rdy
busywait_:	rjmp	busywait
strobe_:	rjmp	strobe
adc_conv_:	rjmp	adc_conv

;***************************************************************************

;***************************************************************************
;*
;* "div8u" - 8/8 Bit Unsigned Division
;*
;* This subroutine divides the two register variables "dd8u" (dividend) and 
;* "dv8u" (divisor). The result is placed in "dres8u" and the remainder in
;* "drem8u".
;*  
;* Number of words	:14
;* Number of cycles	:97
;* Low registers used	:1 (drem8u)
;* High registers used  :3 (dres8u/dd8u,dv8u,dcnt8u)
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	drem8u	=r15		;remainder
.def	dres8u	=r16		;result
.def	dd8u	=r16		;dividend
.def	dv8u	=r17		;divisor
.def	dcnt8u	=r18		;loop counter

;***** Code

div8u:	sub	drem8u,drem8u	;clear remainder and carry
	ldi	dcnt8u,9	;init loop counter
d8u_1:	rol	dd8u		;shift left dividend
	dec	dcnt8u		;decrement counter
	brne	d8u_2		;if done
	ret			;    return
d8u_2:	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_3		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_1		;else
d8u_3:	sec			;    set carry to be shifted into result
	rjmp	d8u_1


;***************************************************************************
;*
;* "div16u" - 16/16 Bit Unsigned Division
;*
;* This subroutine divides the two 16-bit numbers 
;* "dd8uH:dd8uL" (dividend) and "dv16uH:dv16uL" (divisor). 
;* The result is placed in "dres16uH:dres16uL" and the remainder in
;* "drem16uH:drem16uL".
;*  
;* Number of words	:19
;* Number of cycles	:235/251 (Min/Max)
;* Low registers used	:2 (drem16uL,drem16uH)
;* High registers used  :5 (dres16uL/dd16uL,dres16uH/dd16uH,dv16uL,dv16uH,
;*			    dcnt16u)
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	drem16uL=r14
.def	drem16uH=r15
.def	dres16uL=r16
.def	dres16uH=r17
.def	dd16uL	=r16
.def	dd16uH	=r17
.def	dv16uL	=r18
.def	dv16uH	=r19
.def	dcnt16u	=r20

;***** Code

div16u:	clr	drem16uL	;clear remainder Low byte
	sub	drem16uH,drem16uH;clear remainder High byte and carry
	ldi	dcnt16u,17	;init loop counter
d16u_1:	rol	dd16uL		;shift left dividend
	rol	dd16uH
	dec	dcnt16u		;decrement counter
	brne	d16u_2		;if done
	ret			;    return
d16u_2:	rol	drem16uL	;shift dividend into remainder
	rol	drem16uH
	sub	drem16uL,dv16uL	;remainder = remainder - divisor
	sbc	drem16uH,dv16uH	;
	brcc	d16u_3		;if result negative
	add	drem16uL,dv16uL	;    restore remainder
	adc	drem16uH,dv16uH
	clc			;    clear carry to be shifted into result
	rjmp	d16u_1		;else
d16u_3:	sec			;    set carry to be shifted into result
	rjmp	d16u_1
	
	

;***************************************************************************
;*
;* "div32u" - 32/32 Bit Unsigned Division
;*
;* This subroutine divides the two 32-bit numbers 
;* "dd32d:dd32c:dd32b:dd32a" (dividend) and
;* "dv32d:dv32c:dv32b:dv32a" (divisor). 
;* The result is placed in "dres32d..dres32a" and the remainder in
;* "drem32d..drem32a".
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	drem32a=r13	; remainder
.def	drem32b=r14
.def	drem32c=r15
.def	drem32d=r16

.def	dres32a=r17	; result
.def	dres32b=r18
.def	dres32c=r19
.def	dres32d=r20

.def	dd32a	=r17	; dividend
.def	dd32b	=r18
.def	dd32c	=r19
.def	dd32d	=r20

.def	dv32a	=r21	; divisor
.def	dv32b	=r22
.def	dv32c	=r23
.def	dv32d	=r24

.def	dcnt32u	=r25

div32u:	clr	drem32a		;clear remainder Low byte
	clr	drem32b
	clr	drem32c
	sub	drem32d,drem32d	;clear remainder High byte and carry
	ldi	dcnt32u,33	;init loop counter
d32u_1:	rol	dd32a		;shift left dividend
	rol	dd32b
	rol	dd32c
	rol	dd32d
	dec	dcnt32u		;decrement counter
	brne	d32u_2		;if done
	ret			;    return
				;else
d32u_2:	rol	drem32a		;    shift dividend into remainder
	rol	drem32b
	rol	drem32c
	rol	drem32d
	sub	drem32a,dv32a	;    remainder = remainder - divisor
	sbc	drem32b,dv32b	;
	sbc	drem32c,dv32c	;
	sbc	drem32d,dv32d	;
	brcc	d32u_3		;if result negative
	add	drem32a,dv32a	;    restore remainder
	adc	drem32b,dv32b
	adc	drem32c,dv32c
	adc	drem32d,dv32d
	clc			;    clear carry to be shifted into result
	rjmp	d32u_1
				;else
d32u_3:	sec			;    set carry to be shifted into result
	rjmp	d32u_1

;***************************************************************************
;*
;* "mpy16u" - 16x16 Bit Unsigned Multiplication
;*
;* This subroutine multiplies the two 16-bit register variables 
;* mp16uH:mp16uL and mc16uH:mc16uL.
;* The result is placed in m16u3:m16u2:m16u1:m16u0.
;*  
;* Number of words	:14 + return
;* Number of cycles	:153 + return
;* Low registers used	:None
;* High registers used  :7 (mp16uL,mp16uH,mc16uL/m16u0,mc16uH/m16u1,m16u2,
;*                          m16u3,mcnt16u)	
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	mc16uL	=r16		;multiplicand low byte
.def	mc16uH	=r17		;multiplicand high byte
.def	mp16uL	=r18		;multiplier low byte
.def	mp16uH	=r19		;multiplier high byte
.def	m16u0	=r18		;result byte 0 (LSB)
.def	m16u1	=r19		;result byte 1
.def	m16u2	=r20		;result byte 2
.def	m16u3	=r21		;result byte 3 (MSB)
.def	mcnt16u	=r22		;loop counter

;***** Code

mpy16u:	clr	m16u3		;clear 2 highest bytes of result
	clr	m16u2
	ldi	mcnt16u,16	;init loop counter
	lsr	mp16uH
	ror	mp16uL

m16u_1:	brcc	noad8		;if bit 0 of multiplier set
	add	m16u2,mc16uL	;add multiplicand Low to byte 2 of res
	adc	m16u3,mc16uH	;add multiplicand high to byte 3 of res
noad8:	ror	m16u3		;shift right result byte 3
	ror	m16u2		;rotate right result byte 2
	ror	m16u1		;rotate result byte 1 and multiplier High
	ror	m16u0		;rotate result byte 0 and multiplier Low
	dec	mcnt16u		;decrement loop counter
	brne	m16u_1		;if not done, loop more
	ret




;***************************************************************************
;* 
;* EERead
;*
;* This subroutine waits until the EEPROM is ready to be programmed, then
;* reads the register variable "EEdrd" from address "EEardh:EEard"
;*
;* Number of words	:1200 ; 5 + return
;*			:8515 ; 6 + return
;* Number of cycles	:1200 ; 8 + return (if EEPROM is ready)
;*			:8515 ; 9 + return (if EEPROM is ready)
;* Low Registers used	:1 (EEdrd)
;* High Registers used:	:2 (EEard,EEardh)
;*
;***************************************************************************

;***** Subroutine register variables

.def	EEdrd	=r16		;result data byte
.def	EEtmp	=r17
.def	EEtmp2	=r18
.def	EEard	=ZL		;address low to read from
.def	EEardh	=ZH		;address high to read from

;***** Code

EERead:	push	EEtmp
	push	EEtmp2
	in	EEtmp2,SREG
eer0:	cli
	lds	EEtmp,ee_count
	cpi	EEtmp,$ff
	breq	eer1
	sbrc	EEtmp2,7
	sei
	rjmp	eer0

eer1:	sbic	EECR,EEWE	;if EEWE not clear
	rjmp	eer1		;    wait more
	out 	EEARH,EEardh	;output address high for 8515
	out	EEARL,EEard	;output address low for 8515
	sbi	EECR,EERE	;set EEPROM Read strobe
				;This instruction takes 4 clock cycles since
				;it halts the CPU for two clock cycles
	in	EEdrd,EEDR	;get data
	out	SREG,EEtmp2
	pop	EEtmp2
	pop	EEtmp
	ret


;***************************************************************************
;* 
;* EEWrite
;*
;* This subroutine waits until the EEPROM is ready to be programmed, then
;* programs the EEPROM with register variable "EEdwr" at address "EEawr:EEawr"
;*
;* Number of words	:1200 ; 5 + return
;*			:8515 ; 7 + return
;* Number of cycles	:1200 ; 8 + return (if EEPROM is ready)
;*			:8515 ; 11 + return (if EEPROM is ready)
;* Low Registers used	:None
;* High Registers used:	;3 (EEdwr,EEawr,EEawrh)
;*
;***************************************************************************

;***** Subroutine register variables

.def	EEdwr	=r16		;data byte to write to EEPROM
.def	EEtmp	=r17
.def	EEtmp2	=r18
.def	EEawr	=ZL		;address low byte to write to
.def	EEawrh	=ZH		;address high byte to write to

EEWrite:push	EEtmp
	push	EEtmp2
	in	EEtmp2,SREG	; Save interrupt status
eew0:	cli			; Disable interrupts
	lds	EEtmp,ee_count	; Check for error logging activity
	cpi	EEtmp,$ff
	breq	eew1
	sbrc	EEtmp2,7	; Skip if interrupts were turned off
	sei			; Re-enable interrupts
	rjmp	eew0		; loop until clear
	
eew1:	sbic	EECR,EEWE	;if EEWE not clear
	rjmp	eew1		;    wait more

	out 	EEARH,EEawrh	;output address high for 8515
	out	EEARL,EEawr	;output address low for 8515
	out	EEDR,EEdwr	;output data
	sbi 	EECR,EEMWE	;set master write enable
	sbi	EECR,EEWE	;set EEPROM Write strobe
				;This instruction takes 4 clock cycles since
				;it halts the CPU for two clock cycles
	out	SREG,EEtmp2	;restore interrupt status
	pop	EEtmp2
	pop	EEtmp
	ret

;--------------------------------------------------------------
; UART receive interrupt

		.DSEG
rxvec:		.BYTE	2			; Address of handler for next RX byte
rxcnt:		.BYTE	1			; Count of data bytes received
rxsum:		.BYTE	1			; Response checksum (XOR)
rxcmd:		.BYTE	2			; 2-character command

rxposflag:	.BYTE	1			; 0 = processing pos, 1 = done
rxmonth:	.BYTE	1			; month 1-12
rxday:		.BYTE	1			; day 1-31
rxyear:		.BYTE	2			; year 1998-2018
rxhour:		.BYTE	1			; hours 0-23
rxminute:	.BYTE	1			; minute 0-59
rxsecond:	.BYTE	1			; seconds 0-59 (60 for leap second)
rxfracsec:	.BYTE	4			; fraction (0-999,999,999)
rxlat:		.BYTE	4			; latitude in milliarcseconds
rxlon:		.BYTE	4			; longitude in milliarcseconds
rxdop:		.BYTE	2
rxdoptyp:	.BYTE	1			; bit 7: ant. undercurrent, bit 6: ant. overcurrent, bit 5: auto survey mode
rxsats:		.BYTE	1			; number of satellites tracked
rxchnls:	.BYTE	32			; receiver channel status (8x4)
rxstat:		.BYTE	1			; receiver status

sync_err_cnt:	.BYTE	1			; count-down of time sync errors

rxchnls_ptr:	.BYTE	2

;
; 0: no leap second this month
; 1: +1 second
; 2: -1 second
;
rxleap:		.BYTE	1

;
; 0: disabled
; 1: enabled
; 2: auto survey
;
rxposhold:	.BYTE	1

rxdiag:		.BYTE	2

rxidline:	.BYTE	40		; Single line from ID response
rxidptr:	.BYTE	2		; Pointer into rxidline
rxidcount:	.BYTE	1		; Count for which line to receive
rxidchcnt:	.BYTE	1		; Count for # of chars in line

		.CSEG

uart_rxc:	push	r16
		in	r16,SREG
		push	r16
		push	r17
		push	ZH
		push	ZL

		in	r16,UDR			; Received character
		sbic	USR,FE
		rjmp	urx_fe			; Framing error, discard
		sbic	USR,OVR
		rjmp	urx_ovr
		lds	r17,rxsum		; Add byte to checksum
		eor	r17,r16
		sts	rxsum,r17
		lds	r17,rxcnt		; Get byte count & increment
		inc	r17			; increment byte count
		sts	rxcnt,r17
		lds	ZL,rxvec		; Get address of next handler
		lds	ZH,rxvec+1
		ijmp

;--------------------------------------------------------------------
;  External interrupt 1 - master serial activity sense
;
;  This interrupt is called whenever there is a logical change in
;  the line monitoring external serial activity.  It switches the
;  hardware to connect the Oncore to the external machine, sets the
;  internal flag for slave mode, and resets a count-down timer.
;  When the counter reaches zero, the AVR is put back in master mode.

ext_int1:	cbi	PORTD,4
		push	r16
		in	r16,SREG
		push	r16
		ldi	r16,90		; 90 seconds for timeout
		sts	master_time,r16
		cbr	info,(1<<master); Switch to slave mode
		pop	r16
		out	SREG,r16
		pop	r16
		reti

;--------------------------------------------------------------------
;  Timer 0 overflow interrupt - sample and debounce buttons
;
;  To be a valid button press, a button must change state and
;  maintain that new state for four consecutive samples.  Counters
;  for each button are maintained "vertically" in clock_A and
;  clock_B.
;
;  Debounce code adapted from program by Scott Dattalo.
;  http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html

.dseg

current_state:          .byte   1
clock_A:                .byte   1
clock_B:                .byte   1

.cseg

tim0_ovf:       push    r16
                push    r17
                push    r18
                push    r19
                push    r20
                in      r16,SREG
                push    r16

                in      r16,PINC        ; Get button inputs
		sei			; Allow nested interrupts
		com	r16		; Invert sense
                andi    r16,$0F         ; Mask out non-button bits
                lds     r17,current_state
                mov     r20,r17
                eor     r17,r16         ; Get delta
                lds     r18,clock_A
                lds     r19,clock_B
                eor     r18,r19         ; Increment the counters
                com     r19
                and     r18,r17         ; Clear counters where nothing changed
                and     r19,r17
                sts     clock_A,r18
                sts     clock_B,r19
                or      r18,r19         ; Preserve state of bits being
                mov     r17,r20         ;  filtered, and clear state of
                and     r17,r18         ;  bits already filtered.
                com     r18             ; Re-write the bits that
                and     r18,r16         ;  are already filtered.
                or      r17,r18
		sts     current_state,r17
		eor     r20,r17         ; Check for changes in debounced state
		mov	r16,r17
		or	r16,r20
		brne	t0ov000
		cbr	info,(1<<combo)	; No buttons or transitions, exit combo
t0ov000:	tst	r20
                brne    t0ov00
t0ov9:          pop     r16		; Exit interrupt routine
                out     SREG,r16
                pop     r20
                pop     r19
                pop     r18
                pop     r17
                pop     r16
		reti

t0ov00:         mov     r16,r20         ; First check for 1-2 combo
                andi    r16,$03
                breq    t0ov0
                mov     r16,r17         ; Transition in at least one of them
                andi    r16,$03         ; Check that both are pressed
                cpi    	r16,$03
                brne    t0ov0
		ldi	r16,(1<<audible)
		eor	info,r16	; Toggle flag for audible tick
		sbr	info,(1<<combo)	; Indicate in combo state
		rjmp    t0ov9

t0ov0:          mov     r16,r20         ; Check for 2-3 combo
                andi    r16,$06
                breq    t0ov1
                mov     r16,r17
                andi    r16,$06
                cpi    	r16,$06
                brne    t0ov1
		ldi	r16,ev_23	; Report event to main loop
		sts	button_event,r16
		sbr	info,(1<<combo)	; Indicate in combo state
                rjmp    t0ov9

t0ov1:          mov     r16,r20         ; Check for 1-3 combo
                andi    r16,$05
                breq    t0ov2
                mov     r16,r17
                andi    r16,$05
                cpi    	r16,$05
                brne    t0ov2
		sbr	info,(1<<sync)+(1<<combo); Set flag for dial sync
                rjmp    t0ov9

t0ov2:          tst     r17             ; Only interested in button releases
                brne    t0ov9
                sbrc	info,combo	; Not interested in single buttons
		rjmp	t0ov9		;    until combo state cleared.
		sbrs    r20,0
                rjmp    t0ov3
		ldi	r16,ev_display	; Report display event to main loop
		sts	button_event,r16
                rjmp    t0ov9

t0ov3:          sbrs    r20,1
                rjmp    t0ov4
		ldi	r16,ev_menu	; Button 2 release: report evnt
		sts	button_event,r16
                rjmp    t0ov9

t0ov4:          ldi	r16,ev_select
		sbrc	r20,2
		sts	button_event,r16; Button 3 release: report event
		rjmp	t0ov9
;
;  No use for fourth button at this time
;



;--------------------------------------------------------------
; Timer 1 OCRB match interrupt

.EQU		MAX_SCHED	= 12	; Table size for event schedule

		.DSEG
eigth_tick:	.BYTE	2		; Incremented 8 times per second
adj_count:	.BYTE	2		; Number of seconds dial needs to be adjusted
master_time:	.BYTE	1		; Count-down timer for serial activity
sec_count:	.BYTE	2		; Current estimate of 1-second count
sched_ptr:	.BYTE	2		; Pointer to current sched table entry
sched_table:	.BYTE	4*MAX_SCHED

		.CSEG

;
; Copy schedule table to RAM while scaling event times.
;
; Uses X, Z, r0, r16-22
;
; Clocked in simulator at 572us (3.686MHz xtal) to
; scale 11 table entries.
;
sched_copy:	ldi	ZL,LOW(sched_init << 1)		; source (FLASH)
		ldi	ZH,HIGH(sched_init << 1)
		ldi	XL,LOW(sched_table)		; destintion (RAM)
		ldi	XH,HIGH(sched_table)
		lds	r16,sec_count	; Get scale factor
		lds	r17,sec_count+1
sc1:		lpm			; Get low byte event time
		adiw	ZL,1
		mov	r18,r0
		lpm			; Get high byte event time
		adiw	ZL,1
		mov	r19,r0		
		rcall	mpy16u		; Multiply by scale factor
		st	X+,r19		; Store result shifted right 8
		st	X+,r20
		lpm			; Low byte address of handler
		adiw	ZL,1
		st	X+,r0
		lpm			; High byte address of handler
		adiw	ZL,1
		st	X+,r0
		or	r19,r20		; Last byte has time 0
		brne	sc1
		ret

tim1_compb:	push	r16
		in	r16,SREG
		push	r16
		push	r17
		push	XH
		push	XL
		push	ZH
		push	ZL

		lds	XL,sched_ptr
		lds	XH,sched_ptr+1
		ld	r16,X+		; Low byte OCRB value
		ld	r17,X+		; High byte
		out	OCR1BH,r17
		out	OCR1BL,r16
		ld	ZL,X+		; Address of event handler
		ld	ZH,X+
		or	r16,r17		; Test for zero time
		brne	compb1
		ldi	XL,LOW(sched_table)	; Wrap around to first entry
		ldi	XH,HIGH(sched_table)
		in	r16,OCR1AL	; Use OCRA for top instead of 0
		in	r17,OCR1AH
		out	OCR1BH,r17
		out	OCR1BL,r16
compb1:		sts	sched_ptr,XL
		sts	sched_ptr+1,XH
		ijmp			; Jump to handler

compbex:	pop	ZL
		pop	ZH
		pop	XL
		pop	XH
		pop	r17
		pop	r16
		out	SREG,r16
		pop	r16
		reti

set_drive:	sbrc	info,sync
		rjmp	compbex		; Exit if in sync mode
		sbrs	info,dstadj
		rjmp	dt0
		lds	ZH,adj_count+1	; If dstadj active, get count
		tst	ZH
		brpl	dt0		; If adding seconds, step dial
		lds	ZL,adj_count
		adiw	ZL,1		; one less second missed
		sts	adj_count,ZL
		sts	adj_count+1,ZH
		brne	compbex		; Not yet zero, exit
		cbr	info,(1<<dstadj);  else count exhausted, clear flag
		rjmp	compbex		; Exit without stepping dial

dt0:		sbrc	info,dial
		rjmp	dt1
		sbi	PORTB,1		; Drive clock phase 0
		sbr	info,(1<<dial)	; Switch phase
		rjmp	compbex

dt1:		sbi	PORTB,2		; Drive clock phase 1
		cbr	info,(1<<dial)	; Switch phase
		rjmp	compbex

double_drive:	sbrs	info,dstadj
		rjmp	compbex		; No adjustment necessary, skip dial step
		lds	ZH,adj_count+1
		tst	ZH
		brmi	compbex		; adjustment in opposite direction, exit
		lds	ZL,adj_count
		sbiw	ZL,1		; decrement count
		sts	adj_count,ZL
		sts	adj_count+1,ZH
		brne	dt0		; Not yet zero, go step dial
		cbr	info,(1<<dstadj);  else count exhausted, clear flag
		rjmp	dt0		; Step dial ahead 1 second

second_bottom:	in	r17,PORTB
		andi	r17,~$06	; Clear clock face drive
		out	PORTB,r17
		rjmp	eigth

second_top:	rcall	incsec		; Returns field incremented for use by timedisp

dst_adjust:	cpi	r16,3
		brlo	clear_drive	; Skip if only ss or mm changed
		push	r16
		mov	r16,hh		; Calculate local hour sans DST
		mov	r17,tz
		andi	r17,tzmask
		sub	r16,r17
		cpi	r16,1
		brne	da9		; It's not 1:00AM local, exit
		mov	r16,mon		; Check month
		cpi	r16,4
		brne	da1		; Not April, check for Oct.
		mov	r16,day
		cpi	r16,8		; Check for first week of April
		brsh	da9		; No, exit
		rcall	day_of_week	; What day is it?
		tst	r16		;   (Sunday = 0)
		brne	da9		; Not Sunday, exit
		mov	r16,tz		; Set DST
		sbr	r16,(1<<dst)
		mov	tz,r16
		sbr	info,(1<<dstadj); Adjust dial
		ldi	ZL,LOW(60*60)	; Add one hour's worth of seconds
		ldi	ZH,HIGH(60*60)
		rjmp	da2
		
da1:		cpi	r16,10
		brne	da9		; Not October, exit
		mov	r16,day
		cpi	r16,25		; Check for last week of October
		brlo	da9		; No, exit
		rcall	day_of_week
		tst	r16
		brne	da9
		mov	r16,tz		; Clear DST
		cbr	r16,(1<<dst)
		mov	tz,r16
		ldi	ZL,LOW(-(60*60)); Subtract one hour's worth of seconds
		ldi	ZH,HIGH(-(60*60))
da2:		lds	r16,adj_count	; Add adjustment to count
		add	ZL,r16
		lds	r16,adj_count+1
		adc	ZH,r16
		sts	adj_count,ZL
		sts	adj_count+1,ZH
		breq	da9		; Just in case it's zero
		sbr	info,(1<<dstadj); Set flag to adjust dial
da9:		pop	r16

clear_drive:	in	r17,PORTB
		andi	r17,~$06	; Clear clock face drive
		out	PORTB,r17
		
audibletick:	sbrs	info,audible
		rjmp	at9
		mov	r17,ss
		cpi	r17,59		; No tick at 59 seconds
		breq	at9
		tst	r17		; Start of minute?
		breq	at1
		ldi	r17,8		; No, sound tick (4 cycles)
		sts	tone,r17
		rjmp	at9

at1:		ldi	r17,LOW(1772)	; Yes, sound tone (800ms)
		sts	tone,r17
		ldi	r17,HIGH(1772)
		sts	tone+1,r17
at9:
update_lcd:	sbrs	info,intdisp
		rjmp	ul9
		sbrc	info,locdisp
		rjmp	update_local
update_gmt:	rcall	timedisp
		rjmp	ul9

update_local:	rcall	ltimedisp
ul9:
dial_unhold:	tst	ss
		brne	du9
		cbr	info,(1<<sync)	; Clear sync flag after second #0
du9:
master_check:	sbrc	info,master
		rjmp	mc9		; Skip if already master
		lds	r16,master_time	; Get count-down value
		dec	r16		; decrement
		sts	master_time,r16
		brne	mc9
		sbr	info,(1<<master); Reached zero, switch to master mode
		sbi	PORTD,4		; Switch hardware to local
mc9:
sync_eigth:	lds	XL,eigth_tick	; Synchronize eigth-second counter
		ori	XL,7
		sts	eigth_tick,XL

eigth:
		lds	XL,eigth_tick	; Increment eigth-second counter
		lds	XH,eigth_tick+1
		adiw	XL,1
		sts	eigth_tick,XL
		sts	eigth_tick+1,XH
		sbrs	info,intdisp
		rjmp	ei9
		ldi	r16,$80+15	; Move to clock hand position
		rcall	lcd_cmd
		mov	r16,XL		; Make char code from tick value
		andi	r16,7
		rcall	lcd_data
ei9:		rjmp	compbex

capt_adjust:	push	r18
		push	r19
		push	r20
		lds	XL,capt_ptr	; Pick up pointer into queue
		lds	XH,capt_ptr+1
		lds	r18,capt_sum	; Pick up running sum (24-bit)
		lds	r19,capt_sum+1
		lds	r20,capt_sum+2
		ld	r16,X+		; Pick up capture value from end
		ld	r17,X+		;  of ring to remove
		sub	r18,r16		; Subtract from sum
		sbc	r19,r17
		sbci	r20,0
		sbiw	XL,2		; Back up pointer and overwrite
		lds	r16,capture	;  with new capture value
		lds	r17,capture+1
		st	X+,r16
		st	X+,r17
		add	r18,r16		; Add new value into sum
		adc	r19,r17
		clr	r16
		adc	r20,r16
		sts	capt_sum,r18	; Store new sum
		sts	capt_sum+1,r19
		sts	capt_sum+2,r20
		ldi	r16,6		; shift count
ca1:		clc			; Divide sum by 64 (cbufflen)
		ror	r20
		ror	r19
		ror	r18
		dec	r16
		brne	ca1
		sts	capt_avg,r18	; Save resulting average
		sts	capt_avg+1,r19
		ldi	r16,LOW(capt_buff+2*cbufflen)
		ldi	r17,HIGH(capt_buff+2*cbufflen)
		cp	XL,r16
		cpc	XH,r17
		brlo	ca2
		ldi	XL,LOW(capt_buff); Wrap queue pointer
		ldi	XH,HIGH(capt_buff)
ca2:		sts	capt_ptr,XL	; Store the updated pointer
		sts	capt_ptr+1,XH
		lds	r16,capt_holdoff
		subi	r16,1
		brcs	ca3
		sts	capt_holdoff,r16
		rjmp	ca9		; Not enough samples yet, don't use

ca3:		lds	r16,sec_count	; Is average same as sec_count?
		lds	r17,sec_count+1
		cp	r18,r16
		cpc	r19,r17
		breq	ca9
		sts	sec_count,r18	; No, update sec_count
		sts	sec_count+1,r19
		push	r21
		push	r22
		push	r0
		rcall	sched_copy	; re-calculate OCRB values
		pop	r0
		pop	r22
		pop	r21
ca9:		pop	r20
		pop	r19
		pop	r18
		rjmp	compbex

;--------------------------------------------------------------
; Timer 1 capture interrupt

.dseg
capture:	.byte	2

.cseg

tim1_capt:	push	r16
		push	r17
		push	ZL
		push	ZH
		in	r16,SREG
		push	r16
		ldi	r16,0
		out	TCNT1H,r16	; Sync counter
		out	TCNT1L,r16
		in	r16,ICR1L	; Save capture value
		sts	capture,r16
		in	r16,ICR1H
		sts	capture+1,r16
		lds	ZL,sched_ptr	; check schedule phase
		lds	ZH,sched_ptr+1
		ldi	r16,LOW(sched_table)
		ldi	r17,HIGH(sched_table)
		cp	ZL,r16
		cpc	ZH,r17
		breq	tc1
		sts	sched_ptr,r16	; Our of phase, sync schedule
		sts	sched_ptr+1,r17
		ldi	r16,77
		rcall	log_err_code	; Log error
tc1:		pop	r16
		out	SREG,r16
		pop	ZH
		pop	ZL
		pop	r17
		pop	r16
		rjmp	tim1_compb

;--------------------------------------------------------------
; Timer 1 overflow interrupt
;
; This should never occur, and is logged as an error

tim1_ovf:	push	r16
		push	r17
		push	ZL
		push	ZH
		in	r16,SREG
		push	r16
		ldi	r16,44		; Error code
		rcall	log_err_code
		pop	r16
		out	SREG,r16
		pop	ZH
		pop	ZL
		pop	r17
		pop	r16
		reti

;--------------------------------------------------------------
; ADC Conversion Complete Interrupt

.dseg
tone:		.BYTE	2
sample:		.BYTE	2
sample_cnt:	.BYTE	1

.cseg
adc_conv:	push	XL
		push	XH
		in	XH,SREG
		push	XH
		lds	XL,tone		; Get # of cycles left in tone
		lds	XH,tone+1
		sbiw	XL,1		; Decrement count
		brcs	adc0		; Skip if it was zero
		sts	tone,XL		; Update cycle count
		sts	tone+1,XH
		sbi	PORTB,0		; Set speaker bit
		sbrs	XL,0		; Clear it if new count is even
		cbi	PORTB,0
adc0:		in	XL,ADCL
		in	XH,ADCH
		andi	XL,$fc		; Reduce to 8 bits
		ror	XH
		ror	XL
		ror	XH
		ror	XL
		lds	XH,sample	; Add to running total
		add	XH,XL
		sts	sample,XH
		lds	XH,sample+1
		clr	XL
		adc	XH,XL
		sts	sample+1,XH
		lds	XH,sample_cnt	; Increment count
		inc	XH
		sts	sample_cnt,XH
		brne	adc2
		lds	XL,sample+1	; 256 samples, pick up average
		ldi	XH,10
		add	XL,XH		; Fudge factor
		brcc	adc1
		ldi	XL,$ff		; Clamp to max illumination
adc1:		out	OCR2,XL		; Send to PWM
		clr	XL		; reset running total
		sts	sample,XL
		sts	sample+1,XL
adc2:		pop	XH
		out	SREG,XH
		pop	XH
		pop	XL
		reti

;--------------------------------------------------------------
; EEPROM ready interrupt

.dseg
ee_buff:	.BYTE	10
ee_count:	.BYTE	1

.cseg
ee_rdy:		push	ZH
		push	ZL
		push	r16
		in	r16,SREG
		push	r16
ee_r1:		lds	r16,ee_count
		cpi	r16,10
		brlo	ee_r3
		breq	ee_r2
		cbi	EECR,EERIE	; Done with all bytes, clear interrupt
		ldi	r16,$ff		; Set done flag
		sts	ee_count,r16
		rjmp	ee_r9

ee_r2:		inc	r16		; Done storing 10 bytes
		sts	ee_count,r16	; Bump count to 11
		ldi	r16,LOW(ee_err_cnt)
		out	EEARL,r16	; Address error count
		ldi	r16,HIGH(ee_err_cnt)
		out	EEARH,r16
		sbi	EECR,EERE	; Read strobe
		in	r16,EEDR
		inc	r16		; Increment count
		out	EEDR,r16
		sbi	EECR,EERIE	; Disable interrupt
		sbi	EECR,EEMWE	; Set master write enable
		sbi	EECR,EEWE	; Set EEPROM write strobe
		rjmp	ee_r9		; Exit, never to return
		
ee_r3:		rcall	ee_iwrite
ee_r9:		pop	r16
		out	SREG,r16
		pop	r16
		pop	ZL
		pop	ZH
		reti

ee_iwrite:	sbic	EECR,EEWE	; In case we were called from code
		rjmp	ee_iwrite
		ldi	ZL,LOW(ee_buff)	; Offset by count into buffer
		ldi	ZH,HIGH(ee_buff)
		add	ZL,r16
		inc	r16
		sts	ee_count,r16	; Increment count
		clr	r16
		adc	ZH,r16
		in	r16,EEARL	; Bump EEPROM address
		inc	r16
		out	EEARL,r16
		ld	r16,Z		; Get next byte from buffer
		out	EEDR,r16
		sbi	EECR,EERIE	; Enable interrupt
		sbi	EECR,EEMWE	; Set master write enable
		sbi	EECR,EEWE	; Set EEPROM write strobe
		ret

;--------------------------------------------------------------
; Strobe a byte into LCD

strobe:		sbi	PORTC,7	; raise E
		nop
		nop
		cbi	PORTC,7	; lower E
		ret

;--------------------------------------------------------------
; Wait for LCD to be ready for a byte

busywait:	push	r16

		ldi	r16,0
		out	PORTA,r16	; turn off any pull-ups
		out	DDRA,r16	; bus to input
		sbi	PORTC,6		; R/W to read
		cbi	PORTC,5		; RS to status

bw1:		sbi	PORTC,7		; raise E
		nop
		nop
		in	r16,PINA	; read status
		cbi	PORTC,7		; lower E
		nop
		nop
		rcall	strobe		; throw away lower nibble
		tst	r16		; check for busy bit
		brmi	bw1

		cbi	PORTC,6		; R/W to write
		ldi	r16,$f0
		out	DDRA,r16	; bus to output

		pop	r16
		ret			; Note: RS left in CMD mode


;-------------------------------------------------------------
;  This is the top of the Oncore response parsing state machine.
;
;  Oncore responses consist of:
;
;      '@' '@' <cmd1> <cmd2>  [<data bytes> ...] <checksum> <CR> <LF>
;
;  We parse these commands in the interrupt routine by moving from one state
;  to the next on each received character.  Each transition from a state is
;  signaled by a call to rx_next, which will save the return address for use
;  at the next interrupt.  Note that you must call rx_next with nothing on
;  the stack (other than what was pushed by uart_rxc) andfrom the top level
;  of the interrupt code -- do not call from a subroutine.
;
;  On "return" from rx_next, r16 contains the current byte, r17 is the index
;  into the response string (1 = first byte of command following "@@")
;
;  Note that rxvec must be initialized to onc_init before interrupts are
;  enabled, or else code will go crazy.
;

onc_start:	rcall	rx_next
onc_init:	cpi	r16,'@'			; Look for first '@'
		brne	onc_start
		rcall	rx_next
		cpi	r16,'@'			; Second '@'
		brne	onc_start
		ldi	r17,0			; Clear the checksum and byte count
		sts	rxsum,r17
		sts	rxcnt,r17
		rcall	rx_next			; Get first byte of command
		sts	rxcmd,r16
		rcall	rx_next			; Second byte
		sts	rxcmd+1,r16

onc_cmd:	push	r0
		push	r18

		lds	r18,rxcmd		; retrieve first byte of command
		ldi	ZL,LOW(oncore_table << 1)
		ldi	ZH,HIGH(oncore_table << 1)
oc1:		lpm				; Get first byte from table
		tst	r0
		breq	oc4			; Zero 1st byte, end of table
		cp	r0,r18
		breq	oc2
		adiw	ZL,4			; No match, try next
		rjmp	oc1

oc2:		adiw	ZL,1			; 1st matched, try 2nd
		lpm
		cp	r0,r16
		breq	oc3
		adiw	ZL,3			; No match, try next
		rjmp	oc1

oc3:		adiw	ZL,1			; A matched cmd, pick up handler address
		lpm
		mov	r18,r0
		adiw	ZL,1
		lpm
		mov	ZH,r0
		mov	ZL,r18
		pop	r18
		pop	r0
		ijmp				; Jump to handler

oc4:		pop	r18			; No corresponding table entry, default handler
		pop	r0
		rjmp	onc_null
;---------------------------------------------------------------------
onc_pos:	ldi	r16,0			; Indicate processing
		sts	rxposflag,r16
		rcall	rx_next			; Get month
		sts	rxmonth,r16
		rcall	rx_next			; Get day
;		subi	r16,3			; FOO
		sts	rxday,r16
		rcall	rx_next			; Get year
		sts	rxyear,r16
		rcall	rx_next
		sts	rxyear+1,r16
		rcall	rx_next			; Get hour
;		subi	r16,-8			; FOO
		sts	rxhour,r16
		rcall	rx_next			; Get minute
		sts	rxminute,r16
		rcall	rx_next			; Get seconds
		sts	rxsecond,r16
		rcall	rx_next			; Get fractional seconds
		sts	rxfracsec,r16
		rcall	rx_next
		sts	rxfracsec+1,r16
		rcall	rx_next
		sts	rxfracsec+2,r16
		rcall	rx_next
		sts	rxfracsec+3,r16
		rcall	rx_next			; Get latitude
		sts	rxlat,r16
		rcall	rx_next
		sts	rxlat+1,r16
		rcall	rx_next
		sts	rxlat+2,r16
		rcall	rx_next
		sts	rxlat+3,r16
		rcall	rx_next			; Get longitude
		sts	rxlon,r16
		rcall	rx_next
		sts	rxlon+1,r16
		rcall	rx_next
		sts	rxlon+2,r16
		rcall	rx_next
		sts	rxlon+3,r16
op1:		rcall	rx_next
		cpi	r17,34
		brne	op1			; skip until DOP
		sts	rxdop,r16
		rcall	rx_next
		sts	rxdop+1,r16
		rcall	rx_next
		sts	rxdoptyp,r16
		rcall	rx_next			; Skip sats visible
		rcall	rx_next			; Get # sats tracked
		sts	rxsats,r16
		push	XL			; Set up for sat rx channels
		push	XH
		ldi	XL,LOW(rxchnls)
		ldi	XH,HIGH(rxchnls)
		sts	rxchnls_ptr,XL
		sts	rxchnls_ptr+1,XH
		pop	XH
		pop	XL
op2:		rcall	rx_next
		push	XL
		push	XH
		lds	XL,rxchnls_ptr
		lds	XH,rxchnls_ptr+1
		st	X+,r16			; Store byte of rx status in buffer
		sts	rxchnls_ptr,XL
		sts	rxchnls_ptr+1,XH
		pop	XH
		pop	XL
		cpi	r17,71
		brne	op2			; Repeat until receiver status
		sts	rxstat,r16
		ldi	r16,1			; Indicate processing finished
		sts	rxposflag,r16
;
; Test for sync
;
		lds	r16,rxsecond		; Compare current time with time received
		sts	ee_buff+9,r16		; Store in error buff in case
		cp	r16,ss			;   we need to report a sync error.
		lds	r16,rxminute
		sts	ee_buff+8,r16
		cpc	r16,mm
		lds	r16,rxhour
		sts	ee_buff+7,r16
		cpc	r16,hh
		lds	r16,rxday
		sts	ee_buff+6,r16
		cpc	r16,day
		lds	r16,rxmonth
		sts	ee_buff+5,r16
		cpc	r16,mon
		brne	op3
		ldi	r16,5			; Time matches, reset sync error counter
		sts	sync_err_cnt,r16
		rjmp	onc_null

op3:		lds	r16,sync_err_cnt	; Time mis-match, must be 5 in a row
		dec	r16
		breq	op4
		sts	sync_err_cnt,r16
		rjmp	onc_null
		
op4:		rcall	log_err			; Log error with current time
		lds	r16,capt_holdoff
		tst	r16
		breq	op45			; Skip dial adjust if in warmup
		rjmp	op9
		
op45:		push	r18			; Save regs for math
		push	r19
		push	r20
		push	r21
		push	r22
		
		lds	r16,rxhour		; Get hour difference
		mov	r17,r16
		sub	r17,hh
		cpi	r17,12			; Check for positive wrap
		brlt	op5
		ldi	r17,24			; add 24 hours to current time
		add	hh,r17			;  (we're going to over-write it
		rjmp	op6			;   with rxhour later)
		
op5:		cpi	r17,-12			; Check for negative wrap
		brge	op6
		subi	r16,-24			; add 24 hours to correct time
op6:		clr	r17			; Multiply rxhour by 60 minutes
		ldi	r18,LOW(60)
		ldi	r19,HIGH(60)
		rcall	mpy16u
		lds	r16,rxminute		; Add in minutes
		add	r18,r16
		clr	r17
		adc	r19,r17
		ldi	r16,60			; Multiply by 60 seconds
		rcall	mpy16u
		lds	r16,rxsecond
		add	r18,r16
		clr	r17
		adc	r19,r17
		push	r18			; Save correct time for later
		push	r19
		mov	r16,hh			; Now do same for current time
		clr	r17
		ldi	r18,LOW(60)
		ldi	r19,HIGH(60)
		rcall	mpy16u
		mov	r16,mm
		add	r18,r16
		clr	r17
		adc	r19,r17
		ldi	r16,60
		rcall	mpy16u
		mov	r16,ss
		add	r18,r16
		clr	r17
		adc	r19,r17
		pop	r17			; Get back correct time
		pop	r16
		sub	r16,r18			; Subtract current from correct
		sbc	r17,r19			;  to get dial adjustment
		lds	r18,adj_count		; Add adjustment to adj_count
		lds	r19,adj_count+1	
		add	r18,r16			; Note that, with 16 bits, we
		adc	r19,r17			;  can adjust for errors of up
		sts	adj_count,r18		;  to about 9 hours, plus or minus.
		sts	adj_count+1,r19
		or	r18,r19
		breq	op7
		sbr	info,(1<<dstadj)	; Set flag if non-zero
op7:		pop	r22
		pop	r21
		pop	r20
		pop	r19
		pop	r18
op9:		lds	ss,rxsecond		; Re-sync
		lds	mm,rxminute
		lds	hh,rxhour
		lds	day,rxday
		lds	mon,rxmonth
		ldi	r16,5
		sts	sync_err_cnt,r16
		rjmp	onc_null		; Note we're ignoring year

;
; Default Oncore response handler, throw away message
;
onc_null:	rcall	rx_next			; Get next byte
on1:		lds	r17,rxsum		; Look for zero checksum
		tst	r17
		brne	onc_null
		rcall	rx_next
		cpi	r16,$0D			; Byte after checksum should be <CR>
		brne	on1
		rcall	rx_next
		cpi	r16,$0A			; Byte after <CR> should be <LF>
		brne	on1
		rjmp	onc_start		; Catch next command

onc_raim:	rjmp	onc_null
;---------------------------------------------------------------------
onc_leap:	rcall	rx_next
		sts	rxleap,r16
		rjmp	onc_null
;---------------------------------------------------------------------
onc_poshold:	rcall	rx_next
		sts	rxposhold,r16
		rjmp	onc_null
;---------------------------------------------------------------------
onc_diag:	rcall	rx_next
		sts	rxdiag,r16		; Note that bytes are
		rcall	rx_next			;  reversed from AVR std.
		sts	rxdiag+1,r16
		rjmp	onc_null
;---------------------------------------------------------------------
onc_id:		lds	r16,rxidcount
		ldi	r17,26		; All lines have 26 chars
		cpi	r16,1
		brne	oi0
		ldi	r17,33		;  except for first line, which has 33
oi0:		sts	rxidchcnt,r17
		ldi	r16,LOW(rxidline)	; Initialize pointer
		sts	rxidptr,r16
		ldi	r16,HIGH(rxidline)
		sts	rxidptr+1,r16
oi1:		rcall	rx_next
		cpi	r16,$0D			; Look for CR
		brne	oi1
		rcall	rx_next			;  followed by LF
		cpi	r16,$0A
		brne	onc_null
		lds	r16,rxidcount
		dec	r16			; Decrement line count
		sts	rxidcount,r16
		brne	oi1			; Get next line if not zero
oi2:		rcall	rx_next			; Get char from line
		lds	ZL,rxidptr		; Get pointer to line
		lds	ZH,rxidptr+1
		st	Z+,r16			; Store char
		sts	rxidptr,ZL
		sts	rxidptr+1,ZH		; Store pointer
		lds	r16,rxidchcnt		; Get line len count
		dec	r16			; Decrement
		sts	rxidchcnt,r16
		brne	oi2			; Not yet zero, get next char
		clr	r16			; Store terminating null
		st	Z+,r16
		ser	r16			; Indicate line done
		sts	rxidcount,r16
		rjmp	onc_start		; Throw away rest of response
;---------------------------------------------------------------------
rx_next:	pop	ZH			; Get return address
		pop	ZL
		sts	rxvec,ZL
		sts	rxvec+1,ZH

urx_fe:		ldi	r16,55
		rjmp	uf2			; FOO -- lots of FE's for unknown reason
		
uf1:		rcall	log_err_code
uf2:		pop	ZL
		pop	ZH
		pop	r17
		pop	r16
		out	SREG,r16
		pop	r16
		reti

urx_ovr:	ldi	r16,66
		rjmp	uf1

;---------------------------------------------------------------------
; Store error in EEPROM.  (r16) is error code, or write your own
; into ee_buff[0..4] and call log_err.
;
; Uses ZL,ZH,r16,r17
;

log_err_code:	sts	ee_buff+5,r16		; Store error code in 2nd half	
		sts	ee_buff+6,r16
		sts	ee_buff+7,r16
		sts	ee_buff+8,r16
		sts	ee_buff+9,r16
log_err:	lds	r16,capt_holdoff
		tst	r16
		brne	le9			; Ignore errors if in warmup
		lds	r16,ee_count
		cpi	r16,$ff
		brne	le9			; Ignore if error already in progress
le1:		ldi	ZL,LOW(ee_err_cnt)	; Get error count from EEPROM
		ldi	ZH,HIGH(ee_err_cnt)
		out	EEARH,ZH
		out	EEARL,ZL
		sbi	EECR,EERE
		in	r16,EEDR
		mov	r17,r16
		lsl	r16			; Multiply count by 10 for index
		mov	ZL,r16
		lsl	r16
		lsl	r16
		add	ZL,r16
		dec	ZL
		ldi	ZH,1			; Store log in second half of EEPROM
		out	EEARL,ZL
		out	EEARH,ZH
		sts	ee_buff,mon		; Store current at beginning
		sts	ee_buff+1,day
		sts	ee_buff+2,hh
		sts	ee_buff+3,mm
		sts	ee_buff+4,ss
		clr	r16
		sts	ee_count,r16		; Reset the count
		rcall	ee_iwrite		; Start the ball rolling
le9:		ret

;-------------------------------------------------------------------------
; Strings and tables

receiver_id:	.db	"Cj"
pos_stat:	.db	"Ea",1
survey_init:    .db     "At",2
self_test:	.db	"Fa"

listen:		.db	"Listening",0
no_data_err:	.db	"No data from Oncore!",0
short_str:	.db	"Shorted antenna!",0
open_str:	.db	"Open antenna!",0
survey_query:	.db	"Start site survey?",0
survey_start:   .db     "Starting site survey",0
written_str:	.db	"EEPROM updated",0
timezone_str:	.db	"Timezone: ",0
test_query_str:	.db	"Initiate Self-test?",0
test1_str:	.db	"Testing",0
test2_str:	.db	"Test result: ",0
err_str:	.db	"Err: ",0
track_str:	.db	"Tracking ", 0
dop_str:	.db	"DOP ", 0
capture_str:	.db	"Capture ", 0
master_str:	.db	"MASTER",0
slave_str:	.db	"SLAVE",0
mode_str:	.db	" mode",0
freq_str:	.db	"CPU CLK = ",0
;                        12345678901234567890
;-------------------------------------------------------------------------
months:		.db	"Jan", 0
		.db	"Feb", 0
		.db	"Mar", 0
		.db	"Apr", 0
		.db	"May", 0
		.db	"Jun", 0
		.db	"Jul", 0
		.db	"Aug", 0
		.db	"Sep", 0
		.db	"Oct", 0
		.db	"Nov", 0
		.db	"Dec", 0
;-------------------------------------------------------------------------
utc:		.db	"UTC", 0
timezones:	.db	"Eastern", 0,0,0
		.db	"Central", 0,0,0
		.db	"Mountain",  0,0
		.db	"Pacific", 0
;-------------------------------------------------------------------------
monthsize:	.db	31,28,31,30,31,30,31,31,30,31,30,31
;-------------------------------------------------------------------------
dow_table:	.db	6,2,2,5,0,3,5,1,4,6,2,4
;-------------------------------------------------------------------------
;
; Dispatch table for display state handlers
;
;.EQU		MAX_DISPLAY_STATE = 5

dstate_table:	.dw	utc_state
		.dw	local_state
		.dw	disp_lat
		.dw	disp_lon
		.dw	disp_master
;-------------------------------------------------------------------------
;
; Dispatch table for menu state hndlers
;
;.EQU		MAX_MENU_STATE = 7
;.EQU		MAX_SLAVE_MENU_STATE = 4

mstate_table:	.dw	m_timezone
		.dw	m_errors
		.dw	m_track
		.dw	m_capture
		.dw	m_id
		.dw	m_survey
		.dw	m_test
;-------------------------------------------------------------------------
; Display table.  Fields are: Cursor position, address of display routine

disp_table:	.dw	13,sec_disp
		.dw	10,min_disp
		.dw	7,hour_disp
		.dw	4,day_disp
		.dw	0,month_disp
		.dw	0,year_disp
;-------------------------------------------------------------------------
ldisp_table:	.dw	13,sec_disp
		.dw	10,min_disp
		.dw	0,lmonth_disp	; Always update month & day in case
		.dw	0,lmonth_disp	;   of local midnight.
		.dw	0,lmonth_disp
		.dw	0,lyear_disp

;-------------------------------------------------------------------------
;
;  Decode table for incoming Oncore responses.
;
;  Each entry is:
;      Response header (2 bytes)
;      Address of handler (1 word)
;
;  Table is terminated by a null header.
;
oncore_table:	.DB	"Ea"
		.DW	onc_pos		; Position/Status/Data Message

		.DB	"En"
		.DW	onc_raim	; Time RAIM status

		.DB	"Bj"
		.DW	onc_leap	; Leap second status

		.DB	"At"
		.DW	onc_poshold	; Position-Hold mode
		
		.DB	"Fa"		; Self-diagnosis
		.DW	onc_diag
		
		.DB	"Cj"
		.DW	onc_id

		.DB	0

;-------------------------------------------------------------------------
;
;  Format of schedule table is:
;
;     (word) OCRB value of *next* event to schedule in 256ths of a second
;     (word) Address of handler for this event
;
;  Entries must be in chronological order.
;
;  Last entry must have OCRB value of 0.  (Which implies that
;  first entry is executed at rollover.)
;
;  Initial table is in FLASH, but copied to RAM.  In the
;  process, OCRB values are re-scaled by sec_count, and the
;  first field is replaced with actual OCRB values.
;

.EQU		pw	=13		; Width of dial pulse

sched_init:	.dw	32,	second_top
		.dw	64,	eigth
		.dw	65,	eigth
		.dw	96,	capt_adjust
		.dw	128-pw,	eigth
		.dw	128,	double_drive
		.dw	160,	second_bottom
		.dw	192,	eigth
		.dw	224,	eigth
		.dw	256-pw,	eigth
		.dw	0,	set_drive
;-------------------------------------------------------------------------
hands:		.db	$00,$04,$04,$04,$00,$00,$00,$00	;0
		.db	$00,$01,$02,$04,$00,$00,$00,$00	;1
		.db	$00,$00,$00,$07,$00,$00,$00,$00	;2
		.db	$00,$00,$00,$04,$02,$01,$00,$00	;3
		.db	$00,$00,$00,$04,$04,$04,$00,$00	;4
		.db	$00,$00,$00,$04,$08,$10,$00,$00	;5
		.db	$00,$00,$00,$1C,$00,$00,$00,$00	;6
		.db	$00,$10,$08,$04,$00,$00,$00,$00	;7
