;*******************
;* sbrclock.asm    *
;*******************
		list	p=16F628
		radix	hex
		include	"p16f628.inc"

;******************************************************
;* DISCLAIMER
;******************************************************
;*
;* This software is provided "as is" without any guarantee 
;* or statement of fitness for purpose expressed or implied. 
;* It is offered as a courtesy to enable others to build a 
;* Nixie tube clock described in my web pages at the following
;* URL:
;*
;* http://homepage.ntlworld.com/steven.rougier/Pixie_clock.html
;*
;* The author makes no claims regarding the correctness, 
;* quality, efficiency or number of defects in this software.
;*
;* Anyone using this software does so at their own risk
;* and shall not hold the author responsible for any loss, 
;* damages or injury incurred directy or indirectly from 
;* the use of this software.
;*
;* This software shall not be reproduced without including 
;* this DISCLAIMER in its entirety.
;*
;* Steve Rougier 3rd Oct 2003
;******************************************************


;************************
; set config word options
;************************
	__CONFIG _BODEN_OFF & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_ON & _LVP_OFF & _MCLRE_OFF & _INTRC_OSC_CLKOUT & _XT_OSC


; timings all based on 4 MHz crystal

;--------
; these equates may already be in the include file
;--------
w		equ	0
f		equ	1

;--------
; Adjustment values for crystal variation
;--------
; Normal value = 4098 = 0x1002 = number of interrupts for 1 second
; 0 - 0x001002 = 0xEFFE = NORM_COUNT_MSB, NORM_COUNT_LSB
; leap value = 4164 = 1044 = fine adjustment for "number of interrupts for 1 second"
; 0 - 00108A = EFBC (only the lsbyte needs to be changed = LEAP_COUNT_LSB)

NORM_COUNT_MSB	equ	0xEF	; see above comments for derivation of these constants
NORM_COUNT_LSB	equ	0xFE
LEAP_COUNT_LSB	equ	0xBC

ADJUSTMENT		equ	0xB6	; number of seconds between applying fine adjustment, default = 182

N_TONES			equ	0x09	; number of tones (range = 1 to 9)



;******************************
;** DISPLAY RELATED SETTINGS **
;******************************
ANODE_DELAY		equ	0x06	; compensate for anode slow switching

SCAN_RATE			equ	0x25	; scan keys every nth interrupt
										; SCAN_RATE = 6i+1 or 6i-1, where i is an integer
FADE_REPS			equ	0x03	; default number of fade repeats
FADE_LEVELS			equ	0x0D	; default number of brightness levels. 

		; For FADE_LEVELS = n, 
		; fade time = (n-1) * 1.46ms for each level * (n-1) levels * FADE_REPS


ALARM_BIT		equ	0x80	; portb bit 7 used to drive sounder
KEY_BIT			equ	0x40	; key input on portb bit 6
DP_BIT			equ	0x10	; dp enable position for each digit
ALARM_ENB_BIT	equ	0x08	; flags bit 3 enable alarm in flags
ALARM_MOD_BIT	equ	0x10	; toggle ALARM_MOD bit 4 in flags
H_12_BIT	equ	0x40	; toggle H_12 bit 6 in flags
INIT_DIG	equ	0x20	; first digit is digit 6

KEY1		equ	3	; bitmapped keys remapping
KEY2		equ	1
KEY3		equ	4
KEY4		equ	2
KEY5		equ	0
KEY6		equ	5

MODE_TIMEOUT	equ	0x14	; number of secs before returning to mode 0

; mode flag bits
MODE0		equ	5	; Normal
MODE1		equ	4	; Set time
MODE2		equ	3	; Set alarm
MODE3		equ	2	; Adjustment
MODE4		equ	1	; Tone select
MODE5		equ	0	; Display test
;UNUSED6	equ	6	; Not used
;UNUSED7	equ	7	; Not used

;flags bit assignments
ROLLOVER		equ	0	; flags bit 0 to carry rollovers
SECS			equ	1	; flags bit 1 for seconds
ALARM			equ	2	; flags bit 2 to turn on alarm
ALARM_ENB	equ	3	; flags bit 3 enable alarm
ALARM_MOD	equ	4	; modulate alarm tone
SNOOZE		equ	5	; snooze active
H_12			equ	6	; 12 hour mode on
;SPARE_7		equ	7	;

;*********************************************
;* Start of available RAM. 80 bytes on bank 1
;*********************************************
	cblock	0x20		; 80 bytes of RAM
		w_temp		; used by interrupt svc
		status_temp	; used by interrupt svc
		fg_temp		; general temp for isr (foreground)
		bg_temp1		; general temp for background
		bg_temp2		; general temp for background
		flags			; bitmapped flag bit
		mode			; current mode flags
		mode_timer	; return to mode 0 if key not pressed for a while
		isr_ctr_hi	; count interrupts for 1 second msbyte
		isr_ctr_lo	; count interrupts for 1 second lsbyte
		leap_ctr		; account for fractional Hz in isr
		adjust		; fine adjustment of time keeping
		porta_shad	; port a shadow
		portb_shad	; port b shadow
		scan_ctr		; count calls for key scan
		blank_mask	; turn off bitmapped digits
		dig_ctr		; digit counter
		digit_1		; next digit data
		digit_2		; next digit data
		digit_3		; next digit data
		digit_4		; next digit data
		digit_5		; next digit data
		digit_6		; next digit data
		digit_new1		; new digit data
		digit_new2		; new digit data
		digit_new3		; new digit data
		digit_new4		; new digit data
		digit_new5		; new digit data
		digit_new6		; new digit data
		digit_old1		; previous digit data
		digit_old2		; previous digit data
		digit_old3		; previous digit data
		digit_old4		; previous digit data
		digit_old5		; previous digit data
		digit_old6		; previous digit data
		digit_work1		; output digit data
		digit_work2		; output digit data
		digit_work3		; output digit data
		digit_work4		; output digit data
		digit_work5		; output digit data
		digit_work6		; output digit data
		digit_n	 	; temp digit store
		time_1		; time digit 1 (secs)
		time_2		; time digit 2 (secs x 10)
		time_3		; time digit 3 (mins)
		time_4		; time digit 4 (mins x 10)
		time_5		; time digit 5 (hours)
		time_6		; time digit 6 (hours x 10)
		alarm_3		; alarm digit 3 
		alarm_4		; alarm digit 4 
		alarm_5		; alarm digit 5 
		alarm_6		; alarm digit 6 
		adj_1			; adjust digit 1
		adj_2			; adjust digit 2
		adj_3			; adjust digit 3
		keys_pressed	; bit-map of keys pressed
		old_keys		; last pass value of keys_pressed
		tone			; alarm sound pattern
		tone_work	; fg tone burst counter
		tone_ctr		; fg tone burst loop counter
		snooze_mins		; number of minutes left
		snz_delay		; number of mins
		fade_repeats	; current fade reps value
		fade_repwrk		; working fade reps value
		fade_level		; current fade rate value
		fade_counter	; adjust pwm for digit fade.
		pwm_counter		; duty cycle value
	endc
;
;***************
;* Start of ROM
;***************
		org	0x00			;RESET VECTOR
		goto	Start
;
;**********************************
;* INTERRUPT SERVICE ROUTINE
;**********************************
		org	0x04			;interrupt vector
Intsvc		movwf	w_temp		;save context - w register
		swapf	STATUS,w		;get status (can't use movf, it would change status)
		movwf	status_temp	;save status (nibbles are swapped)
;******************************************************
; Context is now saved, start the service routine code (foreground)
;******************************************************
		clrwdt				; clear watchdog timer
;*************************************************
;*  Interrupt occurs each time TMR0 wraps FF->00
;*  TRM0 source = CLKOUT = osc/4 = 1MHz
;*************************************************

	;**********************************************
	;	NB: DO NOT MODIFY CODE ABOVE WITHOUT 
	;	    CHECKING THE FOLLOWING LINE
	;**********************************************
		movlw	0x16		; TMR0 = 0x16 - Make timer wrap every 244 ticks
		movwf	TMR0		; Note - tmr0 updates inhibited for 2
					; cycles after writing

; 1MHz/244 = 4098.36066 interrupts every second. 
; This frequency is chosen as the alarm sounder resonates at 2050Hz so
; toggling the output pin every isr call provides a simple drive signal

		call	Alarm_output	; Drive sounder

		call	Check_1sec	; Count interrupts

		call 	Refresh	; update the display and keys pressed
Continue

;**********************************
; End of ISR, restore context
;**********************************
		swapf	status_temp,w	;get status, swap nibbles back
		movwf	STATUS		;restore status
		swapf	w_temp,f	;swap nibbles ready for restore
		swapf	w_temp,w	;restore w, use swap to preserve STATUS
		bcf	INTCON,2	;clear interrupt flag and
		retfie			;return from interrupt


;########################
;# START OF SUBROUTINES #
;########################


;********************
;* initialise ram   *
;********************
Ram_init	movlw	0xFF		; first second is a short one
		movwf	isr_ctr_hi

		movlw	ADJUSTMENT
		movwf	adjust		; Set default adjust value

		movlw	SCAN_RATE
		movwf	scan_ctr	; initialise display refresh

		movlw	INIT_DIG
		movwf	dig_ctr

		clrf	digit_1	; init display all digs 0, all dp's off
		clrf	digit_2	; init display 
		clrf	digit_3	; init display 
		clrf	digit_4	; init display 
		clrf	digit_5	; init display 
		clrf	digit_6	; init display 

		clrf	time_1		; init time 
		clrf	time_2
		clrf	time_3
		clrf	time_4
		clrf	time_5
		clrf	time_6

		clrf	alarm_3	; init alarm time
		clrf	alarm_4
		clrf	alarm_5
		clrf	alarm_6

		bcf	flags,ALARM		; turn off alarm
		bcf	flags,ALARM_ENB	; turn off alarm enable
		bcf	flags,H_12		; turn off 12 hour mode

		clrf	mode
		bsf	mode,MODE0	; mode = normal	
		call	Update_dps	; set dp's

		movlw	0x01
		movwf	tone		; default alarm sound
		movwf	tone_work	; initialise tone register

		movlw	0x09
		movwf	snz_delay	; default number of mins for snooze

		movlw	FADE_LEVELS
		movwf	fade_level	; fade_level = default

		movlw	FADE_REPS
		movwf	fade_repeats	; default

		return
;
;*********************************************
;*  Set up the i/o ports
;*********************************************
Port_init	clrf	PORTA
		movlw	0x07		; turn off analogue mode on PORTA
		movwf	CMCON

		bcf	STATUS,RP1
		bsf	STATUS,RP0	; select bank1
		movlw	0x00		; set all PORTA lines as outputs
		movwf	TRISA		; RA5 unused, RA6,7 set up as osc pins
		movlw	KEY_BIT	; set PORTB bit 6 as input
		movwf	TRISB		; all other PORTB lines as outputs
		bcf	STATUS,RP1
		bcf	STATUS,RP0	; back to bank0

		clrf	porta_shad	; shadow for port a
		clrf	portb_shad	; shadow for port b
		return
;
;********************************
; setup timer-based interrupts
;********************************
Timer_init	bcf	INTCON,2	;clear TMR0 int flag
		bsf	INTCON,7	;enable global interrupts
		bsf	INTCON,5	;enable TMR0 int
		clrf	TMR0		;clear timer
		clrwdt			;reset watchdog
		bcf	STATUS,RP1
		bsf	STATUS,RP0	; select bank1
		movlw	b'11011000'	;set up timer, prescaler (bit3) bypassed 
		movwf	OPTION_REG	;send to option register
		bcf	STATUS,RP1
		bcf	STATUS,RP0	; back to bank0
		clrf	TMR0		;start timer
		return


;**********************************************
;* update the alarm output if required
;* called from isr
;**********************************************
Alarm_output					; Update alarm output
		btfss	flags,ALARM_ENB	; if alarm enabled and
		goto	Tone_exit
		btfss	flags,ALARM		; if alarm is requested
		goto	Tone_exit
		decfsz	tone_ctr,f		; every n loops, dec tone_work
		goto	Tone_test
		movlw	0x80			;  n = 128
		movwf	tone_ctr
		decfsz	tone_work,f		; measure tone burst
		goto	Tone_test
		movlw	ALARM_MOD_BIT
		xorwf	flags,f		; toggle ALARM_MOD bit
		movf	tone,w			; set up for next delay
		addwf	tone,w			; w = tone * 2
		addwf	tone,w			; w = tone * 3
		movwf	tone_work		; store temp
		decf	tone_work,f		; = ( tone * 3 ) - 1
		decf	tone_work,f		; = ( tone * 3 ) - 2 (range 1 - 25)

Tone_test	btfss	flags,ALARM_MOD	; if alarm modulate on 
		goto	Tone_exit
		movf	portb_shad,w		; toggle the alarm out bit
		xorlw	ALARM_BIT
		movwf	portb_shad
		movwf	PORTB
Tone_exit	return


;**********************************************
;* Count isr calls for 1 second
;* called from isr
;**********************************************
Check_1sec
; Increment the isr_ctr every isr, normally rollover at 4098 isr calls for 4.000MHz
		incf	isr_ctr_lo,f
		btfsc	STATUS,Z
		incfsz	isr_ctr_hi,f
		goto	Check_1sec_exit

; isr_ctr has rolled over to zero and one second has passed.
; Re-initialise isr_ctr and set a flag for the background tasks.
		bsf	flags,SECS	; set flag every 1 second
		movlw	NORM_COUNT_MSB	;
		movwf	isr_ctr_hi	
		movlw	NORM_COUNT_LSB		
		movwf	isr_ctr_lo
		decfsz	leap_ctr,f	; count 1 second ticks until
		goto	Check_1sec_exit	; it is time for fine adjustment
		movlw	LEAP_COUNT_LSB
		movwf	isr_ctr_lo	; 0 - 00108A = EFBC (only the lsbyte needs to be changed)
		movf	adjust,w	; 
		movwf	leap_ctr	; adjust until clock accurate
Check_1sec_exit	return



;**********************************************
;* PWM's the changing digits to achieve 
;* a clean cross-fade effect
;**********************************************
Cross_fade
		movf	fade_repeats,f	; if fade_repeats = 0
		btfsc	STATUS,Z		; then don't do fade
		goto	No_pwm	

						; see if data has changed
		movf	digit_new1,w	; get current digit
		subwf	digit_1,w		; compare with next digit
		btfss	STATUS,Z		; skip if not changed
		goto	Update_digs		; else check next digit

		movf	digit_new2,w	; get current digit
		subwf	digit_2,w		; compare with next digit
		btfss	STATUS,Z		; skip if not changed
		goto	Update_digs		; else check next digit

		movf	digit_new3,w	; get current digit
		subwf	digit_3,w		; compare with next digit
		btfss	STATUS,Z		; skip if not changed
		goto	Update_digs		; else check next digit

		movf	digit_new4,w	; get current digit
		subwf	digit_4,w		; compare with next digit
		btfss	STATUS,Z		; skip if not changed
		goto	Update_digs		; else check next digit

		movf	digit_new5,w	; get current digit
		subwf	digit_5,w		; compare with next digit
		btfss	STATUS,Z		; skip if not changed
		goto	Update_digs		; else check next digit

		movf	digit_new6,w	; get current digit
		subwf	digit_6,w		; compare with next digit
		btfsc	STATUS,Z		; skip if changed
		goto	Do_pwm

Update_digs
		movf	digit_new1,w	; get current digit
		movwf	digit_old1		; save current as old
		movf	digit_1,w		; get next digit
		movwf	digit_new1  	; save as new digit

		movf	digit_new2,w	; get current digit
		movwf	digit_old2		; save current as old
		movf	digit_2,w		; get next digit
		movwf	digit_new2  	; save as new digit

		movf	digit_new3,w	; get current digit
		movwf	digit_old3		; save current as old
		movf	digit_3,w		; get next digit
		movwf	digit_new3  	; save as new digit

		movf	digit_new4,w	; get current digit
		movwf	digit_old4		; save current as old
		movf	digit_4,w		; get next digit
		movwf	digit_new4  	; save as new digit

		movf	digit_new5,w	; get current digit
		movwf	digit_old5		; save current as old
		movf	digit_5,w		; get next digit
		movwf	digit_new5  	; save as new digit

		movf	digit_new6,w	; get current digit
		movwf	digit_old6		; save current as old
		movf	digit_6,w		; get next digit
		movwf	digit_new6  	; save as new digit

		movf	fade_level,w	; initiate cross-fade
		movwf	fade_counter	
		movwf	pwm_counter	
		movf	fade_repeats,w
		movwf	fade_repwrk

Do_pwm					; update count and adjust duty cycle
		decfsz	pwm_counter,f		;
		goto	Pwm_not_zero
		movf	fade_level,w	; reset pwm	
		movwf	pwm_counter	
		decfsz	fade_repwrk,f
		goto	Pwm_not_zero
		movf	fade_repeats,w	;reset repeats
		movwf	fade_repwrk
		decfsz	fade_counter,w	; ramp the duty down to 0
		movwf	fade_counter	; update file register if > 0 


Pwm_not_zero				; if fade > pwm then use old
						; else use new

		movf	fade_counter,w
		subwf	pwm_counter,w
		btfss	STATUS,C		; c = 0 means f-w is negative (w>f)
		goto	Use_old

Use_new					; if w>f ( pwm > fade )
		movf	digit_new1,w	; get new digit
		movwf	digit_work1		; save as work digit
		movf	digit_new2,w	; get new digit
		movwf	digit_work2		; save as work digit
		movf	digit_new3,w	; get new digit
		movwf	digit_work3		; save as work digit
		movf	digit_new4,w	; get new digit
		movwf	digit_work4		; save as work digit
		movf	digit_new5,w	; get new digit
		movwf	digit_work5		; save as work digit
		movf	digit_new6,w	; get new digit
		movwf	digit_work6		; save as work digit

		goto Pwm_exit

Use_old					; else (pwm <= fade)
		movf	digit_old1,w	; get old digit
		movwf	digit_work1		; save as work digit
		movf	digit_old2,w	; get old digit
		movwf	digit_work2		; save as work digit
		movf	digit_old3,w	; get old digit
		movwf	digit_work3		; save as work digit
		movf	digit_old4,w	; get old digit
		movwf	digit_work4		; save as work digit
		movf	digit_old5,w	; get old digit
		movwf	digit_work5		; save as work digit
		movf	digit_old6,w	; get old digit
		movwf	digit_work6		; save as work digit

		goto Pwm_exit


No_pwm
		movf	digit_1,w		; get current digit
		movwf	digit_work1		; save as work digit
		movf	digit_2,w		; get current digit
		movwf	digit_work2		; save as work digit
		movf	digit_3,w		; get current digit
		movwf	digit_work3		; save as work digit
		movf	digit_4,w		; get current digit
		movwf	digit_work4		; save as work digit
		movf	digit_5,w		; get current digit
		movwf	digit_work5		; save as work digit
		movf	digit_6,w		; get current digit
		movwf	digit_work6		; save as work digit


Pwm_exit	return



;**********************************************
;* update the display digits and read the keys
;* called from isr
;**********************************************
Refresh

									; turn off anode drive 
		movf	portb_shad,w	; get current port b
		andlw	ALARM_BIT		; mask off all except alarm bit
		movwf	portb_shad		; write to portb shadow
		movwf	PORTB				; write to portb
		movlw	ANODE_DELAY		; wait for drivers to respond
		call	fg_delay			; (anode drivers turn off slowly)
	
		movlw	0x08				; send out the work digits
		movwf	fg_temp			; get ready for test for > 8
		bcf	STATUS,C			; clear carry (shift in 0) 
		rrf	dig_ctr,w		; select next digit
		btfsc	STATUS,C			; if last digit done
		movlw	INIT_DIG			; reset to initial digit
		movwf	dig_ctr			; select digit to turn on

		btfsc	dig_ctr,0		; load value for selected digit
		movf	digit_work1,w	; select digit 1 value
		btfsc	dig_ctr,1
		movf	digit_work2,w	; select digit 2 value
		btfsc	dig_ctr,2
		movf	digit_work3,w	; select digit 3 value
		btfsc	dig_ctr,3
		movf	digit_work4,w	; select digit 4 value
		btfsc	dig_ctr,4
		movf	digit_work5,w	; select digit 5 value
		btfsc	dig_ctr,5
		movf	digit_work6,w	; select digit 6 value

		subwf	fg_temp,f		; preserve w, check for 9
		btfss	STATUS,DC		; if lower nibble of digit = 9
		addlw	0x03				; then make it 0xXC (preserve dp)
		movwf	porta_shad
		movwf	PORTA				; update to new digit data

		movf	portb_shad,w	; get current port b
		andlw	ALARM_BIT		; mask off all except alarm bit
		iorwf	dig_ctr,w		; add in digit select data
		movwf	portb_shad		; write to portb shadow
		movwf	fg_temp			; write to portb temp shadow

		movf	blank_mask,w	; prepare for blanking
		andwf	dig_ctr,w		; if current digit should be blanked
		btfsc	STATUS,Z			; then update shadow
		goto	Not_blank
		movf	portb_shad,w	; get current port b
		andlw	ALARM_BIT		; mask off all except alarm bit
		movwf	portb_shad		; write to portb shadow
Not_blank
		decfsz	scan_ctr,f	; time for a key scan yet?
		goto	Check_keys_exit			; 
		movlw	SCAN_RATE		; if ctr = 0 then scan keys
		movwf	scan_ctr			; and reset ctr
		movf	fg_temp,w		; snapshot the key input on portb bit6
		movwf	PORTB				; turn on digit scan line
		nop						; stabilise
		movf	PORTB,w			; get coherent port b 
		movwf	fg_temp			; and save it
		movf	portb_shad,w	; then blank the digit if reqd
		movwf	PORTB				; blanked digit was on for 5us

		movf	fg_temp,w		;read keys
		andlw	KEY_BIT			;mask off all except key input
		btfsc	STATUS,Z			;check if key pressed
		goto	Not_pressed
		movf	fg_temp,w		;Pressed: read active output
		andlw	0x3F				;select the scanned lines only
		iorwf	keys_pressed,f	;record key pressed in bitmap
		goto	Check_keys_exit	;and exit
Not_pressed
		movf	fg_temp,w		;Not pressed: get portb image 
		xorlw	0xFF				;negate it
		andlw	0x3F				;get just the scan lines
		andwf	keys_pressed,f	;turn off key in bitmap	
Check_keys_exit
		movf	portb_shad,w	; Update port b every pass
		movwf	PORTB				; 


		btfsc	dig_ctr,5		; have we done digit 6 yet? 
		call	Cross_fade		; update fade after 6th digit
		return

;******** local subroutine
fg_delay	movwf	fg_temp
fg_delay_loop	decfsz	fg_temp,f
		goto	fg_delay_loop
		return
;
;************************************
;* set mode depending on keys pressed
;************************************
Set_mode	movf	keys_pressed,w	;get key bitmap
		xorwf	old_keys,w		;compare with previous value
		btfsc	STATUS,Z		;exit if nothing changed
		goto	Mode_exit
		clrf	mode_timer		;reset timer every time a key changes state
		andwf	keys_pressed,w	;select just the bit(s) that changed
		movwf	old_keys
		btfsc	old_keys,KEY1
		call	Pressed1
		btfsc	old_keys,KEY2
		call	Pressed2
		btfsc	old_keys,KEY3
		call	Pressed3
		btfsc	old_keys,KEY4
		call	Pressed4
		btfsc	old_keys,KEY5
		call	Pressed5	
		btfsc	old_keys,KEY6
		call	Pressed6
		movf	keys_pressed,w
		movwf	old_keys		;save for next pass
Mode_exit	return

Pressed1	bcf	STATUS,C		; shift in 0
		rrf	mode,f			; next mode
		btfsc	STATUS,C		; if carry set then reset mode
		goto	Mode_reset
		call	Update_dps
		goto	Mode1_exit

Mode_reset	clrf	mode
		bsf	mode,MODE0		; mode = normal	
		call	Update_dps
Mode1_exit	return

Pressed2	btfsc	mode,MODE0
		call	K2mode0
		btfsc	mode,MODE1
		call	K2mode1
		btfsc	mode,MODE2
		call	K2mode2
		btfsc	mode,MODE3
		call	K2mode3
		btfsc	mode,MODE4
		call	K2mode4
		btfsc	mode,MODE5
		call	K2mode5
		return

Pressed3	btfsc	mode,MODE0
		call	K3mode0
		btfsc	mode,MODE1
		call	K3mode1
		btfsc	mode,MODE2
		call	K3mode2
		btfsc	mode,MODE3
		call	K3mode3
		btfsc	mode,MODE4
		call	K3mode4
		btfsc	mode,MODE5
		call	K3mode5
		return

Pressed4	btfsc	mode,MODE0
		call	K4mode0
		btfsc	mode,MODE1
		call	K4mode1
		btfsc	mode,MODE2
		call	K4mode2
		btfsc	mode,MODE3
		call	K4mode3
		btfsc	mode,MODE4
		call	K4mode4
		btfsc	mode,MODE5
		call	K4mode5
		return

Pressed5	btfsc	mode,MODE0
		call	K5mode0
		btfsc	mode,MODE1
		call	K5mode1
		btfsc	mode,MODE2
		call	K5mode2
		btfsc	mode,MODE3
		call	K5mode3
		btfsc	mode,MODE4
		call	K5mode4
		btfsc	mode,MODE5
		call	K5mode5
		return

Pressed6	btfsc	mode,MODE0
		call	K6mode0
		btfsc	mode,MODE1
		call	K6mode1
		btfsc	mode,MODE2
		call	K6mode2
		btfsc	mode,MODE3
		call	K6mode3
		btfsc	mode,MODE4
		call	K6mode4
		btfsc	mode,MODE5
		call	K6mode5
		return


K2mode0				;Alarm on/off
		bcf	flags,ALARM	;turn off alarm
		movlw	ALARM_ENB_BIT	;toggle alarm enable
		xorwf	flags,f
		movlw	DP_BIT		;toggle dp status
		xorwf	digit_6,f
		bcf	flags,SNOOZE	;turn off snooze
		return

K2mode1				;hours adjust, set time
		call	Add_hours		
		return

K2mode2				;hours adjust, set alarm
		movf	alarm_5,w		; add 1 hour using
		call	Incdig10		; base 10 increment hours x 1
		movwf	alarm_5
		btfss	flags,ROLLOVER		; carry required?
		goto	Check_24b
		movf	alarm_6,w		; and add it using
		call	Incdig6			; base 6 increment hours x 10
		movwf	alarm_6
Check_24b					;now wrap hours 23->00
		movf	alarm_6,w
		sublw	0x02		;
		btfss	STATUS,Z	;exit if dig 6 is not 2
		goto	No_carry_b	;
		movf	alarm_5,w
		sublw	0x04
		btfss	STATUS,Z	;exit if dig 5 is not 4
		goto	No_carry_b
		clrf	alarm_5		;else reset to 00 hours
		clrf	alarm_6
No_carry_b
		return

K3mode1				;mins adjust, set time
		call	Add_mins
		return

K3mode2				;mins adjust, set alarm
		movf	alarm_3,w		; add 1 min using
		call	Incdig10		; base 10 increment (Mins)
		movwf	alarm_3
		btfss	flags,ROLLOVER		; carry required?
		goto	No_carry_a
		movf	alarm_4,w		; and add it using
		call	Incdig6			; base 6 increment (10xMins)
		movwf	alarm_4
No_carry_a
		return

K5mode4				;change fade rate
		incf	fade_repeats,f	;
		movf	fade_repeats,w
		sublw	0x09
		btfsc	STATUS,C	; if carry is clear, result is negative (w>9)
		return
		clrf	fade_repeats
		return


K3mode0				;stop alarm sounding
K5mode0				;stop alarm sounding
K6mode0				;stop alarm sounding
		bcf	flags,SNOOZE	;turn off snooze
		bcf	flags,ALARM	;stop alarm sounding, leave enabled
		return

K4mode0				;snooze
		btfss	flags,ALARM	;if alarm not sounding
		goto	K4mode0_exit	;then exit
		bcf	flags,ALARM	;else, turn off alarm
		bsf	flags,SNOOZE	;turn on snooze
		movf	snz_delay,w	;init delay
		movwf	snooze_mins
K4mode0_exit
		return

K4mode1				;not used here
K4mode2				;not used here
K4mode3				;not used here
K4mode4				;not used here
		return

K5mode1				;reset secs
		clrf	time_1
		clrf	time_2
		return

K6mode1				;exit to mode 0
K6mode2				;exit to mode 0
K6mode3				;exit to mode 0
K6mode4				;exit to mode 0
K6mode5				;exit to mode 0
		bcf	flags,ALARM	;stop alarm sounding, leave enabled
		clrf	mode
		bsf	mode,MODE0	; mode = normal	
		call	Update_dps	;update dp's
		return

K5mode2				;set snooze delay
		movf	snz_delay,w		; add 1 min using
		call	Incdig10		; base 10 increment (Mins)
		movwf	snz_delay
		return

K2mode3				;timekeeping adjust -
		decf	adjust,f	;make clock run slower (wrap okay)
		return

K3mode3				;timekeeping adjust +
		incf	adjust,f	;clock runs faster (wrap okay)
		return

K5mode3				;toggle 12/24 hour mode
		movlw	H_12_BIT
		xorwf	flags,f
		return

K2mode4				;prev alarm tone
		bsf	flags,ALARM_ENB	; enable alarm
		movlw	DP_BIT		;update dp status
		iorwf	digit_6,f
		bsf	flags,ALARM	; turn on alarm

		decfsz	tone,f		; if tone = 0 
		goto	K2mode4_exit
		movlw	N_TONES
		movwf	tone		;then tone = max
K2mode4_exit
		return

K3mode4				;next alarm tone
		bsf	flags,ALARM_ENB	; enable alarm
		movlw	DP_BIT		;update dp status
		iorwf	digit_6,f
		bsf	flags,ALARM	; turn on alarm

		incf	tone,f		; tone = tone + 1
		movf	tone,w		; if tone <= max
		sublw	N_TONES	; then exit
		btfsc	STATUS,C	; if w>max then reset to 1
		goto	K3mode4_exit
		clrf	tone		;else 
		incf	tone,f		;tone = 1
K3mode4_exit
		return

K2mode5				;not used yet
K3mode5				;not used yet
K4mode5				;not used yet
K5mode5				;not used yet
		return

;***************************************
;* Update dp status based on mode value
;***************************************
Update_dps	movlw	0xEF		;select dp bit
		andwf	digit_1,f	;and turn it off
		andwf	digit_2,f	;and turn it off
		andwf	digit_3,f	;and turn it off
		andwf	digit_4,f	;and turn it off
		andwf	digit_5,f	;and turn it off
		movlw	DP_BIT		;select dp bit
		btfsc	mode,MODE1
		iorwf	digit_5,f	;and turn it on
		btfsc	mode,MODE2
		iorwf	digit_4,f	;and turn it on
		btfsc	mode,MODE3
		iorwf	digit_3,f	;and turn it on
		btfsc	mode,MODE4
		iorwf	digit_2,f	;and turn it on
		btfsc	mode,MODE5
		iorwf	digit_1,f	;and turn it on
		return


;
;************************************
;* copy required data to display 
;* depending on mode
;************************************
Update_display	
		btfsc	mode,MODE0
		call	Clock_mode
		btfsc	mode,MODE1
		call	Clock_mode
		btfsc	mode,MODE2
		call	Alarm_mode
		btfsc	mode,MODE3
		call	Adjust_mode
		btfsc	mode,MODE4
		call	Tone_mode
		btfsc	mode,MODE5
		call	Display_test
		return

Clock_mode				; copy time to display (dp=xx0000)
		movf	digit_1,w	; get current digit data
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	time_1,w	; add in new number
		movwf	digit_1	; send it out

		movf	digit_2,w	;
		andlw	0xF0		; 
		iorwf	time_2,w
		movwf	digit_2

		movf	digit_3,w	;
		andlw	0xF0		; 
		iorwf	time_3,w
		movwf	digit_3

		movf	digit_4,w	;
		andlw	0xF0		; 
		iorwf	time_4,w
		movwf	digit_4

;Convert to 12 hour if required
		movf	time_5,w	; load default hour values (24 hour)
		movwf	bg_temp1
		movf	time_6,w
		movwf	bg_temp2

		btfss	mode,MODE0
		goto	Update_hours	; if in clock mode
		btfss	flags,H_12
		goto	Update_hours	; and in 12 hour mode
		movf	time_6,f
		btfsc	STATUS,Z	; and tens_of_hours != 0
		goto	May_add12
		btfsc	time_6,1	; and tens_of_hours = 2
		goto	Sub12		; or ( tens_of_hours = 1
		movf	time_5,w	;       and hours > 2 )
		sublw	0x02
		btfsc	STATUS,C	; c = 0 means 2-w is negative (w>2)
		goto	Update_hours

Sub12					; then subtract 12
		decf	bg_temp1,f	
		decf	bg_temp1,f	; -2
		btfss	bg_temp1,7	; if result negative
		goto	Not_neg
		movf	bg_temp1,w	; then borrow 10
		addlw	0x0a
		movwf	bg_temp1
		decf	bg_temp2,f	; perform borrow
Not_neg
		decf	bg_temp2,f	; -1
		goto	Update_hours

May_add12
		movf	time_5,f	; test for 00
		btfss	STATUS,Z
		goto	Update_hours	;if hours = 00 then add 12
		incf	bg_temp1,f
		incf	bg_temp1,f	;+2
		incf	bg_temp2,f	;+1

Update_hours
		movf	digit_5,w	;
		andlw	0xF0		; 
		iorwf	bg_temp1,w
		movwf	digit_5

		movf	digit_6,w	;
		andlw	0xF0		; 
		iorwf	bg_temp2,w
		movwf	digit_6

;if digit 6 is 0 and dp is off then blank digit 6
		movf	digit_6,f	; test for 0, dp off
		btfss	STATUS,Z
		goto	Clock_mode_1
		goto	Clock_mode_2

Clock_mode_1
		clrf	blank_mask	; enable all digits

Clock_mode_2
		return

Alarm_mode				; copy alarm time to display (dp = x01000)
		movlw	0x02
		movwf	blank_mask	; enable all digits except digit 2

		movf	digit_1,w	; get current digit data
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	snz_delay,w	; add in new number
		movwf	digit_1		; send it out

		movf	digit_2,w	;
		andlw	0xF0		; 
		movwf	digit_2

		movf	digit_3,w	;
		andlw	0xF0		; 
		iorwf	alarm_3,w	; add in new number
		movwf	digit_3

		movf	digit_4,w	;
		andlw	0xF0		; 
		iorwf	alarm_4,w
		movwf	digit_4

		movf	digit_5,w	;
		andlw	0xF0		; 
		iorwf	alarm_5,w
		movwf	digit_5

		movf	digit_6,w	;
		andlw	0xF0		; 
		iorwf	alarm_6,w
		movwf	digit_6
		return


Adjust_mode					; (dp = x00100)
		movlw	0x08
		movwf	blank_mask	; blank digit 4

		clrf	adj_2		;unpack adjust into adj_1 - adj_3
		clrf	adj_3
		movf	adjust,w	;get value
		movwf	adj_1

		movlw	0x64		;load 100, keep in w 
Hundreds	incf	adj_3,f	;count hundreds
		subwf	adj_1,f	;subtract 100 until negative
		btfsc	STATUS,C
		goto	Hundreds
		addwf	adj_1,f	;make it positive again
		decf	adj_3,f	;adj_1 is in range 0 -> 99
		
		movlw	0x0A		;load 10, keep in w 
Tens		incf	adj_2,f	;count tens
		subwf	adj_1,f	;subtract 10 until negative
		btfsc	STATUS,C
		goto	Tens
		addwf	adj_1,f	;make it positive again
		decf	adj_2,f	;adj_1 is in range 0 -> 9
		
		movf	digit_1,w	; get current digit data
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	adj_1,w	; add in new number
		movwf	digit_1	; send it out

		movf	digit_2,w	;
		andlw	0xF0		; 
		iorwf	adj_2,w	; add in new number
		movwf	digit_2

		movf	digit_3,w	;
		andlw	0xF0		; 
		iorwf	adj_3,w	; add in new number
		movwf	digit_3

		movf	digit_4,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_4

		movlw	0x02
		btfss	flags,H_12	; display 2 or 4 (for 12 or 24 hours)
		movlw	0x04
		movwf	bg_temp1

		movf	digit_5,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	bg_temp1,w
		movwf	digit_5

		movlw	0x01
		btfss	flags,H_12	; display 1 or 2 (for 12 or 24 hours)
		movlw	0x02
		movwf	bg_temp1

		movf	digit_6,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	bg_temp1,w
		movwf	digit_6
		return


Tone_mode					; (dp = x00010)
		movlw	0x24			; 00100100
		movwf	blank_mask	; blank digits digits 6 and 3

		movf	digit_1,w	; get current digit data
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	tone,w		; add in new number
		movwf	digit_1	; send it out

		movf	digit_2,w	;
		andlw	0xF0		; 
		movwf	digit_2

		movf	digit_3,w	;
		andlw	0xF0		; 
		movwf	digit_3


		movf	digit_4,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		iorwf	fade_repeats,w	; add in new number
		movwf	digit_4

		movf	digit_5,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_5

		movf	digit_6,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_6
		return


Display_test				; (dp = x00001)
		clrf	blank_mask	; enable all digits

		movf	digit_1,w	; get current digit data
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_1	; send it out

		movf	digit_2,w	;
		andlw	0xF0		; 
		movwf	digit_2

		movf	digit_3,w	;
		andlw	0xF0		; 
		movwf	digit_3

		movf	digit_4,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_4

		movf	digit_5,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_5

		movf	digit_6,w	;
		andlw	0xF0		; wipe out current number, preserve dp
		movwf	digit_6
		return


;*************************************
;* Sound alarm if time matches
;*************************************
Check_alarm
		movf	time_1,f	; get time (seconds must be 0)
		btfss	STATUS,Z	; exit if not 0
		goto	Check_alarm_exit
		movf	time_2,f	; get time (seconds must be 0)
		btfss	STATUS,Z	; exit if not 0
		goto	Check_alarm_exit
		movf	time_3,w	; get time
		subwf	alarm_3,w	; compare with alarm
		btfss	STATUS,Z	; turn off if values do not match
		goto	Check_alarm_off
		movf	time_4,w	; get time
		subwf	alarm_4,w	; compare with alarm
		btfss	STATUS,Z	; turn off if values do not match
		goto	Check_alarm_off
		movf	time_5,w	; get time
		subwf	alarm_5,w	; compare with alarm
		btfss	STATUS,Z	; turn off if values do not match
		goto	Check_alarm_off
		movf	time_6,w	; get time
		subwf	alarm_6,w	; compare with alarm
		btfss	STATUS,Z	; turn off if values do not match
		goto	Check_alarm_off
		bsf	flags,ALARM	; all match - turn on alarm
		goto	Check_alarm_exit
Check_alarm_off
		bcf	flags,ALARM	; turn off alarm after 1 minute
Check_alarm_exit
		return		

;*************************************
;* Sound alarm if delay expired
;*************************************
Check_snooze
		btfss	flags,SNOOZE		;exit if snooze not active
		goto	Check_snooze_exit
		decfsz	snooze_mins,f		;count down, check for 0
		goto	Check_snooze_exit
		bsf	flags,ALARM		;times up - turn on alarm
		bcf	flags,SNOOZE		;and cancel snooze
Check_snooze_exit
		return
;
;********************************************
;* Reset mode if keys not pressed for a while
;********************************************
Check_mode_timeout
		incf	mode_timer,f		;add 1 second
		movlw	MODE_TIMEOUT	
		subwf	mode_timer,w
		btfss	STATUS,C		;C=0 if W>F
		goto	Mode_timeout_exit	;if timeout expired
		clrf	mode
		bsf	mode,MODE0		; mode = normal	
		call	Update_dps		; set dp's
Mode_timeout_exit
		return
		
;
;************************************
;* Add 1 second to time when flag set
;************************************
Keep_time	btfss	flags,SECS		;check the seconds flag
		goto	Keep_time_exit	;exit if clear
		bcf	flags,SECS		;else clear the flag

		call	Check_mode_timeout	; return to mode 0 after a while

		movf	time_1,w		; add 1 to seconds using
		call	Incdig10		; base 10 increment (Secs)
		movwf	time_1
		btfss	flags,ROLLOVER	; carry required?
		goto	No_carry
		movf	time_2,w		; and add it using
		call	Incdig6		; base 6 increment (10xSecs)
		movwf	time_2
		btfss	flags,ROLLOVER	; carry required?
		goto	No_carry
		call	Check_snooze		; check every min: delay expired?

Add_mins	movf	time_3,w		; and add it using
		call	Incdig10		;base 10 increment (Mins)
		movwf	time_3
		btfss	flags,ROLLOVER	; carry required?
		goto	No_carry
		movf	time_4,w		; and add it using
		call	Incdig6		;base 6 increment (10xMins)
		movwf	time_4
		btfss	flags,ROLLOVER	; carry required?
		goto	No_carry

Add_hours	movf	time_5,w		; and add it using
		call	Incdig10		;base 10 increment (Hours)
		movwf	time_5	
		btfss	flags,ROLLOVER	; carry required?
		goto	Check_24
		movf	time_6,w		; and add it using
		call	Incdig10		;base 10 increment (10xHours)
		movwf	time_6	
Check_24					;now wrap hours 23.59.59->00.00.00
		movf	time_6,w
		sublw	0x02
		btfss	STATUS,Z		;exit if dig 6 is not 2
		goto	No_carry
		movf	time_5,w
		sublw	0x04
		btfss	STATUS,Z		;exit if dig 5 is not 4
		goto	No_carry
		clrf	time_5			;else reset to 00 hours
		clrf	time_6
No_carry
		call	Check_alarm		;check every sec: does alarm = time?
Keep_time_exit
		return		;


;***************************
;*  Add 1, base 10
;***************************
Incdig10	bcf	flags,ROLLOVER	; clear carry flag
		movwf	digit_n
		incf	digit_n,w
		movwf	digit_n	
		sublw	0x09			; 
		btfsc	STATUS,C		; if carry is clear, result is negative (w>9)
		goto	Ndig10
		movlw	0
		bsf	flags,ROLLOVER	; indicate rollover 9->0
		goto	Incdig10_exit

Ndig10		movf	digit_n,w
Incdig10_exit	return


;***************************
;*  Add 1, base 6
;***************************
Incdig6	bcf	flags,ROLLOVER	; clear carry flag
		movwf	digit_n
		incf	digit_n,w
		movwf	digit_n
		sublw	0x05
		btfsc	STATUS,C		; if carry is clear, result is negative (w>5)
		goto	Ndig6
		movlw	0
		bsf	flags,ROLLOVER	; indicate rollover 5->0
		goto	Incdig6_exit
Ndig6		movf	digit_n,w
Incdig6_exit	return

;--------
; End of subroutines
; Program starts here
;--------
Start		call	Ram_init	;set variables to initial values
		call	Port_init	;set up i/o ports
		call	Timer_init	;start timer based interrupt
;--------
; Done initializing, start the endless loop.
;--------
;
Main_loop					;start of main loop
;
		call	Set_mode		;set mode based on keys pressed
		call	Keep_time		;update time every sec
		call	Update_display	;copy required data to display vars
		goto	Main_loop
		end
;--------
; end of file
;--------


