; NWRD Nixie Clock program for 2 B5870 tubes and PIC16F872
;
; Copyright (C) 2005 David Forbes 
;
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
;-------------------------------------------------------------
; Revision history
;
;  7/19/03 DF Got watch running. Tilt is not working - triggers
;             at nearly any angle. Going back to MSBs of ADC.
;  1/11/05 DF Copied from NWFB to NWRA. Changed disp pinouts. 
;             Removed tilt00 stuff. Left LSB A/D in case we modify it.
;  1/12/05 DF Fixed tilt to match board. 
;  1/15/05 DF Converting to NWRB code base for seconds, other improvements.
;  3/05/05 DF Converting from NWRA to NWRD board.
;  5/06/05 DF Did update seconds disp during setting,
;             tried fix blobby glow at turn-on by moving poweron later,
;             Need to improve tilt angle detection
;  5/10/05 DF Added GPL license blurb
;  5/16/05 DF Fixed tilt margins, cleaned up comments, removed dead wood
;  5/18/05 DF Fixing seconds reset to clear timer also for precise zeroing
; 
;-------------------------------------------------------------
;                      Theory of Operation
;
; This clock has two display tubes and uses them to show
; first hours, then minutes when the tilt sensor says to.
;
; The processor runs at the stately speed of 8192 instructions per second.
; This has had a big influence on the code structure, as there are no
; spare cycles to waste. Every NOP is visible in the display.
;
; The tilt sensor is a 2-axis accelerometer ADXL311JE or ADXL202JE
; used in analog mode for max speed. A/D converter is two channels
; with A3 used as both Vref and digital power out to tilt sensor.
;
; The tilt sensing algorithm works by looking for both X and Y g factors
; of approximately 0.65g in the desired direction of upwards and toward 
; the user. 
; To prevent false triggering, an additional constraint is Xtilt + ytilt. 
;
; The ADC is not very sensitive as used; the tilt values have the following 
; range (approximate).
; X is h'70' at horizontal, h'80' at vertical. Good tilt < h'75'.
; Y is h'80' at horizontal, h'90' at vertical. Good tilt > h'8B'.
;
; OffLoop waits for valid angles from tilt sensor or Set button push.
; Pressing the Adv button while off enters diagnostic tilt display mode.
;
; When the display turns on it displays hours for a second, then
; displays minutes for a second, then either shuts off or displays seconds.
;
; The display code is constructed to minimize off-time of tubes
; so that display will be as bright as possible. This means that
; all display formatting is done outside the tube-scanning loop.
;
; There is a diagnostic tilt angle display activated by pressing Adv when off.
; First X tilt is displayed, then Y tilt, then back to off. 
; The tilt angle display does a cheesy hex display by lighting two cathodes
; such as '9' and '1' for hex 'A'. Their sum is the hex value. 
; I could modify this to use BCD, but life is too short. 
; Perhaps someone else wants to do that. 
;
; Timesetting mode is entered from off loop by detecting Set button held.
; first we set 12/24 then hours then tens minutes then unit minutes then 
; the tilt angle. The Adv button advances the digit(s) being set.
;
; If the watch is tilted for 1 whole second after hrs/mins display,
; then it enters seconds display mode. It turns off either
; if it's no longer tilted right or if the seconds timeout expires.
; Tilt check occurs right after the change of the second. 
;
; The display period for each second is 2/3 second to save power.
;
; Timer is set to div/16384 to give one tick per second.
; No interrupts are used since they cause display glitches.
; Instead, time is incremented by DoTime when timer0 overflows.
;
; Hardware trick: Resistor from TiltPwr to HV sense provides
; blanking control by driving HV below 120V if TiltPwr on.
; Tilt is not needed at this time, so it's a free control bit.
;
;---------------------------------------------------------------
;
; define chip type, hardware addrs & bits
;
	list p=16f872

	include "p16f872.inc"

; 12 hour display mode parameters

FHr12	equ	1	; hour to wrap to
LHrT12	equ	1	; last hour tens
LHrU12	equ	3	; last hour units
DHrT12	equ	1	; display hour tens
DHrU12	equ	2	; display hour units

; 24 hour display mode parameters

FHr24	equ	0	; hour to wrap to
LHrT24	equ	2	; last hour tens
LHrU24	equ	4	; last hour units
DHrT24	equ	2	; display hour tens
DHrU24	equ	4	; display hour units

; Time constants for display

DigTim	equ	h'0A'	; # loops per 6 milliseconds (Less due to DoSet)
OffTim	equ	h'C0'	; # loops per 1/8 second for offloop
TiltTim	equ	h'0E'	; # of mux cycles to display tilt
HrsTim	equ	h'1A'	; # of mux cycles to display hours
PausTim	equ	h'30'	; # of mux cycles to pause btwn hrs,mins
MinTim	equ	h'20'	; # of mux cycles to display minutes
SecTim	equ	h'14'	; # of mux cycles to display seconds
BlnkTim	equ	h'0C'	; blink half-period in mux cycles
HangTim	equ	h'06'	; # of Offtims to wait after displaying
SetTOut	equ	h'40'	; time setting timeout in seconds
TDTOut	equ	h'0F'	; tilt display timeout in seconds
XTMargin	equ	h'01'	; display X tilt margin from captured X
YTMargin	equ	h'ff'	; display Y tilt margin from captured Y

; Port A bit definitions
;
; PA5	I	Adv	0=Adv button pushed
; PA4	I 	Set	0=Set button pushed
; PA3	A,O 	TPwr	Analog vref in and 1->TiltPwr, HV blank
; PA2	O	DispEn	1->DispPwr
; PA1	A 	TiltX	Analog X tilt signal
; PA0	A 	TiltY	Analog Y tilt signal

AdvButt	equ	5	; Adv button bit pressed = 0
SetButt	equ	4	; Set button bit pressed = 0
TiltY	equ	0	; Y tilt sensor analog signal
TiltX	equ	1	; X tilt sensor analog signal
;
; Write the following literals to PORTA to set power bits
;
PwrOff	equ	h'00'	; no power to nuthin'
PwrDisp	equ	h'04'	; display HV on and not blanked
PwrTilt	equ	h'08'	; Tilt sensor power on, no HV
PwrBlnk	equ	h'0C'	; display HV on but blanked
;
; Port B bit definitions
;
; PB7	O	R1	; Right digit cathode 1
; PB6	O	R0	
; PB5	O	R2	
; PB4	O	R9	
; PB3	O	R3	
; PB2	O	R8
; PB1	O	R4
; PB0	O	R5

; Port C bit definitions
;
; PC7	O	L2	; Left digit cathode 2
; PC6	O	L3	
; PC5	O	L4	
; PC4	O	L1	
; PC3	O	R6
; PC2	O	R7	
; PC1	O	L0	
; PC0	O	L5
;
; Bits in SetDig tell which digit's being set
;
NotSet	equ	0	; not in set mode
H24Set	equ	1	; setting hours
HrsSet	equ	2	; setting hours
TMinSet	equ	3	; setting tens of minutes
UMinSet	equ	4	; setting units minutes
SecSet	equ	5	; set seconds to 00 when Adv pushed
T45Set	equ	6	; setting tilt angle for 45 degrees
DoneSet	equ	7	; were setting tilt, shut off display now
;
; Bits in TDDig tell which tilt axis is displayed
;
NotTD	equ	0	; not in tilt disp mode
XTDisp	equ	1	; display X tilt
YTDisp	equ	2	; display Y tilt
DoneTD equ	3	; were displaying tilt, shut off display now
;
;  bit definitions for display status word Display
;
LBlink	equ	0	; left blink flag 1=blink
RBlink	equ	1	; right blink flag 1=blink
LBlank	equ	2	; left blank flag 1=blank
RBlank	equ	3	; right blank flag 1=blank
Hrs24	equ	7	; 12/24 hour mode: 1=24hr
;
; Define RAM storage variables
;
	cblock	h'020'
THrs			; tens of hours range 0-1
UHrs			; units hours range 0-9
TMins			; tens of minutes range 0-5
UMins			; units of minutes range 0-9
TSecs			; tens of seconds range 0-5
USecs			; units of seconds range 0-9
TMinD			; displayed tens of minutes
UMinD			; displayed units of minutes
LDigit			; Left digit BCD value to display
RDigit			; Right digit BCD value to display
SetDig			; code of digit being set
SetHist			; time that Set button has been held on
TDHist			; time that Adv button has been held on for tdisplp
TDDig			; mode of tilt display
AdvHist			; time that Adv button has been held on
TltHist			; time that tilt has been correct
Display			; display status bits per above
DispTim			; time left to display this bit
XTilt			; tilt of X axis from tilt sensor
YTilt			; tilt of Y axis from tilt sensor
DTilt			; Y tilt - X tilt from tilt sensor
XTLim45			; X tilt limit for 45 degree display
YTLim45			; Y tilt limit for 45 degree display
DTLim45			; Y-X tilt limit for 45 degree display
TimLeft			; delay routine working storage
LBData			; Left digit B port cathode bits
LCData			; Left digit C port cathode bits
RBData			; Right digit B port cathode bits
RCData			; Right digit C port cathode bits
SetTim			; timeout countdown for time setting mode (secs)
TDTim			; timeout countdown for tilt display mode (secs)
FirstHr			; first hour 0 or 1
LastHrT			; last tens hour 1 or 2
LastHrU			; last units hour 3 or 4
DispHrT			; display tens hour 1 or 2
DispHrU			; display units hour 3 or 4
SecCnt			; number of seconds left to display

	endc
;
; Upon reset, init ports and timer
;
	org	h'000'
	nop			; for debugger
	goto	Start
;
; Load display digits with various time digits
; These are called by all display updating functions
;
LdSecs	movfw	TSecs		; copy current secs into disp digits
	movwf	LDigit
	movfw	USecs
	movwf	RDigit
	call	LdDigs
	return

LdMins	movfw	TMins		; copy current mins into disp digits
	movwf	LDigit
	movfw	UMins
	movwf	RDigit
	call	LdDigs
	return

LdMinD	movfw	TMinD
	movwf	LDigit
	movfw	UMinD		; copy saved minutes into L&R digits
	movwf	RDigit
	call	LdDigs
	return

LdHrs	movfw	THrs
	movwf	LDigit
	movfw	UHrs
	movwf	RDigit		; copy current time into local storage 
	call	LdDigs
	return
;
; Load port B and C digit storage with cathode codes
; Reads LDigit and RDigit
; puts results in LBData..RCData
;
; This is done here to increase brightness by reducing workload
; of DispLp, which used to do all this stuff every iteration.
;
LdDigs	movlw	h'07'		; prepare for left digit display
	andwf	LDigit,f	; fix range of number
	call	BLeft
	movwf	LBData		; save B and C cathode codes
	call	CLeft		; using bizarre PIC table lookup subroutines
	movwf	LCData
	movlw	h'0F'		; prepare for right digit display
	andwf	RDigit,f	; fix range of number
	call	BRight
	movwf	RBData		; Save B and C cathode codes
	call	CRight
	movwf	RCData
	return
;
; The following tables are located here to ensure no wrap on PCL
;
; Lookup routines to set ports B and C to Nixie cathode
; per LDigit and RDigit
;
; Port B left tube
;
BLeft	movfw	LDigit
	addwf	PCL,f
;		  RRRRRRRR
;		  10293845
	retlw	B'00000000'		; left 0
	retlw	B'00000000'		; left 1
	retlw	B'00000000'		; left 2
	retlw	B'00000000'		; left 3
	retlw	B'00000000'		; left 4
	retlw	B'00000000'		; left 5
	retlw	B'00000000'		; left 6 displays 1,5
	retlw	B'00000000'		; left 7 displays 2,5
;
; Port C left tube
;
CLeft	movfw	LDigit
	addwf	PCL,f
;		  LLLLRRLL
;		  23416705
	retlw	B'00000010'		; left 0
	retlw	B'00010000'		; left 1
	retlw	B'10000000'		; left 2
	retlw	B'01000000'		; left 3
	retlw	B'00100000'		; left 4
	retlw	B'00000001'		; left 5
	retlw	B'00010001'		; left 6 displays 1,5
	retlw	B'10000001'		; left 7 displays 2,5
;
; Port B right tube
;
BRight	movfw	RDigit
	addwf	PCL,f
;		  RRRRRRRR
;		  10293845
	retlw	B'01000000'		; right 0
	retlw	B'10000000'		; right 1
	retlw	B'00100000'		; right 2
	retlw	B'00001000'		; right 3
	retlw	B'00000010'		; right 4
	retlw	B'00000001'		; right 5
	retlw	B'00000000'		; right 6
	retlw	B'00000000'		; right 7
	retlw	B'00000100'		; right 8
	retlw	B'00010000'		; right 9
	retlw	B'11000000'		; right 10 displays 1,0
	retlw	B'00110000'		; right 11 displays 2,9
	retlw	B'10100000'		; right 12 displays 1,2
	retlw	B'10001000'		; right 13 displays 1,3
	retlw	B'10000010'		; right 14 displays 1,4
	retlw	B'10000001'		; right 15 displays 1,5
;
; Port C right tube
;
CRight	movfw	RDigit
	addwf	PCL,f
;		  LLLLRRLL
;		  23416705
	retlw	B'00000000'		; right 0
	retlw	B'00000000'		; right 1
	retlw	B'00000000'		; right 2
	retlw	B'00000000'		; right 3
	retlw	B'00000000'		; right 4
	retlw	B'00000000'		; right 5
	retlw	B'00001000'		; right 6
	retlw	B'00000100'		; right 7
	retlw	B'00000000'		; right 8
	retlw	B'00000000'		; right 9
	retlw	B'00000000'		; right 10 displays 1,0
	retlw	B'00000000'		; right 11 displays 2,9
	retlw	B'00000000'		; right 12 displays 1,2
	retlw	B'00000000'		; right 13 displays 1,3
	retlw	B'00000000'		; right 14 displays 1,4
	retlw	B'00000000'		; right 15 displays 1,5
;
; Execution starts here after reset
;
Start	nop
	bcf	STATUS,RP0	; point to low bank
	bcf	STATUS,RP1
	clrf	PORTA		; turn off power
	clrf	PORTB		; Clear nixie ports
	clrf	PORTC
	movlw	B'11000000'	; osc=rc, chan0, A/D disabled
	movwf	ADCON0
; hi RAM
	bsf	STATUS,RP0	; point to high bank
	movlw	B'10000100'	; TMR0 prescaler, 1:32
	movwf	OPTION_REG
	movlw	B'00110011'	; PORTA bits 2,3 output
	movwf	TRISA
	clrf	TRISB		; PORTB all outputs
	clrf	TRISC		; PORTC all outputs
	movlw	B'00000101'	; Left justify, 2 analog channel
;msb	movlw	B'10000101'	; Right justify, 2 analog channel
	movwf	ADCON1		; AN3 and VSS references
; low RAM
	bcf	STATUS,RP0	; point to low bank
	clrf	TMinD
	clrf	UMinD
	clrf	TMins
	clrf	UMins		; set mins,secs to :00:00
	clrf	TSecs
	clrf	USecs
	clrf	LDigit
	clrf	RDigit
	clrf	LBData
	clrf	LCData
	clrf	RBData
	clrf	RCData
	clrf	AdvHist
	clrf	SetHist
	clrf	TltHist
	clrf	Display
	clrf	DispTim
	clrf	XTilt
	clrf	YTilt
	movlw	h'74'	;h'64'		; close to -0.65g
	movwf	XTLim45
	movlw	h'8C'	;h'64'		; close to -0.65g
	movwf	YTLim45
	movlw	h'18'	;h'C0'		; Y - X
	movwf	DTLim45
	call	SetHr12		; set to 12 hour mode
	clrf	THrs
	movfw	FirstHr		; Set the hours to 00:00 or 1:00
	movwf	UHrs
	clrf	SetTim
	clrf	SetDig
	bsf	SetDig,NotSet	; flag no setting going on
	clrf	TDTim
	clrf	TDDig
	bsf	TDDig,NotTD	; turn off tilt display mode
;
; OffLoop follows tilt sequence, incs time, uses little power.
;
OffLoop	call	WaitOff		; wait for 1/8 of second
	call	DoTime		; update time variables
	call	DoSet		; check for set button
	btfss	SetDig,NotSet
	goto	SetLoop		; Got to timesetting loop if setting
	call	DoTD		; check for Adv button
	btfss	TDDig,NotTD
	goto	TDLoop		; goto tilt disp loop if Adv was pressed
	call	GetTilt		; read sensor
	incf	TltHist,f	; assume tilt is good
	call	ChkT45		; See if tilt is 45 yet
	skpc			; good tilt
	clrf	TltHist		; bad tilt, start over
	movlw	2		; test TltHist is 2 for good tilt
	subwf	TltHist,w
	skpz
	goto	OffLoop		; not good, do it all again
;
; Tilt sequence correct - turn on watch to show time
;
	incf	TltHist,f	; force another tilt to display again
	call	LdHrs		; get hours into display bytes
	movfw	TMins
	movwf	TMinD		; save current minutes 
	movfw	UMins		; so it won't show old hrs but new mins
	movwf	UMinD
	bcf	Display,LBlank	; disp both digits for now
	bcf	Display,RBlank
NoHBlnk	movlw	HrsTim
	movwf	DispTim		; set time to display hours
	movlw	PwrBlnk		; turn on HV but blank display
	movwf	PORTA
	call	DispLpN		; display the hours
	movlw	PausTim
	movwf	DispTim
HrMinLp	call	WtDigit		; pause between hrs, mins
	decfsz	DispTim,f
	goto	HrMinLp
	call	LdMinD		; set display to saved minutes
	movlw	MinTim
	movwf	DispTim		; set time to disp minutes
	call	DispLpN		; display the minutes
	movlw	PausTim
	movwf	DispTim
MinScLp	call	WtDigit		; pause between mins, secs
	decfsz	DispTim,f
	goto	MinScLp
	movlw	SecTim
	movwf	SecCnt		; set timeout for seconds display
;
; loop for seconds display till duty cycle reaches zero
;
SecLp	btfss	INTCON,T0IF	; test timer overflow
	goto	SecLp		; loop till set (top of second)
	call	DoTime		; update time variables
	call	GetTilt		; read sensor
	call	ChkT45		; See if tilt is still good
	skpc			; good tilt, do another second
	goto	PwrDown		; bad tilt, exit seconds display
	call	LdSecs		; update seconds value to display
	movfw	SecCnt		; duty cycle is remaining loop index
	movwf	DispTim		; set time to display them
	call	DispLpN		; display the seconds
	decfsz	SecCnt,f	; count down seconds display timeout
	goto	SecLp		; still active, do another second
;
; Finish up time display
;
PwrDown	clrf	PORTB		; shut off nixies
	clrf	PORTC
	clrf	PORTA		; turn off power
	movlw	HangTim
	movwf	DispTim		; set up holdoff timer
HangLp	call	WaitOff
	call	DoTime
	decfsz	DispTim,f	; kill time between displayings
	goto	HangLp
	goto	OffLoop		; ready to display again
;
; TDispLp - diagnostic display of tilt values
;
; TDispLp displays X or Y tilt in hexadecimal on the nixies
; continuously for test purposes. 
; It reads the tilt 2 times a second and updates the display at this 
; rate. Power is shut down for tilt reading, causes display to wink.
;
; TDispLp is entered from OffLp when Adv button is pushed,
; causing TiltDig to be advanced to XTDisp. Adv steps through modes:
; off -> XTilt -> YTilt -> Done
;
TDLoop
	call	DoTime		; keep timekeeping up to date
	call	GetTilt		; read tilt sensor
	movlw	PwrBlnk		; turn on HV but blank display
	movwf	PORTA
	movfw	XTilt
	btfsc	TDDig,YTDisp	; use Y instead of X when in Y mode
	movfw	YTilt
	movwf	LDigit		; MSBs into left digit
	swapf	LDigit,f	; move MSBs to LSBs of left digit
	movwf	RDigit		; LSBs to right digit
	call	LdDigs		; write digits to tubes
	bcf	Display,LBlank	; disp both digits
	bcf	Display,RBlank
	movlw	TiltTim		; set time to display it
	movwf	DispTim
	call	DispLpN		; display tilt but don't check buttons
	movlw	PwrOff		; turn off HV
	movwf	PORTA
	call	DoTD		; process Adv button for tilt modes
	btfss	TDDig,NotTD	; exit this loop if out of tdisp mode
	goto	TDLoop		; pushed, so do it again
	goto	OffLoop		; else go back to being a clock
;
; DoTD detects if the Adv button is pushed, and debounces.
; If it's time for action, it sequences through the tilt display modes.
;
DoTD	btfss	PORTA,AdvButt	; read the button - 0 is pushed
	goto	IsTD
	clrf	TDHist		; button not pressed - clear history
	tstf	TDTim		; timed out?
	skpnz
	goto	DoneT		; yes, turn off tilt display mode
	goto	NoTD
IsTD	incf	TDHist,f	; it is pushed - bump history counter
	movlw	4
	subwf	TDHist,w
	skpnz			; make hist stick at 4 to prevent autorepeat
	decf	TDHist,f
GotTD	movlw	1
	subwf	TDHist,w	; debounce time is 1 loop 
	skpz
	goto	NoTD
	movlw	TDTOut		; load tilt display timeout counter
	movwf	TDTim
	clrc
	rlf	TDDig,f		; bump to next setting mode
	btfss	TDDig,DoneTD	; are we done tilt displaying?
	goto	NoTD
DoneT	clrf	TDDig		; turn off tilt display mode
	bsf	TDDig,NotTD
	clrf	TDTim		; clear timeout counter
NoTD	return
;
; --------------------------------------------------------
;
; Time setting code
;
; Setting procedure:
;
; Hold Set button till display lights, then release.
; Display is unblinking '12' or '24', indicating hours mode.
; Press Adv button to toggle between 12 hour and 24 hour modes.
; Press Set button and release.
; Both digits of Hours will flash.
; Press Adv button once per increment to set hours.
; Press Set button and release.
; Tens of Minutes will flash, units will be solid.
; Press Adv button once per increment to set tens minutes.
; Press Set button and release.
; Units of Minutes will flash, tens will be solid.
; Press Adv button once per increment to set units minutes.
; Press Set button and release.
; Display is incrementing seconds. 
; Press Adv button once to reset seconds to 00.
; Press Set button and release.
; Display is unblinking '45'. 
; Hold watch at best viewing angle, typically 45 degrees.
; Press Adv button to save tilt angle. Display will flash once.
; Press Set button and release.
; Display will blank. Setting is done.
;
; Set & Adv buttons are sampled once per mux iteration.
; Each button must be pressed then released for each action.

; SetLoop is entered from OffLp when Set button is pushed,
; causing SetDig to be advanced to HrsSet.
;
; The setting state machine keeps its state in SetDig.
; SetHist is a count of how long the Set button has been held down.
; When the buton has been hjeld down long enough to debounce, then
; the state machine is advanced to the next state and the necessary
; variables are updated to make the display show the right stuff for
; that mode. Similarly, the Adv button's time pressed is AdvHist. 
; When Adv is pressed, the appropriate vairable is incremented and
; the display variables are updated. 
; 
; Display time with blinking digits, read buttons and act on them.
;
SetLoop	movlw	BlnkTim		; set display duration
	movwf	DispTim
	bcf	Display,LBlank	; Enable display for on phase of blink
	bcf	Display,RBlank
	call	DispLp		; Do display, read buttons
	movlw	BlnkTim
	movwf	DispTim		; set duration
	btfsc	Display,LBlink	; set L blanking flag per blink request
	bsf	Display,LBlank
	btfsc	Display,RBlink	; set R blanking flag per blink request
	bsf	Display,RBlank
	call	DispLp		; do display, read buttons
	btfss	SetDig,NotSet
	goto	SetLoop		; loop if still setting
	clrf	PORTB		; shut off nixies
	clrf	PORTC
	clrf	PORTA		; turn off power
	goto	OffLoop
;
; DoSet detects if the Set button is pushed, and debounces.
; If it's time for action, it selects the digit being set in the order:
;
; This routine sets up the display mode to match the set function.
;
DoSet	btfss	PORTA,SetButt	; read the button - 0 is pushed
	goto	IsSet
	clrf	SetHist		; button not pressed - clear history
	tstf	SetTim		; timed out?
	skpnz
	goto	DoneS		; yes, turn off timesetting mode
	goto	NoSet
IsSet	incf	SetHist,f	; it is pushed - bump history counter
	movlw	4
	subwf	SetHist,w
	skpnz			; make hist stick at 4 to prevent autorepeat
	decf	SetHist,f
GotSet	movlw	2
	subwf	SetHist,w	; debounce time is 2 loops ~=40 millisec
	skpz
	goto	NoSet
	movlw	SetTOut		; load time setting timeout counter
	movwf	SetTim
	clrc
	rlf	SetDig,f	; bump to next setting mode
	btfsc	SetDig,H24Set	; is it 12/24 hour mode?
	goto	H24S
	btfsc	SetDig,HrsSet	; is it hours?
	goto	HrsS
	btfsc	SetDig,TMinSet	; is it tens of minutes?
	goto	TMinS
	btfsc	SetDig,UMinSet	; is it units of minutes?
	goto	UMinS
	btfsc	SetDig,SecSet	; is it seconds?
	goto	SecS
	btfsc	SetDig,T45Set	; is it tilt?
	goto	Tilt45S
	btfsc	SetDig,DoneSet	; are we done setting?
	goto	DoneS
;
; Enter H24 setting mode
;
; This routine turns on the display when timesetting mode is entered.
;
H24S	movlw	PwrBlnk		; turn on HV but blank display	
	movwf	PORTA
	nop
	nop
	movfw	DispHrT		; get mode display value
	movwf	LDigit		; into display digits
	movfw	DispHrU
	movwf	RDigit
	call	LdDigs		; load it into disp storage
	bcf	Display,LBlink	; no blinking
	bcf	Display,RBlink
	goto	NoSet
;
; Enter Hours setting mode
;
HrsS	call	LdHrs		; copy current hours into disp digits
	bsf	Display,LBlink	; blink both digits
	bsf	Display,RBlink
	goto	NoSet
;
; Enter TMins setting mode
;
TMinS	call	LdMins		; copy current mins into disp digits
	bsf	Display,LBlink	; blink only left digit
	bcf	Display,RBlink
	goto	NoSet
;
; Enter UMins setting mode
;
UMinS	call	LdMins		; load minutes into disp storage
	bcf	Display,LBlink	; blink only right digit
	bsf	Display,RBlink
	goto	NoSet
;
; Enter Secs setting mode
;
SecS	call	LdSecs		; load seconds into disp storage
	bcf	Display,LBlink	; No blink, but display advances
	bcf	Display,RBlink	; via code at TimeRet.
	goto	NoSet
;
; Enter Tilt45 setting mode
;
Tilt45S	movlw	h'4'		; get a '45' for display
	movwf	LDigit
	movlw	h'5'
	movwf	RDigit
	call	LdDigs		; load it into disp storage
	bcf	Display,LBlink	; no blinking
	bcf	Display,RBlink
	goto	NoSet
;
; Finish up when exiting set mode
;
DoneS	clrf	SetDig		; leave setting mode
	bsf	SetDig,NotSet
	clrf	SetTim		; clear timeout counter
	clrf	PORTB
	clrf	PORTC
	clrf	PORTA		; Shut off display
NoSet	return
;
; DoAdv processes the Adv button. If it's pushed, we debounce
; via AdvHist. When it's time to advance the clock setting,
; the appropriate digit(s) are incremented without carrying into other
; digits. 
;
DoAdv	btfss	PORTA,AdvButt	; read the button - 0 is pushed
	goto	IsAdv
	clrf	AdvHist		; button not pressed - clear history
	goto	AdvDone
IsAdv	incf	AdvHist,f	; it is pushed - bump history counter
	movlw	4
	subwf	AdvHist,w
	skpnz			; make hist stick at 4 to prevent autorepeat
	decf	AdvHist,f
GotAdv	movlw	2
	subwf	AdvHist,w	; debounce time is 2 loops ~=40 millisec
	skpz
	goto	AdvDone
	btfsc	SetDig,H24Set	; is it 12/24 hour mode?
	goto	H24A
	btfsc	SetDig,HrsSet	; is it hours?
	goto	HrsA
	btfsc	SetDig,TMinSet	; is it Tenmins?
	goto	TMinA
	btfsc	SetDig,UMinSet	; is it UnitsMins?
	goto	UMinA
	btfsc	SetDig,SecSet	; is it seconds?
	goto	SecA
	btfsc	SetDig,T45Set	; is it Tilt at 45?
	goto	Tilt45A
AdvDone	return			; Nothing to do so return
;
; toggle 12/24 hour mode
;
H24A	btfss	Display,Hrs24	; In 24 hour mode?
	goto	H24No
	call	SetHr12		; yes, go from 24 to 12 hour mode
	goto	H24Got
H24No	call	SetHr24		; else go from 12 to 24 hour mode
H24Got	movfw	DispHrT		; get mode display value
	movwf	LDigit		; into display digits
	movfw	DispHrU
	movwf	RDigit
	call	LdDigs		; load it into disp storage
	goto	AdvDone
;
; Set timekeeping to 12 hour mode
;
SetHr12	movlw	FHr12		; Set up for 12 hour mode
	movwf	FirstHr
	movlw	LHrT12
	movwf	LastHrT
	movlw	LHrU12
	movwf	LastHrU
	movlw	DHrT12
	movwf	DispHrT
	movlw	DHrU12
	movwf	DispHrU
	bcf	Display,Hrs24
	return
;
; Set timekeeping to 24 hour mode
;
SetHr24	movlw	FHr24		; Set up for 24 hour mode
	movwf	FirstHr
	movlw	LHrT24
	movwf	LastHrT
	movlw	LHrU24
	movwf	LastHrU
	movlw	DHrT24
	movwf	DispHrT
	movlw	DHrU24
	movwf	DispHrU
	bsf	Display,Hrs24
	return
;
; Advance hours
;
HrsA	call	IncHrs		; increment hours (messy)
	call	LdHrs		; copy new hours into disp digits
	goto	AdvDone
;
; Advance tens of minutes
;
TMinA	incf	TMins,f		; increment tens of minutes
	movlw	6
	subwf	TMins,w		; did it hit 6?
	skpnz	
	clrf	TMins		; yes, wrap from 5 to 0
	call	LdMins		; copy new minutes into disp digits
	goto	AdvDone
;
; Advance units minutes
;
UMinA	incf	UMins,f		; increment units of minutes
	movlw	h'A'
	subwf	UMins,w		; did it hit 10?
	skpnz	
	clrf	UMins		; yes, wrap from 10 to 0
	call	LdMins		; copy new minutes into disp digits
	goto	AdvDone
;
; Reset seconds
;
SecA	clrf	TSecs		; Set seconds to 0
	clrf	USecs
	clrf	TMR0		; set sub-seconds to 0
	call	LdSecs		; copy new seconds into disp digits
	goto	AdvDone
;
; Capture and save 45 deg tilt angle
;
Tilt45A	movlw	PwrOff
	movwf	PORTA		; cut off power now for stability
	call	WaitOff		; pause to flash display
	call	GetTilt		; find out current tilt angle
	movlw	XTMargin	; Get X tilt + XTMargin
	addwf	XTilt,w
	movwf	XTLim45		; save as X limit
	movlw	YTMargin	; Get Y tilt + YTMargin
	addwf	YTilt,w
	movwf	YTLim45		; save as Y limit
	movfw	XTilt		; Get Y tilt - X tilt
	subwf	YTilt,w
	movwf	DTLim45		; save as D limit
	decf	DTLim45,f	; make it Y - X - 1
	goto	AdvDone
;
; IncHrs increments the hours which is an ugly process
; (but it only happens once an hour, so what, me worry?)
;
IncHrs	incf	UHrs,f		; bump units hours
	movfw	LastHrT
	subwf	THrs,w		; Tens at last hour?
	skpz
	goto	NoHWrap		; no, do carry
	movfw	LastHrU
	subwf	UHrs,w		; Units at last hour?
	skpz
	goto	NoHWrap		; no, do carry
	movfw	FirstHr
	movwf	UHrs		; yes, reset hours to 1 or 0 o'clock
	clrw
	movwf	THrs
NoHWrap	movlw	h'A'
	subwf	UHrs,w		; did it hit 10?
	skpz
	goto	NoHrC	
	clrf	UHrs		; yes, wrap from 10 to 0
	incf	THrs,f		; and bump tens
NoHrC	return
;
;--------------------------------------------------------
; Display loop
;
; DispLp displays the cathodes stored in [L,R][B,C]Data.
; It uses the blanking bits LBlank and RBlank per digit.
; It runs for DispTim then returns.
; Also calls Set and Adv button routines for timesetting mode.
;
; This routine does multiplexing by lighting first left digit then right.
;
; Entered and returns with display dark via TiltEn bit of Port A
; Also clears Ports B and C to ensure blankness
;
DispLp	call	DoTime		; update time variables
	movfw	LBData
	movwf	PORTB		; Set ports B and C to left cathode codes
	movfw	LCData
	movwf	PORTC
	movlw	PwrDisp
	btfss	Display,LBlank	; blank this digit?
	movwf	PORTA		; not blanked so let HV go high
	call	WtDigit		; Wait for it to display
	call	DoSet		; process the Set button while lit
	movlw	PwrBlnk
	movwf	PORTA		; blank display while changing cathodes
	movfw	RBData
	movwf	PORTB		; Set ports B and C to right cathode codes
	movfw	RCData
	movwf	PORTC
	movlw	PwrDisp
	btfss	Display,RBlank	; blank this digit?
	movwf	PORTA		; not blanked so let HV go high
	call	WtDigit		; Wait for it to display
	call	DoAdv		; process the Adv button while other digit lit
	movlw	PwrBlnk
	movwf	PORTA		; blank via HV control from TiltPwr
	decfsz	DispTim,f
	goto	DispLp		; do it till count expires
	return
;
; This version doesn't call DoSet or DoAdv. It's used for normal display.
;
DispLpN	call	DoTime		; update time variables
	movfw	LBData
	movwf	PORTB		; Set ports B and C to left cathode codes
	movfw	LCData
	movwf	PORTC
	movlw	PwrDisp
	btfss	Display,LBlank	; blank this digit?
	movwf	PORTA		; not blanked so let HV go high
	call	WtDigit		; Wait for it to display
	movlw	PwrBlnk
	movwf	PORTA		; blank display while changing cathodes
	movfw	RBData
	movwf	PORTB		; Set ports B and C to right cathode codes
	movfw	RCData
	movwf	PORTC
	movlw	PwrDisp
	btfss	Display,RBlank	; blank this digit?
	movwf	PORTA		; not blanked so let HV go high
	call	WtDigit		; Wait for it to display
	movlw	PwrBlnk
	movwf	PORTA		; blank via HV control from TiltPwr
	decfsz	DispTim,f
	goto	DispLpN		; do it till count expires
	return
;
; WtDigit delays for one digit time - about six milliseconds
;
; Destroys W
;
WtDigit	movlw	DigTim		; get constant delay number
	movwf	TimLeft		; copy to local storage
WtLoop1	decfsz	TimLeft,f	
	goto	WtLoop1		; loop that many times
	return
;
; WaitOff delays for about 1/8 second while power is off
;
; Destroys W
;
WaitOff	movlw	OffTim		; get constant delay number
	movwf	TimLeft		; copy to local storage
WtOff1	decfsz	TimLeft,f	
	goto	WtOff1		; loop that many times
	return
;
; DoTime checks timer, updates time vars accordingly
;
; The timer is set up to overflow once per second. This routine is called
; several times a second to poll the timer status to detect an overflow.
; If hte timer overflows, the overflow bit is reset and the time variables
; are incremented according to standard clock behavior. 
;
; The reason that interrupts are not used is that the display flickers
; when the interrupt occurrs. Been there, done that. 
;
DoTime	btfss	INTCON,T0IF	; test timer overflow
	goto	TimeNop		; no overflow, do nothing (equalize time)
	bcf	INTCON,T0IF	; reset overflow flag
	tstf	SetTim		; Setting mode?
	skpz
	decf	SetTim,f	; Yes, count down setting time left
	tstf	TDTim		; Tilt display mode?
	skpz
	decf	TDTim,f		; Yes, count down display time left
	incf	USecs,f		; bump units seconds
	movlw	h'A'		; ten second overflow?
	subwf	USecs,w
	bnz	TimeRet		; no, return
	clrf	USecs
	incf	TSecs,f		; bump tens seconds
	movlw	h'6'		; Minute overflow?
	subwf	TSecs,w
	bnz	TimeRet		; no, return
	clrf	TSecs
	incf	UMins,f		; bump units minutes
	movlw	h'A'		; ten minute overflow?
	subwf	UMins,w
	bnz	TimeRet		; no, return
	clrf	UMins
	incf	TMins,f		; bump tens minutes
	movlw	h'6'		; hourly overflow?
	subwf	TMins,w
	bnz	TimeRet		; no, return
	clrf	TMins
	call	IncHrs		; bump the hour by one
TimeNop	nop
	nop			; equalize timing of routine to
	nop			; prevent display glitch once per second
	nop
	nop
	nop
	nop
	nop
	nop
	nop
TimeRet	btfsc	SetDig,SecSet	; If we're setting seconds, update display
	goto	LdSecs
	return
;
; Read the tilt sensor. 
; Results are placed in XTilt and YTilt.
;
; Disp, Tilt power is turned off by this routine.
;
; Sensor needs 2 milliseconds from TiltPwr on to first A/D conversion.
; Code is interleaved to minimize power-on time of tilt sensor,
; which uses 300 uA. This saves battery drain.
;
; The remnants are from an attempt to use higher resolution on the ADC.
; It needed all 10 bits, so I didn't bother. Seems to work as is.
;
GetTilt	movlw	PwrTilt		; turn on tilt sensor power
	movwf	PORTA
	bsf	ADCON0,ADON	; turn on the A/D converter module
	bcf	ADCON0,CHS0	; select A/D channel 0 = X
	nop
	nop			; wait one millisecond
	nop
	nop
	nop
	nop
	nop
	nop
	bsf	ADCON0,GO	; Start A/D conversion at 2 milliseconds
	nop			; Wait for X conversion to complete
	bsf	ADCON0,CHS0	; select A/D channel 1 = Y
	movfw	ADRESH		; get X tilt from A/D (use 8 MSBs)
;msb	bsf	STATUS,RP0	; point to high bank
;msb	movfw	ADRESL		; get X tilt from A/D (use 8 LSBs)
;msb	bcf	STATUS,RP0	; point to low bank
	movwf	XTilt		; save X tilt
	bsf	ADCON0,GO	; Start second A/D conversion
	nop			; Wait for Y conversion to complete
	nop			; Wait for Y conversion to complete
	movfw	ADRESH		; get Y tilt
;msb	bsf	STATUS,RP0	; point to high bank
;msb	movfw	ADRESL		; get Y tilt
;msb	bcf	STATUS,RP0	; point to low bank
	movwf	YTilt		; save Y tilt
	movlw	PwrOff		; turn off tilt sensor power
	movwf	PORTA
	bcf	ADCON0,ADON	; turn off the A/D converter module
	return
;
; ChkT45   See if tilted to 45 degrees
;
; Returns NC if not tilted right,
; returns C if tilted right.
;
; We want:
; (YTilt - XTilt) > DTLim45 (h'18')
;  XTilt          < XTLim45 (h'74' ~-0.65g)
;  YTilt          > YTLim45 (h'8C' ~+0.65g)
;
ChkT45	movfw	XTilt
	subwf	YTilt,w
	movwf	DTilt		; calc YTilt - XTilt -> DTilt
	movfw	DTLim45
	subwf	DTilt,w		; D tilt minus D lim, pos is good tilt
	skpc			; NC is bad
	goto	CT45Don		; bad Y-X tilt
	movfw	XTilt
	subwf	XTLim45,w	; X lim minus X tilt, pos is good tilt
	skpc			; NC is bad
	goto	CT45Don		; bad X tilt
	movfw	YTLim45
	subwf	YTilt,w		; Y tilt minus Y lim, pos is good tilt
CT45Don	return			; NC is bad, C is good

	end			; end of file


