; This generates code for 4 digits output (HH:MM)
; Replace #UNDEFINE with #DEFINE for 6 digits output (HH:MM:SS)
#UNDEFINE	_6_digits

; The magic wand! V3.1
; clock frequency = 32768Hz
; INT, TMR0 interrupts used.
; TMR0 is free running
; display triggered on INT (TB0) input low
; 9/2/2003 - Alex Lambardi
; Developped and assembled with MPLAB

; ############################################################################
; 12/2006
; Released under Creative Commons (CC) license
; Attribution-Noncommercial-Share Alike 2.5 License
;
; ############################################################################


	include "p16f84.inc"

	LIST p=16F84

; no copy protection, crystal type is XT, no power up delay timer,
; turn off watchdog timer

	__CONFIG	(_CP_OFF & _XT_OSC & _PWRTE_OFF & _WDT_OFF)

hour			equ 0x0C	; hours
min				equ 0x0D	; minutes
sec				equ 0x0E	; seconds
column			equ 0x10	; current column to display
index			equ 0x11	; index to column table of beginning of chr to display
w_temp			equ	0x12	; variable used for context saving 
status_temp		equ	0x13	; variable used for context saving

;; constants
wand		equ 0	; wand button is at PORTB, bit 0
min_butt	equ 0	; minutes set button is at PORTA,0
hour_butt	equ 1	; hours set button is at PORTA,1

	org	0x00
	clrf    PCLATH		; reset page bits
	call	_initialize
	goto	loop
	
ISR
	org	0x04			; interrupt service routine

	movwf   w_temp		; save off current W register contents
	movf	STATUS,w	; move status register into W register
	movwf	status_temp	; save off contents of STATUS register
	bcf		STATUS,RP0	; Bank 0

	btfss	INTCON,T0IF	; TMR0 overflow ?
		goto	ISR_INT	; no, must be button press
	call	_inc_sec	; yes, increment seconds
	bcf	INTCON,T0IF		; clear flag
	goto	ISR_end
ISR_INT
	btfss	INTCON,INTF	; Button pressed ?
		goto	ISR_end	; no, but should never get to here...
	call	is_pressed	; yes, wand!
	bcf		INTCON,INTF	; clear flag
ISR_end
	movf    status_temp,w	; retrieve copy of STATUS register
	movwf	STATUS		; restore pre-isr STATUS register contents
	swapf   w_temp,f
	swapf   w_temp,w	; restore pre-isr W register contents
	retfie
	
loop
	goto	loop		; Just loop. Wait for interrupts

is_pressed
IFDEF	_6_digits
	movf	sec,W		; do units of seconds
	andlw	0x0F		; mask high nibble
	call	do_digit
	swapf	sec,W		; do tens of seconds
	andlw	0x0F		; mask high nibble
	call	do_digit
	movlw	0x0A		; do colon
	call	do_digit
ENDIF
	movf	min,W		; do units of minutes
	andlw	0x0F		; mask high nibble
	call	do_digit
	swapf	min,W		; do tens of minutes
	andlw	0x0F		; mask high nibble
	call	do_digit
	movlw	0x0A		; do colon
	call	do_digit
	movf	hour,W		; do units of hours
	andlw	0x0F		; mask high nibble
	call	do_digit
	swapf	hour,W		; do tens of hours
	andlw	0x0F		; mask high nibble
	call	do_digit
	return				; done
;
do_digit				; Enter with W = the digit to display
						; (0 to 9, 10 is colon) eval the index into
						; the char table at 'get_clm'
	movwf	index		; Save W
	bcf	STATUS,C
	rlf	index,F			; W = W*2
	rlf	index,F			; W = W*4
	addwf	index,W		; W = W*5
	movwf	index
	movlw	0x05		; Each digit is 5 column
	movwf	column		; column counter (5-1)
do_digit_l				; Do 5 times
	movf	index,W
	clrf	PORTB		; Clear display at end of each column to 'visually'
						; separate columns of LEDs
	call	get_clm		; Get the LED pattern to display
	movwf	PORTB
	incf	index
	decfsz	column
	goto	do_digit_l
	clrf	PORTB		; clear display at end of each digit
	return

	radix	dec
		 				; indirect addressing needs the table to be in the
 						; same 256 bytes block

get_clm
	addwf   PCL,F			; Enter with W equal x and
							; will return the x-th value
							; of the table in W
	DT	124,138,146,162,124	; "0"
	DT	0,0,254,0,0			; "1"
	DT	140,146,146,162,196	; "2"
	DT	98,150,154,146,130	; "3"
	DT	32,248,34,44,48		; "4"
	DT	98,146,146,146,142	; "5"
	DT	96,146,146,146,124	; "6"
	DT	14,18,34,66,130		; "7"
	DT	108,146,146,146,108	; "8"
	DT	108,146,146,146,12	; "9"
	DT	0,0,108,108,0		; ":"
	radix	hex

_inc_sec
	btfss	PORTA,hour_butt
	call	_inc_hour
	btfss	PORTA,min_butt
	call	_inc_min
	incf	sec,f		; now, increment seconds
	movf	sec,W		; seconds->W
	andlw	0x0F		; mask most significant nibble of W
	sublw	0x0A		; units of sec == 10?
	btfss	STATUS,Z	; 
	return
	movf	sec,W		; seconds->W
	andlw	0xF0		; resets units of seconds and
	addlw	0x10		; increment tens of seconds
	movwf	sec			; store seconds
	movf	sec,W		; seconds->W
	sublw	0x60
	btfss	STATUS,Z	; seconds == 60?
	return				; no, return
	clrf	sec			; yes
	call	_inc_min
	return
_inc_min
	clrf	sec			; clear seconds
	incf	min,f
	movf	min,W		; minutes->W
	andlw	0x0F		; mask most significant nibble of W
	sublw	0x0A		; units of sec == 10?
	btfss	STATUS,Z
	return				; no, return
	movf	min,W		; yes, minutes->W
	andlw	0xF0		; resets units of minutes and
	addlw	0x10		; increment tens of minutes
	movwf	min			; store minutes
	movf	min,W		; minutes->W
	sublw	0x60		; minutes == 60?
	btfss	STATUS,Z
	return				; no, return
	clrf	min			; yes, reset minutes
	call	_inc_hour
	return

_inc_hour
	incf	hour,f
	movf	hour,W		; hours->W
	andlw	0x0F		; mask most significant nibble of W
	sublw	0x0A
	btfss	STATUS,Z	; units of hour == 10? 
	goto	_ck_24		; No, check if hour == 24
	movf	hour,W		; Yes, hours->W
	andlw	0xF0		; resets units of minutes and
	addlw	0x10		; increment tens of minutes
	movwf	hour
	return
_ck_24
	movf	hour,W		; hours->W
	sublw	0x24		; hour == 24?
	btfsc	STATUS,Z 
	clrf	hour		; yes, reset hours and return
	return				; no, simply return

_initialize
	bcf		STATUS, RP1
	bsf		STATUS, RP0	; Select Bank 1

	movlw	0x01
	movwf	TRISB		; PORTB[1..7] is output
						; PORTB[0] is input
	movlw	0x03
	movwf	TRISA		; PORTA 0-1 are inputs

	movlw	b'00000100'	; RBPU=0 (pull-ups on portB enabled),
						; INTEDG=0 (falling edge INT on RB0),
						; T0CS=0 (TMR0 uses internal clk source),
						; T0SE=x (edge direction if T0CS set),
						; PSA=0 (prescaler assigned to TMR0),
						; prescaler=32 (TMR0 overflows once per sec)
	movwf	OPTION_REG	; 

	bcf		STATUS,RP0	; select bank 0

	clrf	TMR0
	clrf	sec
	clrf	min
	clrf	hour

	clrf	INTCON			; zero interrupt control register

	bsf		INTCON, T0IE	; enable TMR0 interrupt generation
	bsf		INTCON, INTE	; enable INT(RB0) interrupt generation
	bsf		INTCON, GIE		; global interrupt enable

	return
							; fill remaining memory with "goto 0x00"s: in case
							; the micro gets lost (!) it will meet one of these
							; gotos eventually and synch again.
	fill	(goto	0x000), (0x400-$)	; 0x400 is mem size, $ is current addr.

	END
