; $Id: timer.asm 13 2007-07-23 18:56:18Z kpreid $

include "p16f877.inc"

movlwf	macro lv, fv
	movlw lv
	movwf fv
	endm

; ---------------------------------------------------------------------------

bcd_input	equ	PORTC	; high 4 bits
set_button_port	equ	PORTB
set_button_bit	equ	d'4'
run_button_port	equ	PORTB
run_button_bit	equ	d'5'
bankl_button_port	equ	PORTB
bankl_button_bit	equ	d'6'
bankh_button_port	equ	PORTB
bankh_button_bit	equ	d'7'
speaker_port	equ	PORTE
speaker_tris	equ	TRISE
speaker_bit	equ	0

; register assignments, shared-across-banks range (s_*, intsave_*)
intsave_w	equ	0x70
intsave_pclath	equ	0x71
intsave_status	equ	0x72
s_user_bank	equ	0x73
s_d_phase	equ	0x74
s_tmp_0		equ	0x75

; register assignments, banks 0-3 (arrays)
mode_a		equ	0x20
seconds_a	equ	0x21
minutes_a	equ	0x22
hours_a		equ	0x23

; register assignments, bank 0 20-6f (must not overlap arrays)
display_data	equ	0x6f
ticks		equ	0x6e
delay_c1	equ	0x6d
delay_c2	equ	0x6b
d_phase_ticks	equ	0x6a
input_target	equ	0x69
beeping		equ	0x68

mode_run_bit	equ	0x0
mode_ascend_bit	equ	0x1

input_target_count	equ	d'2'
ticks_per_second equ d'240'
ticks_per_d_phase	equ	d'20'
d_phase_last	equ	d'34'	; first phase index is 1, so this is also the count

; ---------------------------------------------------------------------------

	org 0
	goto main
	
	org 0x4
	goto interrupt

	org 0x20
main:
	banksel OPTION_REG
	bcf	OPTION_REG, NOT_RBPU	; enable PORTB pullups
	banksel TRISB
	movlwf	b'11111111', TRISB ; switch inputs
	banksel TRISD
	movlwf	b'00000000', TRISD ; LED outputs
	banksel	TRISC
	movlwf	b'11110000', TRISC ; switch inputs, reserved outputs
	banksel	PORTD
	movlwf	b'00000000', PORTD ; switch on all segments initially
	banksel	speaker_port
	movlwf	b'00000000', speaker_port
	banksel speaker_tris
	bcf	speaker_tris, speaker_bit

	banksel	0x00
	movlwf	0, seconds_a
	movlwf	0, minutes_a
	movlwf	0, hours_a
	movlwf	0, mode_a
	banksel	0x80
	movlwf	0, seconds_a
	movlwf	0, minutes_a
	movlwf	0, hours_a
	movlwf	0, mode_a
	banksel	0x100
	movlwf	0, seconds_a
	movlwf	0, minutes_a
	movlwf	0, hours_a
	movlwf	0, mode_a
	banksel	0x180
	movlwf	0, seconds_a
	movlwf	0, minutes_a
	movlwf	0, hours_a
	movlwf	0, mode_a

	movlwf	0, s_user_bank
	movlwf	d_phase_last, s_d_phase
	banksel input_target
	movlwf	0, input_target
	banksel	beeping
	movlwf	0, beeping
	
	banksel OPTION_REG
	bcf	OPTION_REG, T0CS	; run Timer0 off instruction cycle clock
	bcf	OPTION_REG, PSA		; enable Timer0   prescaler
	bcf	OPTION_REG, PS1		; 1:64 prescaler
	banksel INTCON
	bsf	INTCON, T0IE	; enable Timer0 interrupt
	bsf	INTCON, GIE	;
again:
	; --- action buttons
	banksel set_button_port
	btfss	set_button_port, set_button_bit
	call set_button
	btfss	run_button_port, run_button_bit
	call start_button
	
	; --- bank (timer array) 2-bit selector
	clrf	s_user_bank
	banksel bankl_button_port
	btfsc	bankl_button_port, bankl_button_bit
	bsf	s_user_bank, RP0
	banksel bankh_button_port
	btfsc	bankh_button_port, bankh_button_bit
	bsf	s_user_bank, RP1
	
	; --- display painting
	
	movf	s_user_bank, W
	movwf	STATUS		; select bank for current displayed timer
	
	movlwf	HIGH d_phase_table, PCLATH	; PCLATH is in all banks
	movf	s_d_phase, W
d_phase_table:
	addwf	PCL, F		; jump table
	nop			; value 0 never occurs
	goto blank
	goto blank
	goto blank
	goto blank
	goto blank
	goto digit_ls
	goto digit_ls
	goto digit_ls
	goto blank
	goto digit_hs
	goto digit_hs
	goto digit_hs
	goto blank
	goto blank
	goto digit_lm
	goto digit_lm
	goto digit_lm
	goto blank
	goto digit_hm
	goto digit_hm
	goto digit_hm
	goto blank
	goto blank
	goto digit_lh
	goto digit_lh
	goto digit_lh
	goto blank
	goto digit_hh
	goto digit_hh
	goto digit_hh
	goto blank
	goto d_do_which
	goto d_do_which
	goto d_do_which
	; end of jump table -- size of table is d_phase_last

blank:
	banksel	PORTD
	movlwf	0xFF, PORTD
	goto again
	
d_do_which:
	movf	s_user_bank, W
	xorlw	0xFF
	banksel	PORTD
	movwf	PORTD
	goto	again

digit_ls:	
	movf	seconds_a, W
	goto digit
digit_hs:
	swapf	seconds_a, W
	goto digit
digit_lm:	
	movf	minutes_a, W
	goto digit
digit_hm:
	swapf	minutes_a, W
	goto digit
digit_lh:	
	movf	hours_a, W
	goto digit
digit_hh:
	swapf	hours_a, W
digit:
	banksel	display_data
	movwf	display_data
	call 	display
	xorlw	0xFF
	banksel	PORTD
	movwf	PORTD

	goto	again
	
interrupt:		
	movwf	intsave_w	; save W
	swapf	STATUS, W	; save STATUS
	clrf	STATUS
	movwf	intsave_status	
	movf	PCLATH, W	; save PCLATH
	movwf	intsave_pclath
	clrf	PCLATH

	; only Timer0 interrupt used, so no flag testing
	
	; speaker driving
	banksel	beeping
	movf	beeping, W
	bz	not_beeping
	banksel	ticks
	movf	ticks, W
	andlw	(1 << speaker_bit)
	banksel	speaker_port
	movwf	speaker_port
not_beeping:

	; step fraction-of-second counter
	banksel ticks
	incf	ticks, F	; our ticks register serves as another scaler
	btfss	STATUS, Z
	 goto	not_tick_roll
	movlwf	0xFF - (ticks_per_second), ticks
	
	; a second has passed - start adjusting timers
	; hm, should we have individual tick-counters for each timer instead?
	clrf	STATUS			; bank 0
step_one_time:				; returns to here for each bank
	btfss	mode_a, mode_run_bit
	 goto	done_one_time
	btfsc	mode_a, mode_ascend_bit	; counting up or down
	 goto	ascend_one_time
;descend_one_time:
	call	dectime			; counting down
	movf	hours_a, F		; test for reaching 0
	bnz	done_one_time
	movf	minutes_a, F
	bnz	done_one_time
	movf	seconds_a, F
	bnz	done_one_time

	bcf	mode_a, mode_run_bit	; reached 0: stop timing,
	banksel	beeping
	movlwf	1, beeping		; start alarm

	goto	done_one_time
ascend_one_time:
	call	inctime
	movf	hours_a, W		; test for reaching 99:59:59
	xorlw	0x99
	bnz	done_one_time
	movf	minutes_a, W
	xorlw	0x59
	bnz	done_one_time
	movf	seconds_a, W
	xorlw	0x59
	bnz	done_one_time
	btfsc	STATUS, Z		; if maximum,
	 bcf	mode_a, mode_run_bit	; stop timer
	;goto	done_one_time
done_one_time:
	btfss	STATUS, RP0		; if we haven't reached bank 3, ...
	 goto	step_next_time
	btfss	STATUS, RP1
	 goto	step_next_time
	goto	done_all_times
step_next_time:
	movlw	(1 << RP0)		; ... increment bank selector
	addwf	STATUS, F
	goto	step_one_time
done_all_times:
not_tick_roll:

	; display phase ticking
	banksel	d_phase_ticks
	incf	d_phase_ticks, F
	btfss	STATUS, Z
	goto	end_phase_tick_roll
	movlwf	0xFF - (ticks_per_d_phase), d_phase_ticks
	decfsz	s_d_phase, F
	goto	end_phase_tick_roll
	movlwf	d_phase_last, s_d_phase	; start over
	;movf	s_user_bank, W		; increment bank selection
	;addlw	(1 << RP0)
	;andlw	(1 << RP0) | (1 << RP1)
	;movwf	s_user_bank
	
end_phase_tick_roll:

	banksel INTCON
	bcf	INTCON, T0IF	; clear interrupt flag
	
	movf	intsave_pclath, W
	movwf	PCLATH
	swapf	intsave_status, W
	movwf	STATUS
	swapf	intsave_w, F
	swapf	intsave_w, W
	retfie
	
set_button:
	banksel	beeping
	movf	beeping, W
	clrf	beeping
	skpz
	 return
	
	movf	s_user_bank, W
	movwf	STATUS		; select bank for current selected timer
	bcf	INTCON, GIE	; disable interrupts while we twiddle the value
	
	swapf	hours_a, W	; left shift hours by digit
	andlw	0xF0		
	movwf	hours_a
	
	swapf	minutes_a, W	; move minutes high into hours low
	andlw	0x0F
	iorwf	hours_a, F
	
	swapf	minutes_a, W	; left shift minutes
	andlw	0xF0		
	movwf	minutes_a
	
	swapf	seconds_a, W	; move seconds high into minutes low
	andlw	0x0F
	iorwf	minutes_a, F

	swapf	seconds_a, W	; left shift seconds
	andlw	0xF0		
	movwf	seconds_a
	
	banksel	bcd_input
	swapf	bcd_input, W	; read digit input, 4 high bits
	andlw	0xF		; ignore other bits
	movwf	s_tmp_0		; stash in bank-shared register
	movf	s_user_bank, W	; switch back to timer's bank
	movwf	STATUS
	movf	s_tmp_0, W	; retrieve read digit
	iorwf	seconds_a, F 	; put in low digit of seconds
	bcf	mode_a, mode_ascend_bit	; if user-set, then assume descending;
					; start button routine checks for 0
	
	;incf	input_target, F		; cycle input target
	;movlw	input_target_count
	;xorwf	input_target, W
	;btfsc	STATUS, Z
	; xorwf	input_target, F
	
	
	bsf	INTCON, GIE	; enable interrupts
	call	delay
	banksel	set_button_port
iwait:	btfss	set_button_port, set_button_bit
	goto 	iwait
	movlwf	0xFF, PORTD
	call	delay
	return

start_button:
	banksel	beeping
	movf	beeping, W
	clrf	beeping
	skpz
	 return
	
	bcf	INTCON, GIE	; disable interrupts while we twiddle the mode
	movf	s_user_bank, W
	movwf	STATUS		; select bank for current selected timer
	movlw	(1 << mode_run_bit)
	xorwf	mode_a, F	; toggle run bit
	
	btfss	mode_a, mode_run_bit	; if now running, test for zero
	 goto start_finish
	movf	seconds_a, F
	btfss	STATUS, Z
	 goto	start_finish
	movf	minutes_a, F
	btfss	STATUS, Z
	 goto	start_finish
	movf	hours_a, F
	btfss	STATUS, Z
	 goto	start_finish
	bsf	mode_a, mode_ascend_bit	; starting at 0, so count ascending
	
start_finish:
	bsf	INTCON, GIE	; enable interrupts
	call	delay	; debounce
	banksel	run_button_port
dwait:	btfss	run_button_port, run_button_bit	; wait for button up
	goto 	dwait
	banksel	PORTD
	movlwf	0xFF, PORTD	; make wait interval visible
	call	delay	; debounce
	return

;; segment layout:
;;  0
;; 1 5
;;  6
;; 7 4
;;  2  3

display:
	banksel	display_data
	btfsc	display_data, 3
	goto	d8_15
; d0_7:
	btfsc	display_data, 2
	goto	d4_7
; d0_3:	
	btfsc	display_data, 1
	goto	d2_3
; d0_1:
	btfss	display_data, 0
	retlw	b'10110111' ; 0
	retlw	b'00110000' ; 1
d2_3:		
	btfss	display_data, 0
	retlw	b'11100101' ; 2
	retlw	b'01110101' ; 3
d4_7:
	btfsc	display_data, 1
	goto	d6_7
; d4_5:
	btfss	display_data, 0
	retlw	b'01110010' ; 4
	retlw	b'01010111' ; 5
d6_7:		
	btfss	display_data, 0
	retlw	b'11010111' ; 6
	retlw	b'00110001' ; 7
d8_15:
	btfsc	display_data, 2
	goto	d12_15
; d8_11:	
	btfsc	display_data, 1
	goto	d10_11
; d8_9:
	btfss	display_data, 0
	retlw	b'11110111' ; 8
	retlw	b'01110111' ; 9
d10_11:		
	btfss	display_data, 0
	retlw	b'11110011' ; A
	retlw	b'11010110' ; b
d12_15:
	btfsc	display_data, 1
	goto	d14_15
; d12_13:
	btfss	display_data, 0
	retlw	b'10000111' ; C
	retlw	b'11110100' ; d
d14_15:		
	btfss	display_data, 0
	retlw	b'11000111' ; E
	retlw	b'11000011' ; F


delay:	banksel delay_c1
loopb:	decfsz	delay_c1, F
	goto loopb
	decfsz	delay_c2, F
	goto loopb
	return

dectime:
	movf	seconds_a, W	; dec seconds
	call	decbcd
	movwf	seconds_a
	xorlw	0xF9		; test for borrow
	skpz
	 return
	movlwf	0x59, seconds_a
	movf	minutes_a, W	; dec minutes
	call	decbcd
	movwf	minutes_a
	xorlw	0xF9		; test for borrow
	skpz
	 return
	movlwf	0x59, minutes_a
	movf	hours_a, W	; dec hours
	call	decbcd
	movwf	hours_a
	return

inctime:
	movf	seconds_a, W	; inc seconds
	call	incbcd
	movwf	seconds_a
	xorlw	0x60		; test for carry
	skpz
	 return
	movlwf	0, seconds_a
	movf	minutes_a, W	; inc minutes
	call	incbcd
	movwf	minutes_a
	xorlw	0x60		; test for carry
	skpz
	 return
	movlwf	0, minutes_a
	movf	hours_a, W	; inc hours
	call	incbcd
	movwf	hours_a
	return


incbcd:		; supplied by Ben Jackson
        addlw d'7'
        skpdc
        addlw (d'256'-d'6')
        return

decbcd:
	addlw (d'256'-d'1')
	skpdc
	addlw (d'256'-d'6')
	return

	end
