
VERSIONSTRING equ "Button v2.21"

!
! Filename:     button.asm
! Module name:  Button
! Description:  coke button i/o driver
!
! Commands:
!   string QueryVersion () - query version string
!   byte QueryPattern () - read the current button state
!   Push (byte buttonnum) - do a virtual button push
!   ReleaseBeverage (byte buttonnum) - drop a cold one
!
! IPC:
!   none
!
! Timers:
!   TIMER_Button_Push - timer for virtual button pushing
!   TIMER_Button_Beverage - timer for beverage dropping
!
! Hooks:
!   HOOK_USER - interrupt hook for button checking/debouncing
!
! Comments:
!   none yet.
!
! Hardware Notes:
!   To read the state of the buttons, read from PORT_Button,
!   and AND with BUTTON_MASK.  Bits are active high.  To do a
!   virtual push, write the soda stack number (1-6) to PORT_Button.
!   If you read from PORT_Button_Pushed, real button pushes
!   are read back.  If you read from PORT_Button_Latched,
!   the virtual push is read.
!
! History:
!   bv 9/13/98	- initial revision
!   bv 9/17/98	- added output routines
!   bv 5/27/99	- converted to cokeOS v3 module
!   bv 6/22/99	- re-wrote
!   bv 7/06/99	- added QueryPattern and ReleaseBeverage, changed Push
!		- to avoid multiple simultaneous pushes
!   bv 7/16/99	- changed for new IPC style

	include <coke/coke.inc>

; direct page variables

button_debounce0	equ DIRPAGE_Button
button_debounce1	equ DIRPAGE_Button+1
button_debounce2	equ DIRPAGE_Button+2
button_debounce3	equ DIRPAGE_Button+3
button_debounce4	equ DIRPAGE_Button+4
button_debouncestate	equ DIRPAGE_Button+5
button_state		equ DIRPAGE_Button+6

; constants

DEBOUNCETIME	equ 7		; seven debounce cycles, at 8 ms each...
NUMBUTTONS	equ 5		; five buttons
BUTTON_MASK	equ %00011111 	; five buttons right now
NUMSTACKS	equ 6		; six soda stacks
BUTTON_HOLDTIME	equ 150		; hold down (and up) for 1200 ms
BEVERAGETIMEOUT	equ 250		; delay between AllowPurchase and Push

; IPC EntryPoint

EntryPoint	subroutine
	cmpa	#IPC_Coke_Hello
	beq	.init
	cmpa	#IPC_Coke_Goodbye
	beq	.free
	sec
	rts

.init
	clra			; the button outputs start up pressed
	staa	PORT_Button	; so first, we've got to un-press them
	
	clr	button_pushing		; not pushing
	clr	button_state		; clear stuff
	clr	button_debouncestate

	ldaa	#ID_Button		; install the IPC handler
	ldx	#EntryPoint
	jsr	Coke_RegisterIPC

	ldx	#CommandTable		; install the command table
	jsr 	Coke_InstallCommandTable
	
	ldaa	#HOOK_USER		; install the hook routine
	ldx	#HookFunctionEnd
	jsr	Coke_InstallHook
	clc
	rts

.free
	ldaa	#HOOK_USER		; remove interrupt hook
	ldx	#HookFunctionEnd
	jsr	Coke_RemoveHook
	ldaa	#TIMER_Button_Push	; remove timer
	jsr	Coke_RemoveTimer
	ldaa	#TIMER_Button_Beverage	; remove other timer
	jsr	Coke_RemoveTimer
	ldx	#CommandTable		; remove commands
	jsr	Coke_RemoveCommandTable
	ldaa	#ID_Button		; remove IPC handler
	jsr	Coke_UnregisterIPC
	clra				; stop pressing any buttons
	staa	PORT_Button
	clc
	rts


; Command_Push (byte buttonnum) starts pushing a soda stack.

Command_Push subroutine
	jsr	Coke_GetChar	; get buttonnum
	tsta			; is it zero?
	beq	.failnum	; if so, fail
	cmpa	#NUMSTACKS	; is it too big?
	bhi	.failnum	; if so, fail
	tst	button_pushing	; are we already pushing?
	bne	.failpushing	; if so, don't do it again!
	
	staa	PORT_Button		; store the push
	inc	button_pushing		; we be pushing

	ldaa	#TIMER_Button_Push	; install the timer
	ldx	#PushTimerFunction
	ldy	#BUTTON_HOLDTIME
	jsr 	Coke_SetTimer

	ldaa	#COKE_ACK
	rts
.failnum
	ldab	#ERROR_Button_InvalidStackNumber
	ldaa	#COKE_FAIL
	rts
.failpushing
	ldab	#ERROR_Button_AlreadyPushing
	ldaa	#COKE_FAIL
	rts


; Command_ReleaseBeverage (byte num) drops a soda automatically,
; if the Money module has been loaded.

Command_ReleaseBeverage subroutine
	jsr	Coke_GetChar		; get buttonnum
	tsta				; is it zero?
	beq	.failnum		; if so, fail
	cmpa	#NUMSTACKS		; is it too big?
	bhi	.failnum		; if so, fail
	tst	button_pushing		; are we already pushing?
	bne	.failpushing		; if so, don't do it again!
	tab				; put buttonnum in b

	ldaa	#CMD_Money_AllowPurchase
	jsr	Coke_InvokeCommandMem	; try to invoke the allow purchase cmd
	cmpa	#COKE_ACK		; did we get an acknowledge?
	bne	.failmod		; if not, fail

	ldaa	#TIMER_Button_Beverage	; set up the timer
	ldx	#BeverageTimerFunction
	ldy	#BEVERAGETIMEOUT
	jsr	Coke_SetTimer		; install the timer

	ldaa	#COKE_ACK
	rts
.failnum
	ldab	#ERROR_Button_InvalidStackNumber
	ldaa	#COKE_FAIL
	rts
.failmod
	ldab	#ERROR_Button_MoneyModuleNotLoaded
	ldaa	#COKE_FAIL
	rts
.failpushing
	ldab	#ERROR_Button_AlreadyPushing
	ldaa	#COKE_FAIL
	rts


; Command_QueryPattern () returns the current button pattern.

Command_QueryPattern subroutine
	ldaa	PORT_Button_Pushed	; get the current push
	anda	#BUTTON_MASK		; zero invalid bits
	staa	button_scratch		; store byte
	ldaa	#COKE_QUERY		; prepare to return a query response
	ldab	#1
	ldx	#button_scratch	
	rts


; PushTimerFunction sends out virtual button pushes.

PushTimerFunction subroutine
	psha
	pshx

	ldaa	button_pushing		; get pushing status
	cmpa	#1			; are we pushing?
	beq	.push			; if so, release.
	
	ldaa	#TIMER_Button_Push	; if not, we're done with the uptime
	jsr	Coke_RemoveTimer	; remove this timer
	clr	button_pushing		; no longer pushing
	bra	.done			; outa here
.push
	clra
	staa	PORT_Button		; let up the button
	inc	button_pushing		; now, wait the uptime.
.done
	pulx
	pula
	rts


; BeverageTimerFunction waits some time before AllowPurchase and Push
; button number is param in a

BeverageTimerFunction subroutine
	psha
	pshx

	staa	button_scratch		; store the parameter to Push cmd

	ldx	#button_scratch		; get ptr to parameter "list"
	ldaa	#CMD_Button_Push	; get command code
	jsr	Coke_InvokeCommandMem	; do the command

	ldaa	#TIMER_Button_Beverage	; remove this timer function
	jsr	Coke_RemoveTimer

	pulx
	pula
	rts


; _debounce macro is used to detect and debounce a particular button.
; It is so much easier and quicker to expand it inline like this than to
; try to make a loop...
; _debounce #bitmask button_debouncevariable #MESSAGE_ButtonHit
;           #MESSAGE_ButtonReleased

	mac	_debounce

	brset	button_debouncestate ,{1},.debounce
						; are we debouncing this bit?
	bitb	{1}				; if not, did this bit change?
	beq	.endbit
	clr	{2}	; set debounce count
	bset	{2}, #DEBOUNCETIME
	bset	button_debouncestate ,{1}	; say that we are debouncing it
	bra	.endbit
.debounce
	bitb	{1}				; still different than state?
	bne	.stilldifferent
	bclr	button_debouncestate ,{1}	; if not, forget about it!
	bra	.endbit
.stilldifferent
	dec	{2}				; lower debouncecount
	bne	.endbit				; if not zero, we're outa here
	bclr	button_debouncestate ,{1}	; we win!  clear debounce state
	bita	{1}				; is it being hit or released?
	xgdx					; stash stuff in x
	beq	.zero
	bset	button_state, {1}		; if hit, set state bit
	ldaa	{3}
	bra	.msg
.zero
	bclr	button_state, {1}		; if release, clear state bit
	ldaa	{4}
.msg
	jsr	Coke_SendMessage		; and send message
	xgdx
.endbit
	endm

	
; HookFunction is called during an interrupt proc to scan and
; debounce the buttons, and send out the appropriate messages.

HookFunction	subroutine
	ldaa	PORT_Button_Pushed	; get the current press
	anda	#BUTTON_MASK		; set inactive bits to 0
	tab				; put press in b
	eorb	button_state		; xor of press and state in b
	bne	HookDosomething		; press != state, do something
	tst	button_debouncestate	; are we debouncing anything?
	bne	HookDosomething		; if not, we leave early
HookFunctionEnd
	_endhook HookFunction

HookDosomething
	_debounce #$1, button_debounce0, #MESSAGE_Button_Button0Hit, #MESSAGE_Button_Button0Released

	_debounce #$2, button_debounce1, #MESSAGE_Button_Button1Hit, #MESSAGE_Button_Button1Released

	_debounce #$4, button_debounce2, #MESSAGE_Button_Button2Hit, #MESSAGE_Button_Button2Released

	_debounce #$8, button_debounce3, #MESSAGE_Button_Button3Hit, #MESSAGE_Button_Button3Released

	_debounce #$10, button_debounce4, #MESSAGE_Button_Button4Hit, #MESSAGE_Button_Button4Released

	jmp	HookFunctionEnd
	


; version stuff

	_versionstuff

	
; command table

CommandTable
	dc.b	CMD_Button_QueryVersion
	dc.w	Command_QueryVersion
	dc.b	CMD_Button_QueryPattern
	dc.w	Command_QueryPattern
	dc.b	CMD_Button_Push
	dc.w	Command_Push
	dc.b	CMD_Button_ReleaseBeverage
	dc.w	Command_ReleaseBeverage

	dc.b	0


; data segment

button_pushing		ds 1
button_scratch		ds 2
