; Binary LED Clock
; By Hein Ragas
; http://home.publishnet.nl/~hragas
; Version 1.0
; Clock functionality ONLY

	title "Binary LED Clock"
	list P = 16F628A
	include "p16F628A.inc"
	__CONFIG _CP_OFF & _XT_OSC & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _BODEN_OFF & _LVP_OFF

; Variables for delay loop
teller		EQU	070h
teller2	        EQU	071h

; Variables for time-keeping
sece		EQU	072h	; Second units
sect		EQU	073h	; Second tens
mine		EQU	07Bh	; Minute units
mint		EQU	07Ch	; Minute tens
hre		EQU	07Dh	; Hour units
hrt		EQU	07Eh	; Hour tens

; Variables for the one second timer
bres_hi		EQU	076h	; hi byte of our 24bit variable
bres_mid	EQU	077h	; mid byte
bres_lo		EQU	078h	; lo byte

; Variables to save register statusses during interrupt
temp_s		EQU	079h
temp_w		EQU	07Ah

; Variables for debouncing and inputs
bounce_timer	EQU	07Fh	; Counter for the debounce routine
csa		EQU	060h
cva		EQU	061h
count_A		EQU	062h
count_B		EQU	063h
prev_val	EQU	064h


		GOTO init
                ORG	0x4		; Choose starting adress

; Interrupt routine. This is called every 256 instructions.
isr		MOVWF	temp_w          ; save w register
          	MOVF	STATUS, W       ; W = STATUS
          	MOVWF	temp_s          ; save STATUS register
          	
          	; First, see if we need to call the debouncing routine
          	; We don't call the debouncing routine every interrupt, but every 10
          	;   interrupts. That's once every 2560 instructions. Because an input needs
          	;   to be the same for 4 consecutive calls to the debouncing routine, it
          	;   needs to be the same for roughly 0.01 seconds.
          	DECFSZ	bounce_timer, f	; Decrement bounce_timer, see if it's 0
		GOTO 	bookkeeping	; Not 0, skip the debouncing routine
		
		MOVLW	009h		; Bounce_timer == 0
		MOVWF	bounce_timer	; Reset bounce_timer to 9
		
		CALL	read_inputs
		CALL	debounce
		CALL	process_inputs

          	; Here comes the bookkeeping for the timer routine
bookkeeping	TSTF bres_mid		; first test for mid==0
		SKPNZ			; nz = no underflow needed
		DECF bres_hi,f		; z, so is underflow, so dec the msb

		DECFSZ bres_mid,f	; dec the mid byte (subtract 256)
		GOTO end_isr		; nz, so definitely not one second yet.

		TSTF bres_hi		; test hi for zero too
		SKPZ			; z = both hi and mid are zero, is one second!
		GOTO end_isr		; nz, so not one second yet.

		MOVLW 0x0F		; get msb value 
		MOVWF bres_hi		; load in msb
		MOVLW 0x42		; get mid value
		MOVWF bres_mid		; load in mid
		MOVLW 0x40		; lsb value to add
		ADDWF bres_lo,f		; add it to the remainder already in lsb
		SKPNC			; nc = no overflow, so mid is still ok

		INCF bres_mid,f		; c, so lsb overflowed, so inc mid

          	CALL continue		; A second has passed, increase the second count          	
          	
		; End of the interrupt routine
end_isr        	BCF	INTCON, T0IF	; clear the TMR0 flag bit
		MOVF	temp_s, W	; Put STATUS and W back where they belong
          	MOVWF	STATUS
          	SWAPF	temp_w, F
          	SWAPF	temp_w, W
          	RETFIE			; Return from interrupt


; Initialisation routine
init		BSF 	STATUS,5	; Choose bank 1
		CLRF	TRISA		; All pins of port A are outputs
		CLRF	TRISB		; All pins of port B are outputs...
		BSF	TRISB, 6
		BSF	TRISB, 7	; ...except for the last two
		MOVLW	b'10001000'	; Set the options (prescaler for watchdog timer)
		MOVWF	OPTION_REG
	        BCF 	STATUS,5	; Choose bank 0

		CALL initial_vals	; Set initial values
		
		MOVLW 0x0F		; get msb value 
		MOVWF bres_hi		; put in hi
		MOVLW 0x42 +1		; get mid value (note we added 1 to it)
		MOVWF bres_mid		; put in mid
		MOVLW 0x40		; get lsb value
		MOVWF bres_lo		; put in mid
		
		CLRF	INTCON
		BSF	INTCON, T0IE
		BSF	INTCON, GIE	; Set Timer interrupt

; Routine to show the numbers on the display
shownumbers		
		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	sece,w		; Load sece into W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 0	; Show the first row
		CALL delay		; Call our delay routine

		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	sect,w		; Load sect in W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 1	; Show the second row
		CALL delay
		
		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	mine,w		; Load mine into W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 2	; Show the third row
		CALL delay

		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	mint,w		; Load mint into W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 3	; Show the fourth row
		CALL delay

		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	hre,w		; Load hre into W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 4	; Show the fifth row
		CALL delay

		MOVLW	000h
		MOVWF	PORTB		; All LEDs off
		MOVF	hrt,w		; Load hrt into W
		MOVWF	PORTA		; Set the LEDs
		BSF	PORTB, 5	; Show the sixth row
		CALL delay

		GOTO shownumbers	; Just keep on showing those numbers -- the
					;   rest of the functionality is called from the interrupt
					;   routine.

; This routine is called whenever a second has passed.
continue	INCF	sece, 1		; Increase sece
		MOVF	sece, w
		ADDLW	0F6h		; sece > 9?
		SKPNC
		GOTO overflow_sece	; sece == 10, so increment sect
		RETURN

overflow_sece	CLRF	sece		; Set sece back to 0
		INCF	sect, 1		; Increase sect with 1
		MOVF	sect, w
		ADDLW	0FAh		; sect > 5?
		SKPNC
		GOTO overflow_sect	; sect == 5, so increment mine
		RETURN

overflow_sect	CLRF	sect		; Set sect back to 0
		CLRF	sece
		INCF	mine, 1		; Increase mine
		MOVF	mine, w
		ADDLW	0F6h		; mine > 9?
		SKPNC
		GOTO overflow_mine	; mine == 10, so increment sect
		RETURN

overflow_mine	CLRF	mine		; Set mine back to 0
		INCF	mint, 1		; Increase mint
		MOVF	mint, w
		ADDLW	0FAh		; mint > 5?
		SKPNC
		GOTO overflow_mint	; mint == 5, so increment hre
		RETURN

overflow_mint	CLRF	mint		; Reset mint to 0
incr_hre	INCF	hre, 1		; Increase hre
		MOVF	hre, w
		ADDLW	0FCh		; hre > 4?
		SKPNC
		GOTO check_hre		; hre > 4, so we need to check whether we need
					;   to overflow hrt
		RETURN

check_hre	MOVF	hrt, w
		ADDLW	0FEh		; hrt > 1?
		SKPNC
		GOTO	overflow_hre	; hrt == 2, hre == 5 -> overflow!
		MOVF	hre, w		; hrt == 1
		ADDLW	0F6h		; hre > 9?
		SKPNC
		GOTO overflow_hre	; hrt == 1, hre == 10 -> overflow!
		RETURN

overflow_hre	CLRF	hre		; Reset hre to 0
		INCF	hrt, 1		; Increase hrt
		MOVF	hrt, w
		ADDLW	0FDh		; hrt > 2?
		SKPNC
		GOTO overflow_hrt	; hrt == 2; overflow hrt (it's midnight!)
		RETURN

overflow_hrt	CLRF	sece		; Midnight, reset everything to 0!
		CLRF	sect
		CLRF	mine
		CLRF	mint
		CLRF	hre
		CLRF	hrt
		RETURN

; Sets the initial values. We start 9 seconds before midnight!
initial_vals	MOVLW	001h
		MOVWF	sece
		CLRF	sect
		CLRF	mine
		CLRF	mint
		CLRF	hre
		CLRF	hrt
		MOVLW	005h
		MOVWF	sect
		MOVLW	009h
		MOVWF	mine
		MOVLW	005h
		MOVWF	mint
		MOVLW	003h
		MOVWF	hre
		MOVLW	002h
		MOVWF	hrt
		; Also, set the variables for the counters etc.
		MOVLW	009h
		MOVWF	bounce_timer
		CLRF	csa
		CLRF	cva
		CLRF	count_A
		CLRF	count_B
		CLRF	prev_val
		RETURN

; Our delay loop. Waits roughly 1000 instructions, that's 1 msec
delay					
		MOVLW	00Ah		;10 times...
		MOVWF	teller
outerloop	MOVLW	064h		;100 times...
		MOVWF	teller2
innerloop	DECFSZ	teller2, 1
		GOTO innerloop
		DECFSZ	teller, 1
		GOTO outerloop
		RETURN

; Read the inputs
read_inputs	
		; Check minute pin, sets the appropriate bit in the csa variable
		BTFSC	PORTB, 6
		GOTO	minute_een
minute_nul	BCF	csa, 0
		GOTO	einde_minute
minute_een	BSF	csa, 0
einde_minute

		; Check hour pin, sets the appropriate bit in the csa variable
		BTFSC	PORTB, 7
		GOTO	hour_een
hour_nul	BCF	csa, 1
		GOTO	einde_hour
hour_een	BSF	csa, 1
einde_hour	RETURN

; Debounce routine
debounce
		;Increment the vertical counter
		MOVF    count_B,W
		XORWF   count_A,F
		COMF    count_B,F

		;See if any changes occurred
		MOVF    csa,W
		XORWF   cva,W

		;Reset the counter if no change has occurred
		ANDWF   count_B,F
		ANDWF   count_A,F

		;Determine the counter's state
		MOVF    count_B,W
		IORWF   count_A,W

		;Clear all bits that are filtered-or more accurately, save
		;the state of those that are being filtered
		ANDWF   cva,F
		XORLW   0xff

		;Re-write the bits that haven't changed.
		ANDWF   csa,W
		IORWF   cva,F

		RETURN

; Process the debounced inputs
; We have two variables to work with: prev_val, which contains the previous (debounced) states of the inputs;
;   and cva, the debounced current state of the inputs.
; Each input is set as a bit in both the variables.
process_inputs
		; Check the previous value of the minute-input
		BTFSS	prev_val, 0
		GOTO	minute_was_zero
minute_was_one	BTFSS	cva, 0
		BCF	prev_val, 0	; from 1 to 0 -> reset prev_val to 0
		GOTO	check_hour
minute_was_zero	BTFSC	cva, 0
		GOTO	minute_press
		GOTO	check_hour
minute_press	BSF	prev_val, 0
		CALL	overflow_sect	; from 0 to 1 -> tick!
check_hour	; Check the previous value of the hour input
		BTFSS	prev_val, 1
		GOTO	hour_was_zero
hour_was_one	BTFSS	cva, 1
		BCF	prev_val, 1	; from 1 to 0 -> reset prev_val to 0
		RETURN
hour_was_zero	BTFSC	cva, 1
		GOTO	hour_press
		RETURN
hour_press	BSF	prev_val, 1
		CALL	incr_hre	; from 0 to 1 -> tick!
		RETURN

		END
