	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   
;	__CONFIG _LP_OSC & _PWRTE_OFF & _WDT_OFF & _CP_OFF & _MCLRE_OFF & _LVP_OFF
	__IDLOCS 0xbc11

; Declare the variables.
	cblock	0x20
	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
	timevh		; Value for the timer register, high byte
	timevl		; Value for the timer register, low byte
	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
	bcf	STATUS, RP1 ; Select BANK0 or BANK1
	clrwdt
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	clrf	OPTION_REG
	errorlevel +302

;INTCON
; Set GIE
; Set PEIE -- apparently timer 1 is a peripheral?
	bcf	STATUS, RP0 ; Select BANK0
	movlw	(1<<GIE) | (1<<PEIE)
	movwf	INTCON

;PIE1
; Set TMR1IE
	movlw	(1<<TMR1IE)
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	movwf	PIE1
	errorlevel +302
	bcf	STATUS, RP0 ; Select BANK0

;PIR1
; Clear CMIF
; Clear TMR1IF
	clrf	PIR1

;PCON
; Set OSCF (4Mhz) (default)
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	movlw	(1<<OSCF)
	movwf	PCON
	errorlevel +302
	bcf	STATUS, RP0 ; Select BANK0

;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
	movlw	0x20	; Set PORTA low bits for output
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	movwf	TRISA

; Initialize PORTB as a 8-bit output register
	errorlevel -302
	bcf	STATUS, RP0 ; Select BANK0
	clrf	PORTB
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	clrf	TRISB	; Set PORTB all bits for output
	errorlevel +302
	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.
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	clrf	EEADR
	bsf	EECON1, RD
	movfw	EEDATA
	errorlevel +302
	bcf	STATUS, RP0 ; Select BANK0
	movwf	timevh
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	incf	EEADR, F
	bsf	EECON1, RD
	movfw	EEDATA
	errorlevel +302
	bcf	STATUS, RP0 ; Select BANK0
	movwf	timevl
	errorlevel -302
	bsf	STATUS, RP0 ; Select BANK1
	incf	EEADR, F
	bsf	EECON1, RD
	movfw	EEDATA
	errorlevel +302
	bcf	STATUS, RP0 ; Select BANK0
	movwf	ticksec

; Initialize the timer

;T1CON
; The prescaler is useless, as it is cleared on every access 
; to the timer.  So clear T1CKPS1 and T1CKPS0.
; Set T1OSCEN
; Set TMR1ON
; Clear TMR1CS
	movlw	(1<<T1OSCEN) | (1<<TMR1ON)
	movwf	T1CON

;CCP1CON
; Clear CCP1M3:CCP1M0
	clrf	CCP1CON

; Initialize the timer
	bcf	STATUS, RP0 ; Select BANK0
	movfw	timevh	; Initialize the timer
	movwf	TMR1H
	movfw	timevl	; Initialize the timer
	movwf	TMR1L

; 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


; The main loop implements 'stem' handling and sleeps 
; if the display is dark.
loop
	; Must be BANK0!
	movfw	dark	; Screen dark?
	btfsc	STATUS, Z
;;	sleep		; Yes, conserve power
	nop ;; TODO: Implement power save mode
	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
	bcf	PIR1, TMR1IF ; Clear the interrupt flag
	movfw	timevl
	addwf	TMR1L, F
	movfw	timevh
	btfsc	STATUS, C
	addlw	0x1
	addwf	TMR1H, F

; 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
	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
TicksLost equ	1	; Ticks Lost per clock update
TimeVal	equ	-d'25'*d'64'+TicksLost
	de	(TimeVal>>8) & 0xFF
	de	TimeVal & 0xFF
	de	d'125'	; Configure the clock rate
	end
