; $Id: clock.asm,v 1.13 2001/05/13 21:45:11 dogcow Exp dogcow $
;
; General notes: this is dervied from the 99 minute timer code, but I've
; whacked close to every single line of code there, so it's MINE! MINE!
;
; The "check 8 seconds" doesn't get triggered on second 00; this reduces the
; maximum number of ticks/minute to 7. (This can be fixed by moving the
; incf halfseconds, f to 'chkforminute' instead of right at 'checkseconds'.)
;
		list	p=16f84a, r=dec
		include "p16f84a.inc"

		org	0
		goto	init	;power-on-reset
		goto	start	;interrupt vector (shouldn't be called)

numticksperminute equ 6	; how many ticks occur in a minute?

regbase		equ	0ch	; vars start at 0ch on the 16C84.
oldkey		equ	regbase+0
newkey		equ	regbase+1	
counter_4ms	equ	regbase+2	;incremented every 4 msec
counter_100ms	equ	regbase+3	;incremented every 100 msec
halfseconds	equ	regbase+4	
minutes		equ	regbase+5	
hours		equ	regbase+6
minuteticks	equ	regbase+7	
hourticks	equ	regbase+8
tmr_comp	equ	regbase+9	;tmr0 comparator register
scrtch0		equ	regbase+10
led1		equ	regbase+11
led2		equ	regbase+12

; pin assignments:
; output:
;  ra1,2,3   - minute/hour/ageofman outputs (to power Q's/solenoids)
;  rb0,1,2,3 - minute/tenminute/hour/resettozero inputs (from pushbuttons)
;  rb4,5,6   - strobe,clock,data outputs (to 4094s)
;  ra0, rb7: unused
;
; bit positions inside digit_inc and newkey
minute_inc	equ	0	;aka rb0
tenminute_inc	equ	1	;aka rb1 XXX NOT CURRENTLY IMPLEMENTED XXX
hour_inc	equ	2	;aka rb2
reset_minutes	equ	3	;aka rb3
button_PORT	equ	PORTB
keymask		equ	0fh	; mask for lower keys

; the 4094 8-bit shift registers
strobe_4094	equ	4	;aka rb4
clock_4094	equ	5	;aka rb5
data_4094	equ	6	;aka rb6
shiftreg_PORT	equ	PORTB

; the solenoids that kick the escapements
; note that since the output solenoids are on their own PORT, we can do
; bcf/bsfs without hitting a read latch we care about.
minute_sol	equ	1	;aka ra1
hour_sol	equ	2	;aka ra2
aom_sol		equ	3	;aka ra3 (age of man relay)
solenoid_PORT	equ	PORTA

;7 segments decoded data. lo is segment on & hi is segment off
get_seg		addwf	PCL, f
;			ABCDEFGPb
		retlw	00000011b	;0
		retlw	10011111b	;1
		retlw	00100101b	;2
		retlw	00001101b	;3
		retlw	10011001b	;4
		retlw	01001001b	;5
		retlw	01000001b	;6
		retlw	00011111b	;7
		retlw	00000001b	;8
		retlw	00011001b	;9
		retlw	00010001b	;A
		retlw	11000001b	;b
		retlw	01100011b	;C
		retlw	10000101b	;d
		retlw	01100001b	;E
		retlw	01111001b	;F
dec_pt		equ	11111110b	;decimal point bit

getticksperhour	movf	hours, w
		addwf	PCL, f
		retlw	5	;00
		retlw   4	;01
		retlw   3	;02
		retlw   4	;03
		retlw   2	;04
		retlw   2	;05
		retlw   2	;06
		retlw   2	;07
		retlw   2	;08
		retlw   2	;09
		retlw   4	;10
		retlw   4	;11
		retlw   4	;12
		retlw   3	;13
		retlw   3	;14
		retlw   1	;15
		retlw   2	;16
		retlw   2	;17
		retlw   3	;18
		retlw   3	;19
		retlw   2	;20
		retlw   4	;21
		retlw   4	;22
		retlw   5	;23

; clear vars,
; set the ports to the proper impedence/io states,
; and frob prescaler to make timer0 run more slowly.
;
init		clrf	hours
		clrf	minutes
		clrf	halfseconds
start		clrf	oldkey
		clrf	newkey
		clrf	counter_4ms
		clrf	counter_100ms
; set up the TRIS flags properly - have to bank-switch to proper region
		bsf	STATUS, RP0
;
		clrf	TRISA		;lazy way "all ports are out"
		movlw	00001111b	;ports 0-3 are pb input
		movwf	TRISB
;
		movlw   11000011b       ;tmr0 enable with 1:16 prescaler
		movwf	OPTION_REG	;see 2.2.2.2 in 16f84a datasheet
		clrf	INTCON		;we don't want no steenking interrupts!
;
		bcf	STATUS, RP0	;bank-switch back

; and now, the timer bits
		clrf	TMR0		
; why 250? each isn takes 4 cycles, so with a 4mhz processor, 1m isn/sec.
; with 1:16 prescaler, 16 * 250 / 1e6 Hz = 4e-3 seconds
		movlw	250
		movwf	tmr_comp
		goto	resetticks	;have to populate ticks/hour&min
;
; and now, the main main time-wasting loop
;
main		movf	tmr_comp, w	;is tmr0 = tmr_comp (4 msec over?)
		xorwf	TMR0, w
		btfss	STATUS, Z	;skip if = 250
		goto	main
		movlw	250
		addwf	tmr_comp, f	;update 4 msec compare register
		incf	counter_4ms, f
		movlw	25
		xorwf	counter_4ms, w	;is 4ms * 25 = 100ms over ?
		btfss	STATUS, Z	;skip if = 100ms
		goto	main
		clrf	counter_4ms
; 100ms over - time to check the status of the buttons!
; 
; in order for a button to register, the key has to have been pressed
; for two successive 100ms intervals. We invert the active-low
; bits to positive so we can just AND oldkey and newkey together.
;
check_buttons	movf    newkey, w       
		movwf   oldkey		; save old key
		comf    button_PORT, w	; invert keys, since active low
		movwf   newkey		; save now active-high key
;		clrf	keyaction
		andwf   oldkey, f	; and AND'em to oldkey in place
;		movlw	keymask
;		andwf	oldkey, f	; just in case we pick up spurious bits
					; (I hope this ain't really needed)
;
; and back to the horribly important business of keeping the counters up
                incf    counter_100ms, f
                movlw   5
                xorwf   counter_100ms, w ;is 500ms done?
                btfss   STATUS, Z       
                goto    main		;nope, 500ms ain't done.
;
; huzzah! welcome to 500ms.		
; all sorts of crap gets done here:
;   turn off solenoids if it's an odd halfsecond;
;   do clock ticks on multiples of 8 seconds ;
;   do minute->hour rollover and stuff if needed;
;   and finally, display the time on the LEDs.
;
		clrf	counter_100ms	;half a second over!

		movf	oldkey, w	;have buttons been pressed?
		btfsc	STATUS, Z	
		goto	checkseconds	; nope.
; so, we got button presses. buttons are active high.
		btfss	oldkey, minute_inc
		goto	chkhourbutton   ; advance the minute!
		clrf	halfseconds
		incf	minutes, f
		goto	chkminuterollover
chkhourbutton	btfss	oldkey, hour_inc
		goto	chkresetbutton
		incf	hours, f	; inc hours, don't reset minute/hsec
		goto	chkhourrollover
chkresetbutton	btfss	oldkey, reset_minutes
		goto	checkseconds
		clrf	halfseconds
		clrf	minutes
		goto	chkhourrollover	; and this'll fall into 

checkseconds	incf	halfseconds, f
; if it's a odd half-second, turn off solenoids
		movlw	1
		andwf	halfseconds, w
		btfsc	STATUS,Z
		goto	check8secs
; actually turn off the solenoids
		bcf	solenoid_PORT, minute_sol
		bcf	solenoid_PORT, hour_sol
; check and see if a multiple of 8 seconds has elapsed
check8secs	movlw	15
		andwf	halfseconds, w
		btfss	STATUS, Z
		goto	chkforminute	; nope, don't worry about tocking
; tick if need be...
		movf	minuteticks, f
		btfsc	STATUS, Z
		goto	checkhourticks	;nope, no minute ticks needed
		bsf	solenoid_PORT, minute_sol
		decf	minuteticks, f
checkhourticks	movf	hourticks, f
		btfsc	STATUS, Z
		goto	chkforminute	;nope, no hour ticks needed
		bsf	solenoid_PORT, hour_sol
		decf	hourticks, f
;
chkforminute	movlw	120
		xorwf	halfseconds, w	;is 1 minute over ?
		btfss	STATUS, Z	
		goto	displaytime	;nope.
; roll over minutes, and possibly hours
		clrf	halfseconds
		incf	minutes, f
chkminuterollover movlw	60
		xorwf	minutes, w	;is 60 minutes over?
		btfss	STATUS, Z
		goto	resetticks	;reset minutetick & hourtick
; update hours
		incf	hours, f
chkhourrollover	movlw	24
		xorwf	hours, w
		btfsc	STATUS, Z
		clrf	hours
resetticks	call	getticksperhour
		movwf	hourticks
		movlw	numticksperminute
		movwf	minuteticks
; and fall through to "display the time on the LEDs"
displaytime	movlw	255		;all segs off
		movwf	led1
		movf	hours, w
		call	get_seg
		movwf	led2
		movlw	1
		andwf	halfseconds, w
		btfss	STATUS,Z
		goto	xmittoleds
		movlw	15
		andwf	minutes, w
		call	get_seg
		movwf	led2
		swapf	minutes, w
		andlw	15
		call	get_seg
		movwf	led1
;transmit data from scrtch1 thr scrtch1 to display circuit (msb first).
xmittoleds	movlw	16		;no of bit to tx
		movwf	scrtch0
xmitloop	rlf	led2, f
		rlf	led1, f
		btfsc	STATUS, C		;data hi if cy=1 else lo
		goto	toggle_hi
		bcf	shiftreg_PORT, data_4094
		goto	toggle_clock
toggle_hi	bsf	shiftreg_PORT, data_4094
toggle_clock	nop					;delay
		nop			
		bsf	shiftreg_PORT, clock_4094
		nop
		nop
		bcf	shiftreg_PORT, clock_4094
		nop
		nop
		decfsz	scrtch0, f 		;next bit
		goto	xmitloop
		bsf	shiftreg_PORT, strobe_4094
		nop
		bcf	shiftreg_PORT, strobe_4094
		bcf	shiftreg_PORT, data_4094
		goto	main
;	
		__CONFIG _RC_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF
		end
