	list      p=16f627a
	#include  p16f627a.inc

	; Set config bits 
;	__CONFIG _INTRC_OSC_NOCLKOUT & _PWRTE_OFF & _WDT_OFF & _CP_OFF & _MCLRE_OFF & _LVP_OFF   
;	__CONFIG _INTOSC_OSC_NOCLKOUT & _PWRTE_OFF & _WDT_OFF & _CP_OFF & _MCLRE_OFF & _LVP_OFF   
	__CONFIG _XT_OSC & _PWRTE_OFF & _WDT_OFF & _CP_OFF & _MCLRE_OFF & _LVP_OFF   

; Declare the variables.
	cblock	0x20
	debug		; TODO: Used only for debugging
	debugh		; TODO: Used only for debugging
	dark		; Time until the display is blanked.
			; (Blanking the display saves battery life.)
	row		; Row to refresh on display
	ticks		; Ticks this second (0-124)
	seconds		; Seconds this minute (0-59)
	minutes		; Minutes this hour (0-59)
	hours		; Hours this day (0-23)
	days		; Day of month (1-31)
	months		; Month of year (1-12)
	mode		; Current operatin mode
			; 0 - displaying time
			; 1 - setting minutes
			; 2 - setting hours
			; 3 - setting days
			; 4 - setting months
; Start in mode 4.  Pressing the button decrements the mode.
; Pressing the button generally decrements the mode, 
	bouncing	; Non-zero during debounce intervals
	savew		; Saves W during interrupts
	saves		; Saves STATUS during interrupts
	timeval		; Value for the timer register
	ticksec		; Ticks per second (after row division)
	endc
	
	org	0x0000  ;Reset vector
	goto	main		; Jump to main code defined in Example.asm
	nop			; Pad out so interrupt
	nop			;  service routine gets
	nop			;    put at address 0x0004.
	goto	service 	; Points to interrupt service routine
	org       0x0020	; Begin program
main			; Main code entry -- once only initialization
;OPTION
; Clear RBPU
; Clear TOCS
; Clear PSA
; Set prescaler = 111 (divide by 256)
	clrwdt
	bcf	STATUS, RP1 ; Select BANK0 or BANK1
	bsf	STATUS, RP0 ; Select BANK1
	movlw	(1<<PS2) | (1<<PS0)
	errorlevel -302
	movwf	OPTION_REG

;INTCON
; Set GIE
; Set T0IE
	bcf	STATUS, RP0 ; Select BANK0
	movlw	(1<<GIE) | (1<<T0IE)
	movwf	INTCON

; Clear PIE1
	bsf	STATUS, RP0 ; Select BANK1
	clrf	PIE1

;PCON
; Set OSCF (4Mhz) (default)
	bsf	STATUS, RP0 ; Select BANK1
	movlw	(1<<OSCF)
	movwf	PCON

;Initialize PORTA as a 4-bit output register
; NOTE: RA5 is input only (watch stem?)
	bcf	STATUS, RP0 ; Select BANK0
	clrf	PORTA
	movlw	0x07	; Disable comparator
	movwf	CMCON
	bsf	STATUS, RP0 ; Select BANK1
	movlw	0x20	; Set PORTA low bits for output
	movwf	TRISA

; Initialize PORTB as a 8-bit output register
	bcf	STATUS, RP0 ; Select BANK0
	clrf	PORTB
	bsf	STATUS, RP0 ; Select BANK1
	clrf	TRISB	; Set PORTB all bits for output
	bcf	STATUS, RP0 ; Select BANK0

; Initialize the time.
	clrf	row
	clrf	ticks
	clrf	seconds
	clrf	minutes
	clrf	hours
	clrf	days
	clrf	months
; Initialize other locals.
	clrf	bouncing ; Non-zero during debounce intervals
	movlw	d'60'	; Set time until the display is blanked.
	movwf	dark

;
; Get 'ticks per second' from configuration flash, to allow use of 
; parts whose internal oscillators aren't exactly 4Mhz.
	bsf	STATUS, RP0 ; Select BANK1
	clrf	EEADR
	bsf	EECON1, RD
	movfw	EEDATA
	bcf	STATUS, RP0 ; Select BANK0
	movwf	timeval
	bsf	STATUS, RP0 ; Select BANK1
	incf	EEADR, F
	bsf	EECON1, RD
	movfw	EEDATA
	bcf	STATUS, RP0 ; Select BANK0
	movwf	ticksec

; Initialize the timer
	bcf	STATUS, RP0 ; Select BANK0
	movfw	timeval	; Initialize the timer
	movwf	TMR0

; Start in Mode 4 -- set the month
; The modes are:
; Mode 0 -- Keep time
; Mode 1 -- advance minutes
; Mode 2 -- advance hours
; Mode 3 -- advance days
; Mode 4 -- advance months
	movlw	0x4
	movwf	mode

	errorlevel +302
; The main loop implements 'stem' handling.
loop
	; Must be BANK0!
	btfss	PORTA, 5 ; Check the button
	goto	loop

; Someone pressed the button!
	bsf	bouncing, 1 ; Set the debounce flag

;
; If the display is dark, just light it.
; TODO: Worry about the race condition where the display goes dark 
; just as we decide it isn't.
	movfw	dark
	addlw	0x0
	btfss	STATUS, Z
	goto	newmode	; Wasn't dark change mode instead
	bsf	STATUS, RP0 ; Select BANK1
	errorlevel -302
	clrf	TRISB	; Set PORTB all bits for output
	movlw	0x20	; Set PORTA low bits for output
	movwf	TRISA
	bcf	STATUS, RP0 ; Select BANK0
	errorlevel -302
	movlw	d'60'	; Light up for one minute
	movwf	dark
	goto	debounce1 ; Go wait for the debounce interval
;
; Change to the new mode
newmode
	movfw	mode	; Get current mode
	addlw	-0x0	; Time to wrap?
	btfsc	STATUS, Z
	movlw	0x3	; Yes, set up to get mode 2
	addlw	-0x1	; Otherwise, just decrement
	movwf	mode	; Store new mode

; Wait for the next interrupt to clear the "bouncing" flag.
debounce1
	btfsc	bouncing, 1
	goto	debounce1
	bsf	bouncing, 1 ; Set the debounce flag again
debounce1b
	btfsc	bouncing, 1
	goto	debounce1b
; Wait further for the user to release the button.
waituser
	btfsc	PORTA, 5 ; Check the button
	goto	waituser
; Now wait for another debounce interval.
	bsf	bouncing, 1
debounce2
	btfsc	bouncing, 1
	goto	debounce2
	bsf	bouncing, 1 ; Set the debounce flag again
debounce2b
	btfsc	bouncing, 1
	goto	debounce2b
; Go kill time until next time the button is pushed.
	goto	loop
;
; Interrupt routine, called to update the display,
; the time, and whatnot.
; Come here approximately 625 times per second.
;
service
; First, some housekeeping
	movwf	savew	; Save W, some bank or other
	swapf	STATUS, W; Save STATUS in W, swapped
	bcf	STATUS, RP0 ; Select BANK0
	movwf	saves	; Save Status in BANK0
	movfw	timeval	; Reset the timer
	movwf	TMR0
	bcf	INTCON, T0IF ; Clear the interrupt flag

 incf debug, F ; Count interrupts
 btfsc STATUS, Z ; Low byte wrapped?
 incf debugh, F ; Yes, bump high byte

; To keep the refresh rate high, we skip most of the code 
; here, except every fifth time.
	incf	row, F
	movfw	row
	sublw	d'5'
	btfss	STATUS, Z
	goto	display
	clrf	row

; Come here approximately 125 times per second.
; These interrupts do switch debouncing, time 
; updates, and EEPROM updates as well as refreshing
; the display.

; Clear the flag to indicate a debounce interval has ended.
	clrf	bouncing

; Now, increment the tick counter to divide by 125.
	bcf	STATUS, RP0 ; Select BANK0
	incf	ticks, F
	movfw	ticks
	subwf	ticksec, W
	btfss	STATUS, Z
	goto	display
	movwf	ticks	; Wrap Tick Counter

; Come here once per second.
; Dispatch here for modal stem stuff.
	movfw	mode	; Which mode are we in?
	addwf	PCL, F	; Branch based on the digit
	goto	mode0
	goto	mode1
	goto	mode2
	goto	mode3
	goto	mode4

;
; Update the display.
; (Moved this here so as not to have to fudge PCLATH 
; for the computed goto.)
display
	movfw	dark
	addlw	0x00
	btfsc	STATUS, Z ; Is the display dark?
	goto	intret	; Yes, just return
	movfw	row	; No, get row to display next
	addwf	PCL, F	; Branch based on the row
	goto	secs	; Row 0
	goto	mins	; Row 1
	goto	hour	; Row 2
	goto	day	; Row 3
	goto	month	; Row 4
intret
	; Must be BANK0
	swapf	saves, W; Restore STATUS
	movwf	STATUS
	swapf	savew, F; Restore W
	swapf	savew, W
	retfie		; Return from Interrupt service

; Mode 0 -- keep accurate time.
mode0
;
; Check once per second to see if it is time to darken
; the display.
	movfw	dark
	addlw	0x0
	btfsc	STATUS, Z
	goto	uptime
	decfsz	dark, F
	goto	uptime
;
; Shut down the display.
	bsf	STATUS, RP0 ; Select BANK1
	errorlevel -302
	movlw	0xFF	; Tri-state all PORTA and PORTB outputs
	movwf	TRISB
	movwf	TRISA
	bcf	STATUS, RP0 ; Select BANK0
	errorlevel -302

;
; Update the time.
uptime
 clrf debug
 clrf debugh
	incf	seconds, F
	movfw	seconds
	sublw	d'60'
	btfss	STATUS, Z
	goto	display
	movwf	seconds	; Wrap Seconds Counter
	incf	minutes, F
	movfw	minutes
	sublw	d'60'
	btfss	STATUS, Z
	goto	display
	movwf	minutes	; Wrap Minutes Counter
	incf	hours, F
	movfw	hours
	sublw	d'24'
	btfss	STATUS, Z
	goto	display
	movwf	hours	; Wrap Hours Counter
	incf	days, F
	movfw	days
	sublw	d'32'
	btfss	STATUS, Z
	goto	display
	movlw	1
	movwf	days	; Wrap Days Counter
	incf	months, F
	movfw	months
	sublw	d'13'
	btfss	STATUS, Z
	goto	display
	movlw	1
	movwf	months	; Wrap Months Counter
	goto	display

;
; Mode 1 -- just increment minutes
mode1
	incf	minutes, F
	movfw	minutes
	sublw	d'60'
	btfss	STATUS, Z
	goto	display
	movwf	minutes	; Wrap Minutes Counter
	goto	display
;
; Mode 2 -- just increment hours
mode2
	incf	hours, F
	movfw	hours
	sublw	d'24'
	btfss	STATUS, Z
	goto	display
	movwf	hours	; Wrap Hours Counter
	goto	display
;
; Mode 3 -- just increment days
mode3
	incf	days, F
	movfw	days
	sublw	d'32'
	btfss	STATUS, Z
	goto	display
	movlw	1
	movwf	days	; Wrap Days Counter
	goto	display
;
; Mode 4 -- just increment months
mode4
	incf	months, F
	movfw	months
	sublw	d'13'
	btfss	STATUS, Z
	goto	display
	movlw	1
	movwf	months	; Wrap Months Counter
	goto	display

secs			; Output Second
	movlw	0x1E
	movwf	PORTA
	movfw	seconds
	movwf	PORTB
	goto	intret

mins			; Output Minute
	movlw	0x1D
	movwf	PORTA
	movfw	minutes
	movwf	PORTB
	goto	intret

hour			; Output Hour
	movlw	0x1B
	movwf	PORTA
	movfw	hours
	movwf	PORTB
	goto	intret

day			; Output Day
	movlw	0x17
	movwf	PORTA
	movfw	days
	movwf	PORTB
	goto	intret

month			; Output Month
	movlw	0x0F
	movwf	PORTA
	movfw	months
	movwf	PORTB
	goto	intret

;
; Compute the values for the timer.
; Goal is 625 interrupts per second, so we need to divide 
; 1Mhz by 1600 (2^6*25) to get 625Hz.  Set the prescaler for 
; (2^6)=64, then  set the timeval for (5^2)=25.
	org	0x2100	; EEPROM address
	de	-d'25'	; timeval is loaded into the timer
	de	d'125'	; Configure the clock rate
			; Hmm.  128, not 125?
; These values are empirically determined.
; The clock seems to actually oscillate at around 4*1024474 Hz.
; Here we set the dividers for 1024320, which is as close as
; we can get.  We expect the clock to run about 13 seconds fast
; per day.
;	de	-d'33'	; timeval is loaded into the timer
;	de	d'97'	; Configure the clock rate
	end
