;===========================================================================
;
; Super Accurate Clock using TCXO
; suprclk2.asm
;
; Date 		19/12/02
; Version 	0.2
; Target	AT90S2313-10PC
;
; sjr mods 23/03/10 #1
;
; Software (C) Copyright M. Greenman 2000. All rights of the owner reserved.
; No performance promises are made or implied, and no responsibility will be
; accepted for misuse or failure of software to perform. 
;
; DESCRIPTION
;
; Uses a cell-phone TCXO, 9.600, 12.800, 14.85 MHz to provide a really accurate
; UTC clock. Can use any crystal n x 50 kHz, (actually any crystal n x 1 kHz),
; although baud rate might be a challenge, and has outputs from 1 kHz down.
; Initial version uses 12 MHz crystal. LCD display in 4 bit mode.
;
; Time is set via serial command interpreter, and reported every second.
; Has a 1 sec tick output for precise sync to time sigs. Other outputs
; used as timing references. What's available depends on the TCXO used.
;
; Uses ICP as TV sync input in order to send phase of local ref.
;
; Intended to operate in sleep for minimum current drain. Uses NiCd etc backup.
;
;* History:
;* 10/11/02	First cut
;* 25/11/02	Several mods. PORTB corrected for LCD routines corrupting I/O
;* 19/12/02	":" removed before phase in RS232 msg.
;*		"<" and ">" commands replace "L"
;*		Power fail detection added (PD2)
;* 02/01/03	Chime on hour added - also chimes every min when pwr failed
;
;===========================================================================

;	.device AT90S2313
	.include "2313DEF.INC"		;Processor specific definitions
	.include "superclk.inc"		;Definitions peculiar to this project
;===========================================================================

; PROGRAM OPERATION

; The main program performs the routine tasks that are not time conscious,
; including binary time to decimal conversion, display update and serial TX.
; Hopefully the processor will sleep when all is done.

; T1 divides by n, to give a 2 kHz interrupt. Software provides a toggle for
; 1 kHz output, and further divides by 1000
; to one sec, where the clock registers are updated.

; Serial rx interrupt is used for command interpreter, which sets time.


; TIMING

; System clock TCXO at 14.85 MHz etc (n x 50 kHz). T1 operates from clock /1,
; and divides to 1 kHz. (It divides by n x 50 /2). The timer auto-reloads on output
; compare, and the PB4 output is toggled in hardware at 1 kHz. 

; The T1 interrupt operates a software divider which generates the 1 sec tick and
; increments the real time registers, counting seconds, minutes and hours.

; The ICP interrupt may be driven by TV frame sync, and then samples T1 (which must
; count at a multiple of 50 Hz of course), storing the phase for transmission every second.


; PORTS

;	PORTB - reprogramming, LCD data, OC1 25 kHz output
;	PORTD - Digital I/O, UART

;	PB0	12	AIN0		LCD D4
;	PB1	13	AIN1		LCD D5
;	PB2	14 			LCD D6
;	PB3	15	OC1		LCD D7
;	PB4	16			1 kHz output
;	PB5	17	MOSI		Programming, 1Hz pulse
;	PB6	18	MISO		Programming, Hour chime (low 1 sec)
;	PB7	19	SCK		Programming

;	PD0	 2	RXD		UART receive data
;	PD1	 3	TXD		UART transmit data
;	PD2	 6	INT0		POWERFAIL (low = main supply low)
;	PD3	 7	INT1		
;	PD4	 8	T0		LCD RS
;	PD5	 9	T1		LCD E
;	PD6	11	ICP		External reference sample (50 Hz frame sync)

; EEPROM

	.eseg
	.org	0x00

	.db	0,0,0,0			;first four not used
;	.db	77			;Baud rate (9600 baud with 12 MHz xtal)
;	.db	96			;Baud rate (9600 baud with 14.85 MHz)
	.db	0x52			;baud rate 9600 with 12.8 Mhz tcxo
;	.db	0x17, 0x70		;T1 reload for 2 kHz interrupt with 12 MHz crystal
;	.db	0x1D, 0x00		;T1 reload for 2 kHz interrupt with 14.85 MHz TCXO
	.db	0x18, 0xFF
;	.db	0x13			;NZDT offset
	.db	0x05			;USA central time offset CT: -6 = 0x06, DCT: -5 = 0x05

; MAIN PROGRAM

	.cseg
	.org	0x00
START:
	rjmp	RESET
	reti
	reti
	rjmp	CAPT		;samples phase
	rjmp	T1COMP		;output compare triggers timekeeping
	reti			;no T1 interrupt
	reti			;no T0 interrupt
	rjmp	UARTRX		;command interpreter

	.org	0x08

RESET:
	ldi	TEMP, RAMEND	;Top of RAM
	out	SPL, TEMP	;set stack pointer

	ldi	TEMP, 0x1F	;set watchdog
	out	WDTCR, TEMP
	ldi	TEMP, 0x17	;to turn off WDE
	out	WDTCR, TEMP	;and kill the watchdog
; set ports
	ser	TEMP		;all outputs
	out	DDRB, TEMP	;set Port B direction

	ldi	TEMP, 0x3A	;PD0, PD2, PD6 inputs, others outputs
	out	DDRD, TEMP	;
	ldi	TEMP, 0x00	;all off
	out	PORTD, TEMP	;

;clear clock to 00:00:00
	clr	UTCHR
	clr	UTCMIN
	clr	UTCSEC
	ldi	TEMP, 0x03	;reload with 999 (0x3E7)
	mov	MSECH, TEMP
	ldi	TEMP, 0xE7
	mov	MSECL, TEMP

; start display
	rcall	LCDSET
	rcall	CLEAR
	rcall	LCDBLURB

; read EEPROM baud rate and set UART
	ldi	TEMP, 0x04
	mov	R0, TEMP	;pointer to baud rate
	rcall	EEPR
	out	UBRR, XFR
	ldi	TEMP, 0x98	;Enable UART TX, RX and RX interrupt
	out	UCR, TEMP

; setup timers
	clr	TEMP
	out	TCCR0, TEMP
	out	TCNT0, TEMP

	out	TCCR1A, TEMP	;clear all the timer registers
	out	TCCR1B, TEMP
	out	TCNT1H, TEMP
	out	TCNT1L, TEMP
	out	OCR1AH, TEMP
	out	OCR1AL, TEMP	

; Load T1 compare value from EEPROM to fire T1COMP interrupt at 1000 Hz rate
	clr	TEMP
	out	TCCR1A, TEMP	;no OC1 or PWM
	ldi	TEMP, 0x09
	out	TCCR1B, TEMP	;ICP -ve edge, T1 source ck /1, reset on comp match
	ldi	TEMP, 0x05
	mov	R0, TEMP	;pointer to "N" divider value
	rcall	EEPR		;high byte of counter
	mov	TEMP2, XFR
	rcall	EEPR		;low byte of counter
;debug
;	ldi	TEMP2, 0x17
;	ldi	XFR, 0x70

;end debug
	out	OCR1AH, TEMP2
	out	OCR1AL, XFR	;compare match at 1 kHz rate

	rcall	EEPR
	mov	OFFSET, XFR

; set interrupt configuration
	ldi	TEMP, 0x00
	out	GIMSK, TEMP	;no ext interrupts
	ldi	TEMP, 0x48 
	out	TIMSK, TEMP	;enable Output Compare and Input Capture
	ldi	TEMP, 0x03
	out	MCUCR, TEMP	;INT0 low interrupts
; Start interrupts
	sei			;starts the interrupts

	rcall	LCDBLURB

WBLURB:
;Wait 6 sec with BLURB displaying
	mov	TEMP, UTCSEC
	cpi	TEMP, 0x10
	brcs	WBLURB
	rcall	CLEAR


; USER PROGRAM

USER1:
; If TICK flag is set then
	sbrs	FLAGS, 7	;TICK flag
	rjmp	USER3

;update display
	rcall	DISPTIME
	rcall	DISPLOCAL
	rcall	DISPPHASE
USER2:
;send time to UART
	rcall	SENDTIME
;	ldi	XFR, 0x3A	;":"
;	rcall	SEND
	rcall	SENDPHASE
;send CR/LF
	ldi	XFR, 0x0D	;CR at end of line
	rcall 	SEND
	ldi	XFR, 0x0A	;LF at end of line
	rcall 	SEND

;check and set BATT flag (power failure)
;change cursor from 0x52 to 0x50	
	ldi	XFR, 0x50
	rcall	CURSOR
	sbic	PIND, 2		;power fail pin
	rjmp	PWRFAIL

	ldi	XFR, 0x42		;"B"
	rcall	DATA_LCD
	ldi	XFR, 0x41		;"A"
	rcall	DATA_LCD
	mov	XFR, UTCSEC
	ldi	XFR, 0x54		;"TT"
	rcall	DATA_LCD
	rcall	DATA_LCD
	rjmp	CHKSEC			;sends chime every minute while power failed

PWRFAIL:
	ldi	XFR, 0x20		;" "
	rcall	DATA_LCD
	rcall	DATA_LCD
	rcall	DATA_LCD
	rcall	DATA_LCD

USER3:
; else
;check for hour chime

	clr	TEMP
	add	TEMP, UTCMIN
	breq	CHKSEC

;not new hour
USER4:
	cbr	FLAGS, 0x80	;clear TICK flag

	;sleep
	ldi	TEMP, 0x20	;idle mode, set sleep bit
	out	MCUCR, TEMP
	sleep

;loop back to USER1
	rjmp	USER1

CHKSEC:
	clr	TEMP
	add	TEMP, UTCSEC
	breq	CHIME
	sbi	PORTB,6		;raise chime
	rjmp	USER4

CHIME:
	cbi	PORTB, 6	;lower chime output
	rjmp	USER4


;=====================================================================================
; INTERRUPTS

CAPT:
;Samples current phase
	in	PHASEL, ICR1L
	in	PHASEH, ICR1H
	sbr	FLAGS, 0x40	;set PHASE flag
	reti

T1COMP:
;1/2 ms elapsed, toggle pin
	push 	TEMP
	push	XFR
	sbrs	FLAGS, 5	;if 1 kHz FLAG is set
	rjmp	T1COMP1
	cbi	PORTB, 4	;so clear 1 kHz port pin
	cbr	FLAGS, 0x20	;and flag

T1COMP2:
	pop	XFR
	pop	TEMP		;that's all this time
	reti

T1COMP1:
	sbi	PORTB, 4	;set 1 kHz output
	sbr	FLAGS, 0x20	;and flag
;inc software counter
; (An alternative would be to up-count MSECL to 20 to give 50 Hz, then count MSECH to 50 to
; give secs. This would have the advantage of full phase resolution using MSECL:PHASEL:PHASEH)


	clr	TEMP
	ldi	XFR, 0x01
	sub	MSECL, XFR
	sbc	MSECH, TEMP	;gets the carry

; if carry now set then must have reached zero

	brcc	T1A

;reload - second has been reached
	sbi	PORTB, 5	;set 1 Hz pulse
	sbr	FLAGS, 0x80	;set TICK flag
;debug
;	rjmp	T1B
;
;reset millisecs counter
	cli
	ldi	TEMP, 0x03	;reload with 999 (0x3E7)
	ldi	XFR, 0xE7
	mov	MSECH, TEMP
	mov	MSECL, XFR
	sei


;increment clock
;secs
	mov	XFR, UTCSEC
	inc	XFR
	rcall	FIXBCD
	cpi	XFR, 0x60	;"60" secs
	brcs	SECSOK
	clr	XFR
	mov	UTCSEC, XFR	;zero secs
;mins
	mov	XFR, UTCMIN
	inc	XFR
	rcall	FIXBCD
	cpi	XFR, 0x60	;"60" min
	brcs	MINOK
	clr	XFR
	mov	UTCMIN, XFR	;zero min
;hours
	mov	XFR, UTCHR
	inc	XFR
	rcall	FIXBCD
	cpi	XFR, 0x24	;"24" hours
	brcs	HROK
	clr	XFR
	mov	UTCHR, XFR	;zero hours
	rjmp	T1B
SECSOK:	
	mov	UTCSEC, XFR
	rjmp	T1B
MINOK:
	mov	UTCMIN, XFR
	rjmp	T1B
HROK:
	mov	UTCHR, XFR
	rjmp	T1B

T1A:
;All done
	cbi	PORTB, 5	;clear 1Hz pulse output
T1B:
	rjmp	T1COMP2


UARTRX:
;Used to set display (conmmand interpreter)
	push	XFR
	in	XFR, UDR
	cpi	XFR, 0x10	;control char
	brcs	ERROR
;make command upper case
	cpi	XFR, 0x60
	brcs	CMD1
	subi	XFR, 0x20	;make upper case
	rjmp	CMD1
	
ERROR:
	ldi	XFR, 0x63
	rcall	SEND
UDONE:
	ldi	XFR, 0x0D
	rcall	SEND
	ldi	XFR, 0x0A
	rcall	SEND
	pop	XFR
	reti

CMD1:
	cpi	XFR, 0x48	;"H"
	breq	HOURS
	cpi	XFR, 0x4D	;"M"
	breq	MINUTES
	cpi	XFR, 0x53	;"S"
	breq	SECONDS
	cpi	XFR, 0x3E	;">" (inc local time hrs offset)
	breq	LOCALINC
	cpi	XFR, 0x3C	;"<" (dec local time hrs offset)
	breq	LOCALDEC
	cpi	XFR, 0x52	;"R" (reset)
	breq	RESETSECS
	cpi	XFR, 0x2B	;"+"
	breq	PLUS
	cpi	XFR, 0x2D	;"-"
	breq	MINUS
	rjmp	UDONE		;nothing else valid

HOURS:
	rcall	SEND
	rcall	GETDATA
	mov	UTCHR, XFR
	rjmp	UDONE

MINUTES:
	rcall	SEND
	rcall	GETDATA
	mov	UTCMIN, XFR
	rjmp	UDONE

SECONDS:
	rcall	SEND
	rcall	GETDATA
	mov	UTCSEC, XFR
	rjmp	UDONE

LOCALINC:
	inc	OFFSET
	rjmp	UDONE

LOCALDEC:
	dec	OFFSET
	rjmp	UDONE

RESETSECS:
	rcall	SEND
	clr	TEMP
	ldi	XFR, 0x0A
	add	MSECL, XFR
	adc	MSECH, TEMP	;gets the carry
	rjmp	UDONE

PLUS:
	mov	XFR, UTCSEC
	cpi	XFR, 0x59	;can't increment
	breq	UDONE
	inc	XFR
	rcall	FIXBCD
	mov	UTCSEC, XFR	;zero secs
	rjmp	UDONE

MINUS:
	clr	XFR
	add	XFR, UTCSEC	;can't decrement
	breq	UDONE
	dec	XFR
;	rcall	FIXBCD		;fixed next sec
	mov	UTCSEC, XFR	;zero secs
	rjmp	UDONE


GETDATA:
;get rest of command - two char
	rcall	NEXTCHAR
;	rcall	SEND
	rcall	UNASCII		;data in low nibble
	brcs	ERROR1
	swap	XFR
	mov	CMD, XFR	;high nibble loaded
	rcall	NEXTCHAR
;	rcall	SEND
	rcall	UNASCII
	brcs	ERROR1
	or	CMD, XFR	;now packed BCD data
	mov	XFR, CMD
	rcall	SENDBOTH
	ret

NEXTCHAR:
;waits for and gets one character from UART
	in	TEMP, USR	;wait for byte ready
	rol	TEMP
	brcc	NEXTCHAR
	in	XFR, UDR	;get byte
	ret

UNASCII:
; Converts ASCII char in XFR to low nibble binary in XFR. Returns error as carry
	cpi	XFR, 0x30	;control char
	brcs	WRONG
	andi	XFR, 0x7F	;remove top bit
	cpi	XFR, 0x60	;lower case
	brcs	UOK
	subi	XFR, 0x20	;make Upper Case
UOK:
	cpi	XFR, 0x40	;letter?
	brcs	NUMBS
	subi	XFR, 0x37
	rjmp	UARTDONE
NUMBS:
	subi	XFR, 0x30	;make binary 0-9
UARTDONE:
	clc			;no error
WRONG:
ERROR1:
	ret


;=====================================================================================
; SUBROUTINES

EEPR:
; Arrive with pointer to EEPROM location in R0. Returns value in XFR, and R0 is
; post-incremented. Allows repeated reads without messing with pointer. Destroys TEMP.
	out	EEAR, R0
	ldi	TEMP, 1
	out	EECR, TEMP	;Read
	in	XFR, EEDR	;Get data
	inc	R0
	ret

FIXBCD:
;Arrive with byte in XFR. Correct BCD (i.e. if LSB > 9, add one to MSB and sub 10 from LSB)
;Return result in XFR. Routine should work if XFR is (correct BCD)+1 to 5. Destroys TEMP
	ldi	TEMP, 0x0F
	and	TEMP, XFR
	cpi	TEMP, 0x0A
	brcc	FIXNEEDED	;greater than 9 - needs correction
FIXDONE:
	ret

FIXNEEDED:
	ldi	TEMP, 0x06
	add	XFR, TEMP	;adds 1 to MSB and subtracts 10 from LSB
	rjmp	FIXDONE

SENDBOTH:
	swap	XFR
	rcall	ASCII
	swap	XFR
	rcall	ASCII
	ret

ASCII:

; ASCII print @ serial port. Arrive with low nibble in XFR, return
; Arrive with binary in low nibble of XFR, return with XFR unchanged,
; and low nibble sent to UART as ASCII. To send both nibbles, SWAP and call again.

	push	XFR		;so we don't destroy XFR
	cbr	XFR, 0xF0	;clear MSBs
	cpi	XFR, 0x0A	;sets carry if XFR is smaller than 0x0A
	brcs	ASCII2		;to do the numbers
	ldi	TEMP, 0x37	;magic number for A - F
	rjmp	ASCII3
ASCII2:	ldi	TEMP, 0x30	;magic number for 0 - 9
ASCII3:	add	XFR, TEMP	;to make ASCII

ASCII4:	in	TEMP, USR	;get UART TX status
	rol	TEMP
	rol	TEMP
	rol	TEMP		;TX status in carry
	brcc	ASCII4		;wait for TX ready
	out	UDR, XFR	;no waiting, no delay!
	pop	XFR		;restore the char 
	ret

TOASCII:
; just converts lower nibble of XFR to ASCII
	cbr	XFR, 0xF0	;clear MSBs
	cpi	XFR, 0x0A	;sets carry if XFR is smaller than 0x0A
	brcs	TOA2		;to do the numbers
	ldi	TEMP, 0x37	;magic number for A - F
	rjmp	TOA3
TOA2:	
	ldi	TEMP, 0x30	;magic number for 0 - 9
TOA3:
	add	XFR, TEMP
	ret

SEND:

; Send Character
; This subr will send any (already ASCII) byte to the UART, so
; a control char (space or CR) can be sent
	sbis	USR, UDRE
	rjmp	SEND
	out	UDR, XFR
	ret

SENDTIME:
	mov	XFR, UTCHR
	rcall	SENDBOTH
	ldi	XFR, 0x3A		;colon ":"
	rcall	SEND
	mov	XFR, UTCMIN
	rcall	SENDBOTH
	ldi	XFR, 0x3A		;colon ":"
	rcall	SEND
	mov	XFR, UTCSEC
	rcall	SENDBOTH
	ldi	XFR, 0x20
	rcall	SEND
	ret

DISPTIME:

; LCD display of UTC time, top line
; Data is in UTCHR, UTCMIN, UTCSEC, packed BCD

	ldi	XFR, 0x02
	rcall	CURSOR
	mov	XFR, UTCHR
	rcall	PRINTB
	ldi	XFR, 0x3A		;colon ":"
	rcall	DATA_LCD
	mov	XFR, UTCMIN
	rcall	PRINTB
	ldi	XFR, 0x3A		;colon ":"
	rcall	DATA_LCD
	mov	XFR, UTCSEC
	rcall	PRINTB


	ldi	XFR, 0x20		;" UTC"
	rcall	DATA_LCD
	ldi	XFR, 0x55
	rcall	DATA_LCD
	ldi	XFR, 0x54
	rcall	DATA_LCD
	ldi	XFR, 0x43
	rcall	DATA_LCD
	ret

DISPLOCAL:

; LCD display of LOCAL time, lower line
; Data is in UTCHR, UTCMIN, UTCSEC, packed BCD

	ldi	XFR, 0x42
	rcall	CURSOR
	mov	XFR, UTCHR
	add	XFR, OFFSET
	rcall	FIXBCD

	cpi	XFR, 0x24
	brcs	L2
	ldi	TEMP, 0xDC	;"-24" in packed BCD
	add	XFR, TEMP	;add -24 and ignore carry
	brhs	L2
	subi	XFR, 0x06	;fix for no half carry

L2:
	rcall	FIXBCD
	cpi	XFR, 0x12
	brcs	L3

;time is PM
	ldi	TEMP, 0xEE	;"-12" in packed BCD
	add	XFR, TEMP	;add -12 and ignore carry
	brhs	L2A
	subi	XFR, 0x06	;fix for no half carry
L2A:
	rcall	PRINTB
	ldi	XFR, 0x3A		;colon ":"
	rcall	DATA_LCD
	mov	XFR, UTCMIN
	rcall	PRINTB

	ldi	XFR, 0x20		;" pm"
	rcall	DATA_LCD
	ldi	XFR, 0x70
	rcall	DATA_LCD
	rjmp	L4


L3:
;time is AM
	rcall	PRINTB
	ldi	XFR, 0x3A		;colon ":"
	rcall	DATA_LCD
	mov	XFR, UTCMIN
	rcall	PRINTB

	ldi	XFR, 0x20		;" am"
	rcall	DATA_LCD
	ldi	XFR, 0x61
	rcall	DATA_LCD
L4:
	ldi	XFR, 0x6d
	rcall	DATA_LCD

	ldi	XFR, 0x20		;" -" was " +" 0x20,0x2B 
	rcall	DATA_LCD
	ldi	XFR, 0x2D
	rcall	DATA_LCD

	mov	XFR, OFFSET
	rcall	PRINTB
	ret


SENDPHASE:

; HEX-ASCII reference phase to UART
; Data is in PHASEH, PHASEL, binary

	mov	XFR, PHASEH
	rcall	SENDBOTH
	mov	XFR, PHASEL
	rcall	SENDBOTH
	ret

DISPPHASE:

; LCD display of reference phase, bottom line
; Data is in PHASEH, PHASEL, binary

; changed cursor from 0x12 to 0x10
	ldi	XFR, 0x10
	rcall	CURSOR
	mov	XFR, PHASEH
	rcall	PRINTB
	mov	XFR, PHASEL
	rcall	PRINTB
	ret

PRINTB:
;prints both nibbles, high first
	swap	XFR
	rcall	PRINTNIB
	swap	XFR
	rcall	PRINTNIB
	ret

PRINTNIB:

; Prints lower nibble on LCD. Data arrives in lower nibble of XFR. Cursor
; must be already in the right place. Like ASCII, it restores XFR, and to
; send both nibbles, swap, send one, swap and send again.
	push	XFR
	andi	XFR, 0x0F		;Only lower nibble
	rcall	TOASCII			;XFR conv to ASCII
	rcall	DATA_LCD
	pop	XFR
	ret


STUFF:	
; debug subr for use in timer
	push	XFR
	inc	DEBUG		;next register to report
	andi	DEBUG, 0x1F	;only 0 - 31, and sets Z if zero
	brne	STUFF2		;not zero
	ldi	XFR, 0x0D	;CR at end of line
	rcall 	SEND
	ldi	XFR, 0x0A	;LF at end of line
	rcall 	SEND

STUFF2:	clr	ZHI		;pointer
	mov	ZLO, DEBUG	;to register to report
	ld	XFR, Z		;in turn, get registers 0 - 15 (visible as RAM)
	push	XFR
	swap	XFR		;high nibble
	rcall	ASCII		;send it as ASCII
	pop	XFR
	rcall	ASCII		;and send low nibble
	ldi	XFR, 0x20	;space char
	rcall	SEND
	pop	XFR
	ret
;=====================================================================================
; LCD SUBROUTINES

LCDSET:
; sets up display
	ldi	TEMP, 250		; Wait at least 15msec after powerup
SET1: 
	rcall	WAITN			; to display
	dec	TEMP
	brne	SET1
        ldi     TEMP, system_set
	swap 	TEMP
        out     PORTB, TEMP
	cbi	PORTD, 4		;Back to CMD mode 
	sbi	PORTD, 5
        nop
        nop                             ; Data write cycle must be >1 ms
        cbi	PORTD, 5         	; OE low to clock in data
	rcall	WAIT2			; Wait 4 msec
	rcall	WAIT2

        ldi     TEMP, system_set 	; And again
	swap	TEMP
        out     PORTB, TEMP
	cbi	PORTD, 4		;Back to CMD mode 
	sbi	PORTD, 5
        nop
        nop
        cbi	PORTD, 5
	rcall	WAIT2			; wait at least 100usec

        ldi     TEMP, system_set	; 3rd time
	swap	TEMP
        out     PORTB, TEMP
	cbi	PORTD, 4		;Back to CMD mode 
	sbi	PORTD, 5
        nop
        nop
        cbi     PORTD, 5
        rcall   WAIT2             	; Wait at least 40usec (2 msec)

        ldi     XFR, function_set	; 4 bit mode, 2 lines 5X7 pixels
        rcall   CMD_LCD			; Write to display

        ldi     XFR, display_off	; Display off, cursor off and blink off
        rcall   CMD_LCD

        ldi     XFR, display_clear
        rcall   CMD_LCD
        rcall   WAIT2     		; Must wait 1.6 msec after clear

        ldi     XFR, entry_mode_set	; Increment RAM, dont shift display
        rcall   CMD_LCD

        ldi     XFR, display_on 	; Display on, cursor/blink off
        rcall   CMD_LCD
	ret

WAITN:
; delay defined by val in TEMP
	push	TEMP
	mov	DELAY1, TEMP
	rjmp	W2_1
WAIT2:
; 2ms delay with 4/8/12/16 MHz clock
	push	TEMP
;	ldi	TEMP, 0x0B
;	ldi	TEMP, 0x16	;8 MHz
	ldi	TEMP, 0x22	;12 MHz

	mov	DELAY1, TEMP
W2_1:
	clr	DELAY2
	dec	DELAY2			;start at 0xFF
W2_2: 	dec	DELAY2
	brne	W2_2
	dec	DELAY1
	brne	W2_1
	pop	TEMP
	ret

LCDBLURB:
; sign-op message
	ldi	ZHI, HIGH(2*TXT)
	ldi	ZLO, LOW(2*TXT)
L3A:	lpm				;from prog mem into R0
	mov	XFR, R0
	tst	XFR
	brne	L3B
	rjmp	L4A
L3B:	cpi	XFR, 0x0D		;Look for CR in message (new line)
	brne	L3C
	ldi	XFR, 0xC0		;Cursor to position 0x40, start of 2nd line
	rcall	CMD_LCD			;Sends command
	rjmp	L3D

L3C:	rcall	DATA_LCD
L3D:	adiw	ZLO, 1			;Increment pointer. This neat instruction does
	rjmp	L3A			;16 bit addition to pointer ZHI:ZLO.


L4A:
	ret				;All done now

DATA_LCD:
; displays char as two byte ASCII on display
	push	XFR
	push	TEMP
	push	TEMP2
	in	TEMP2, PORTB
	andi	TEMP2, 0xF0		;only the output pins
	mov	TEMP, XFR
	andi	XFR, 0xF0		;Mask off lower 4 bits
	swap	XFR
	or	XFR, TEMP2		;add the port bits to the LCD stuff
	out	PORTB, XFR		;Write upper 4 bits to display
	sbi	PORTD, 5		;OE bit high
	sbi	PORTD, 4		;Data/command bit high
	nop				;Brief delay
	nop
	nop
	nop
	nop
	cbi	PORTD, 5		;OE low to clock data
	cbi	PORTD, 4		;Back to default 

	andi	TEMP, 0x0F		;Masked off
	or	TEMP, TEMP2		;add the port bits to the LCD stuff
	out	PORTB, TEMP		;Lower 4 bits to LCD
	sbi	PORTD, 5		;OE bit high
	sbi	PORTD, 4		;Data/command bit high
	nop	
	nop
	nop
	nop
	nop	
	cbi	PORTD, 5		;OE low to clock data
	cbi	PORTD, 4		;Back to default 

;	ldi	TEMP, 100
;	ldi	TEMP, 200		;8 MHz
	ldi	TEMP, 255		;12 MHz
	mov	DELAY, TEMP		;50 usec   
DEL1:
	dec     DELAY
        brne    DEL1
	pop	TEMP2
	pop	TEMP
	pop	XFR
        ret

CURSOR:
;	Moves the LCD display cursor
	ori	XFR, 0x80		;Set MSB for address command
	rcall	CMD_LCD
	ret

CLEAR:
;	Clears the LCD display and restores the cursor to start - destroys XFR
	ldi	XFR, display_clear
	rcall	CMD_LCD
	rcall	WAIT2
	ret

CMD_LCD:
; Low level command sends control info to display
	push	XFR
	push	TEMP
	push	TEMP2
	mov	TEMP, XFR
	andi	XFR, 0xF0		;Mask off lower 4 bits
	swap	XFR
	in	TEMP2, PORTB
	andi	TEMP2, 0xF0		;only the output pins
	or	XFR, TEMP2		;add the port bits to the LCD stuff
	out	PORTB, XFR		;Send upper 4 bits to display (on lower 4 bits)
	cbi	PORTD, 4		;Back to CMD mode 
	sbi	PORTD, 5
	nop				;Brief delay to give reasonable OE
	nop				;pulse width
	cbi	PORTD,5         	;OE goes low to clock in data

	mov	XFR, TEMP		;Get data back
	andi	XFR, 0x0F		;Lower 4
	or	XFR, TEMP2		;add the port bits to the LCD stuff
	out	PORTB, XFR		;Write lower 4 bits to LCD
	cbi	PORTD, 4		;Back to CMD mode 
	sbi	PORTD, 5
	nop
	nop
	cbi	PORTD,5			;OE clock low

;	ldi	XFR, 100
;	ldi	XFR, 200		;8 MHz
	ldi	XFR, 255		;12 MHz
;	ldi	XFR, 100		;50 usec approx  
DEL0:
	dec	XFR
	brne	DEL0
	pop	TEMP2
	pop	TEMP
	pop	XFR
	ret

TXT:
	.DB	"*UTC SUPERCLOCK* ",0x0D
	.DB	"(C) M. Greenman.",0x00

