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 ; 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