;******************************************************************************
;       Push Buttons & Path Switcher (with abbreviated real time clock)
;
;
;       Length: >=74 bytes (depends upon number of buttons & clock type)
;       Author: Craig Webb
;       Written: 98/8/17
;
;	This program implements a software time clock virtual peripheral
;	that keeps a 16 bit count of elapsed time in milliseconds.
;	The option is available to include seconds, minutes, hours and even
;	days to this clock if desired.
;	The code takes advantage of the SX's internal RTCC-driven interrupt
;	to operate in the background while the main program loop is executing.
;
;******************************************************************************
;
;****** Assembler directives
;
; uses: SX28AC, 2 pages of program memory, 8 banks of RAM, high speed osc.
;       operating in turbo mode, with 8-level stack & extended option reg.
;                
		DEVICE  pins28,pages2,banks8,oschs
		DEVICE  turbo,stackx,optionx
		ID      'Buttons'			;program ID label
		RESET   reset_entry			;set reset/boot address
;
;******************************* Program Variables ***************************
;
;****** Program Parameters
;
clock_type	=	0			;16 bit msec count only
;clock_type	=	1			;include sec, min, hours
;clock_type	=	2			;include day counter
;
num_buttons	=	2			;number of buttons (1-4)
;
;******	Program Constants
;
int_period	=	163			;period between interrupts
;
hold_bit	=	4-(num_buttons/2)	;debounce period = 2^hold_bit msec
;
tick_lo		=	80			;50000 = msec instruction count
tick_hi		=	195			; for 50MHz, turbo, prescaler=1
;
mspersec_hi	=	1000/256		;msec per second hi count
mspersec_lo	=	1000-(mspersec_hi*256)	;msec per second lo count
;
;******	Port definitions
;
button0		EQU	RB.0			;Push button 0
button1		EQU	RB.1			;Push button 1
button2		EQU	RB.2			;Push button 2
button3		EQU	RB.3			;Push button 3
;
;****** Register definitions
;
		ORG	8			;start of program registers
main            =	$			;main bank
;
temp            DS	1			;temporary storage
temp2		DS	1
;
		ORG	010H			;bank0 variables
clock		EQU	$			;clock bank
buttons		EQU     $                       ;push button bank
;
time_base_lo	DS	1			;time base delay (low byte)
time_base_hi	DS	1			;time base delay (high byte)
msec_lo		DS	1			;millisecond count (low)
msec_hi		DS	1			;millisecond count (high)

		IF	clock_type>0		;do we want sec, min, hours?
seconds		DS	1			;seconds count
minutes		DS	1			;minutes count
hours		DS	1			;hours count
		ENDIF				

		IF	clock_type>1		;do we want day count?
days		DS	1			;days count
		ENDIF				
;
;
debounce0	DS	1			;push button 0 debounce count
debounce1	DS	1			;push button 1 debounce count
debounce2	DS	1			;push button 2 debounce count
debounce3	DS	1			;push button 3 debounce count
pbflags		DS	1			;push button status flags
pb0_pressed	EQU	pbflags.0		;push button 0 action status
pb1_pressed	EQU	pbflags.1		;push button 1 action status
pb2_pressed	EQU	pbflags.2		;push button 2 action status
pb3_pressed	EQU	pbflags.3		;push button 3 action status
pb0_down	EQU	pbflags.4		;push button 0 down status
pb1_down	EQU	pbflags.5		;push button 1 down status
pb2_down	EQU	pbflags.6		;push button 2 down status
pb3_down	EQU	pbflags.7		;push button 3 down status
;
;*************************** INTERRUPT VECTOR ******************************
;
; Note: The interrupt code must always originate at 0h.
;	A jump vector is not needed if there is no program data that needs
;	to be accessed by the IREAD instruction, or if it can all fit into
;	the lower half of page 0 with the interrupt routine.
;
		ORG     0				;interrupt always at 0h
;		JMP	interrupt			;interrupt vector
;
;**************************** INTERRUPT CODE *******************************
;
; Note: Care should be taken to see that any very timing sensitive routines
;       (such as adcs, etc.) are placed before other peripherals or code
;       which may have varying execution rates (like the software clock, for
;	   example).
;
interrupt						;beginning of interrupt code
;
;****** Virtual Peripheral: Time Clock
;
; This routine maintains a real-time clock count (in msec) and allows processing
; of routines which only need to be run once every millisecond.
;
;	Input variable(s) : time_base_lo,time_base_hi,msec_lo,msec_hi
;				seconds, minutes, hours, days
;	Output variable(s) : msec_lo,msec_hi
;				seconds, minutes, hours, days
;	Variable(s) affected : time_base_lo,time_base_hi,msec_lo,msec_hi
;				seconds, minutes, hours, days
;	Flag(s) affected : 
;	Size : 17/39/45 bytes (depending upon clock type)
;		+ 1 if bank select needed
;	Timing (turbo) : [99.9% of time] 14 cycles
;			 [0.1% of time] 17/39/45 cycles (or less)
;			+ 1 if bank select needed
;
;		BANK	clock			;select clock register bank
		MOV	W,#int_period		;load period between interrupts
		ADD	time_base_lo,W		;add it to time base
		SNC				;skip ahead if no underflow
		INC	time_base_hi		;yes overflow, adjust high byte
		MOV	W,#tick_hi		;check for 1 msec click
		MOV	W,time_base_hi-W	;Is high byte above or equal?
		MOV	W,#tick_lo		;load instr. count low byte
		SNZ				;If hi byte equal, skip ahead
		MOV	W,time_base_lo-W	;check low byte vs. time base
		SC				;skip ahead if low

;commented out because of path_switcher/pushbutton routines which use msec count
;		JMP	:done_clock		;If not, end clock routine
		JMP	done_pbs		;If not, end clock routine

:got_tick	CLR	time_base_hi		;Yes, adjust time_base reg.'s
		SUB	time_base_lo,#tick_lo	; leaving time remainder
		INCSZ	msec_lo			;And adjust msec count
		DEC	msec_hi			; making sure to adjust high
		INC	msec_hi			; byte as necessary

		IF	clock_type>0		;do we want sec, min, hours?
		MOV	W,#mspersec_hi		;check for 1000 msec (1 sec tick)
		MOV	W,msec_hi-W		;Is high byte above or equal?
		MOV	W,#mspersec_lo		;load #1000 low byte
		SNZ				;If hi byte equal, skip ahead
		MOV	W,msec_lo-W		;check low byte vs. msec count
		SC				;skip ahead if low
		JMP	:done_clock		;If not, end clock routine
		INC	seconds			;increment seconds count
		CLR	msec_lo			;clear msec counters
		CLR	msec_hi			;
		MOV	W,#60			;60 seconds per minute
		MOV	W,seconds-W		;are we at minute tick yet
		JNZ	:done_clock		;if not, jump
		INC	minutes			;increment minutes count
		CLR	seconds			;clear seconds count
		MOV	W,#60			;60 minutes/hour
		MOV	W,minutes-W		;are we at hour tick yet?
		JNZ	:done_clock		;if not, jump
		INC	hours			;increment hours count
		CLR	minutes			;clear minutes count
		ENDIF				;<if> we wanted sec, min, hours

		IF	clock_type>1		;do we want to count days?
		MOV	W,#24			;24 hours per day
		MOV	W,hours-W		;are we at midnight?
		JNZ	:done_clock		;if not, jump
		INC	days			;increment days count
		CLR	hours			;clear hours count
		ENDIF				;<if> we wanted day count
:done_clock
;
;****** Virtual Peripheral: Path Switch
;
; This routine allows alternating execution of multiple modules which don't
; need to be run during every interrupt pass in order to reduce the overall
; execution time of the interrupt on any given pass (i.e. it helps the code
; run faster).
; This version runs with the software clock virtual peripheral msec_lo variable
; allowing altenation between the switch positions once each millisecond.
;
;       Input variable(s) : msec_lo
;       Output variable(s) : 
;       Variable(s) affected : 
;       Flag(s) affected : 
;	Size : 3 bytes + 1 bytes per jump location
;       Timing (turbo) : 8 cycles
;
:path_switch	MOV	W,msec_lo		;load switch selector byte
		AND	W,#00000011b		;keep low 2 bits - 4 position
		JMP	PC+W			;jump to switch position pointer
:pos0		JMP	pb0			;pushbutton 0 checking routine
:pos1		JMP	pb1			;pushbutton 1 checking routine
:pos2		JMP	pb2			;pushbutton 2 checking routine
:pos3		JMP	pb3			;pushbutton 3 checking routine
;
;
;****** Virtual Peripheral: Push Buttons*
;
; This routine monitors any number of pushbuttons, debounces them properly
; as needed, and flags the main program code as valid presses are received.
; *Note: this routine requires the Time Clock virtual peripheral or similar
;	 pre-processing timer routine.
;
;       Input variable(s) : pb0_down,pb1_down,debounce0,debounce1
;       		    pb2_down,pb3_down,debounce2,debounce3
;       Output variable(s) : pb0_pressed, pb1_pressed, pb2_pressed, pb3_pressed
;       Variable(s) affected : debounce0, debounce1, debounce2, debounce3
;       Flag(s) affected : pb0_down,pb1_down,pb0_pressed,pb1_pressed
;       		   pb2_down,pb3_down,pb2_pressed,pb3_pressed
;	Size : 12 bytes per pushbutton + actions (see below**)
;		+ 1 byte if path switch not used
;       Timing (turbo) : 7,10, or 12 cycles/pushbutton (unless path switch used)
;			 + actions (see below**)
;
pb0
;		BANK	buttons			;select bank (if not done elsewhere)
		JB	button0,:pb0_up		;button0 pressed?
		JB	pb0_down,:done_pb0	;yes, but is it new press?
		INC	debounce0		; and adjust debounce count
		JNB	debounce0.hold_bit,:done_pb0	;wait till long enough
		SETB	pb0_down		;yes, flag that button is down

;**If the button activity is short (a few bytes), it can fit here, though be
; careful that longest possible interrupt doesn't exceed int_period # of cycles.
;
; <short code segment can go here>
;
;**Otherwise, use this flag to process button press in main code (and don't
; forget to reset the flag once the button activity is complete).
		SETB	pb0_pressed		; and set pb0 action flag

		SKIP				;skip next instruction
:pb0_up		CLRB	pb0_down		;button up, clear flag
		CLR	debounce0		; and clear debounce count
:done_pb0
;
		JMP	done_pbs		;this needed only if path switch used


pb1		
		IF	num_buttons>1		;more than 1 push button?
;		BANK	buttons			;do bank select (if not done elsewhere)
		JB	button1,:pb1_up		;button1 pressed?
		JB	pb1_down,:done_pb1	;yes, but is it new press?
		INC	debounce1		; and adjust debounce count
		JNB	debounce1.hold_bit,:done_pb1	;wait till long enough
		SETB	pb1_down		;yes, flag that button is down

;**If the button activity is short (a few bytes), it can fit here, though be
; careful that longest possible interrupt doesn't exceed int_period # of cycles.
;
; <short code segment can go here>
;
;**Otherwise, use this flag to process button press in main code (and don't
; forget to reset the flag once the button activity is complete).
		SETB	pb1_pressed		; and set pb1 action flag

		SKIP				;skip next instruction
:pb1_up		CLRB	pb1_down		;button up, clear flag
		CLR	debounce1		; and clear debounce count
:done_pb1
;
		JMP	done_pbs		;this needed only if path switch used
		ENDIF				;more than 1 push button

pb2
		IF	num_buttons>2		;more than 2 push buttons?
;		BANK	buttons			;do bank select (if not done elsewhere)
		JB	button2,:pb2_up		;button2 pressed?
		JB	pb2_down,:done_pb2	;yes, but is it new press?
		INC	debounce2		; and adjust debounce count
		JNB	debounce2.hold_bit,:done_pb2	;wait till long enough
		SETB	pb2_down		;yes, flag that button is down

;**If the button activity is short (a few bytes), it can fit here, though be
; careful that longest possible interrupt doesn't exceed int_period # of cycles.
;
;**Otherwise, use this flag to process button press in main code (and don't
;  orget to reset the flag once the button activity is complete).
		SETB	pb2_pressed		; and set pb2 action flag

		SKIP				;skip next instruction
:pb2_up		CLRB	pb2_down		;button up, clear flag
		CLR	debounce2		; and clear debounce count
:done_pb2
;

		JMP	done_pbs		;this needed only if path switch used
		ENDIF				;more than 2 push buttons

pb3		
		IF	num_buttons>2		;more than 3 push buttons?
;		BANK	buttons			;do bank select (if not done elsewhere)
		JB	button3,:pb3_up		;button3 pressed?
		JB	pb3_down,:done_pb3	;yes, but is it new press?
		INC	debounce3		; and adjust debounce count
		JNB	debounce3.hold_bit,:done_pb3	;wait till long enough
		SETB	pb3_down		;yes, flag that button is down

;**If the button activity is short (a few bytes), it can fit here, though be
;  careful that longest possible interrupt doesn't exceed int_period # of cycles.
;
;**Otherwise, use this flag to process button press in main code (and don't
;  forget to reset the flag once the button activity is complete).
		SETB	pb3_pressed		; and set pb3 action flag

		SKIP				;skip next instruction
:pb3_up		CLRB	pb3_down		;button up, clear flag
		CLR	debounce3		; and clear debounce count
:done_pb3
		ENDIF				;more than 3 push buttons
;
done_pbs
;

done_int	mov     w,#-int_period     	;interrupt every 'int_period' clocks
		retiw                      	;exit interrupt
;
;******	End of interrupt sequence
;
;************************** RESET ENTRY POINT *****************************
;
reset_entry
;		PAGE	start			;Set page bits and then
;		JMP	start			; jump to start of code
;
;*********************
;* Main Program Code *
;*********************
;
start		mov     !rb,#%00001111          ;Set RB in/out directions
		CLR     FSR                     ;reset all ram starting at 08h
:zero_ram	SB      FSR.4	                ;are we on low half of bank?
		SETB    FSR.3                   ;If so, don't touch regs 0-7
		CLR     IND                     ;clear using indirect addressing
		IJNZ    FSR,:zero_ram           ;repeat until done

		MOV     !OPTION,#%10011111      ;enable rtcc interrupt
;
Main:loop
;
;the following code watches pb0-pb3 for presses and acts on them
button_check
;		BANK	buttons			;select pb bank
		MOV	W,pbflags		;load pushbutton flags
		AND	W,#00001111b		;keep only 'pressed' flags
		JZ	:no_press		;jump ahead if not pressed
		MOV	temp,W			;store flags temporarily
		CLR	temp2			;clear 2nd temp storage reg.
:which_pb	INC	temp2			;increment 2nd temp value
		RR	temp			;check which button
		SC				;skip ahead if not this one
		JMP	:which_pb		;keep looping
		MOV	W,--temp2		;get 2nd temp value (less 1)
		MOV	temp,W			;save it in temp
		MOV	W,#11110000b		;get clear mask for pbflags
		AND	pbflags,W		;clear all "pressed" flags
		MOV	W,temp			;get which button pressed
		JMP	PC+W			;Go do PB routines
:pb0		JMP	pb0_action		;do pb0 action	
:pb1		JMP	pb1_action		;do pb1 action	
:pb2		JMP	pb2_action		;do pb2 action	
:pb3		JMP	pb3_action		;do pb3 action	

:no_press
;
;	<main program code goes here>
;
		JMP     Main:loop			;back to main loop
;
pb0_action
;
;	<pb0 action here>
;
		JMP	Main:loop
;
pb1_action
;
;	<pb1 action here>
;
		JMP	Main:loop
;
pb2_action
;
;	<pb2 action here>
;
		JMP	Main:loop
;
pb3_action
;
;	<pb3 action here>
;
		JMP	Main:loop
;
;***************
		END                             ;End of program code