; BUTTON pin, port, state, delay, repeat_rate
; This routine debounces input from a pushbutton and jumps to an 
; "action" routine. The pin (0-7), port (0-2 for RA through RC), 
; action state of the input (0,1) are all specified at run time. 
; The delay and repeat rate are constants. 

; Device data and reset vector
        device  pic16c55,xt_osc,wdt_off,protect_off
        reset   start

        org     8
dbH     ds      1       ; MSB of 16-bit debounce counter. 
dbL     ds      1       ; LSB of 16-bit debounce counter. 
pin     ds      1       ; Pin no. (0-7). 
port    ds      1       ; Port no. (0-2). 
flags   ds      1       ; Bit flags for use by Button:
state   =       flags.3 ; Do action when button is in this state. 
rpt_sw  =       flags.2 ; Repeat switch: 0=off; 1=on. 
old     =       flags.1 ; Previous state of button. 
new     =       flags.0 ; Current state of button. 

; You can tune the constants assigned to delay, repeat, and upval below by 
; substituting different numbers for 150, 10, and 255. The "^0FFh" inverts
; the values to simplify the program code by allowing loops to count up
; instead of down. If ^0FFh is removed, delays will be smaller when the 
; numbers are larger. 
delay   =       150 ^0FFh       ; Delay before autrepeat.
repeat  =       10 ^0FFh        ; Delay between repeats.
upval   =       255 ^0FFh       ; Debounce for button-up.

        org     0

; Table to convert numbers 0-7 into bytes with a 1 in the corresponding
; bit position. For example, 3 converts to 00001000b. 
Pinz    jmp     pc+w
        retw    1,2,4,8,16,32,64,128

start   mov     !rb, #0 ; Make RB output to drive LEDs. 
        mov     !ra, #15        ; Make RA input for button. 
        clr     rb      ; Turn off LEDs. 
        clr     flags   ; Clear the bit flags. 
        clr     dbL     ; Clear the debounce counter. 
        clr     dbH
        setb    state   ; Set up for active-high (1=pushed)
        setb    rpt_sw  ; Turn on autorepeat. 
        setb    old     ; Initialize old state of button. 
        mov     pin,#2  ; Read button on pin 2
        mov     port,#0 ; of port RA. 
        call    btn_init        ; Initialize button variables. 
; This illustrates how button is used--the routine is called from a main
; loop that may or may not contain other instructions. The more instructions
; inside the loop, the smaller the button delay and repeat values should be. 
:loop   call    button
        jmp    :loop

; Since Button is called frequently from within a main program loop, it makes
; sense to move code that only needs to be executed once into a separate 
; initialization routine. 
btn_init        mov     w,pin   ; Look up pin value in table and
        call    Pinz    ; store it for use by Button. 
        mov     pin,w
        ADD     port,#ra        ; Add offset to point to I/O ports. 
        ret

Button  mov     fsr,port        ; Point to the port. 
        mov     w,indirect      ; Move port bits into w. 
        sb      state   ; IF state=0 THEN w = NOT(port bits).
        mov     w,/indirect             
        AND     w,pin   ; IF w AND pin THEN new = 1
        movb    new,z   ; ELSE new = 0 (0 means button "on")
        mov     w,#3    ; Move 0000011b to w to mask bits 
        AND     w,flags ; other than old and new.       
        jmp     pc+w    ; Jump based on state of bits old, new. 
        jmp     :held   ; old = 0, new = 0 - button held down. 
        jmp     :release        ; old = 0, new = 1 - button just released. 
        jmp     :push   ; old = 1, new = 0 - button just pushed. 
        jmp     :up     ; old = 1, new = 1 - button left up. 

:push   clrb    old     ; Copy new button state to old. 
        test    dbL     ; If dbL is not 0, then the last button-up
        sz              ; period was short--probably a bounce--
        ret             ; so don't do theAction. Else, load delay
        mov     dbH,#delay      ; and jump to theAction. 
        jmp     theAction

:held   incsz   dbL     ; Increment 16-bit db variable until it
        ret             ; reaches 0. When it does, reload it with
        incsz   dbH     ; the repeat delay , and, if the repeat
        ret             ; switch is on, do theAction. If repeat
        mov     dbH,#repeat     ; switch is off, skip theAction and return. 
        snb     rpt_sw  
        jmp     theAction
        ret
:release        mov     dbL,#upval      ; Button released: put upval into
        setb    old     ; dbL and copy new state to old. This
        ret             ; sets up the button-up debounce in :up. 

:up     test    dbL     ; Increment dbL until it equals 0. 
        sz              ; Since :push won't do theAction unless
        inc     dBL     ; dbL=0, this debounces button release. 
        ret     

; This is the code activated by Button. Note that it ends with ret in order to get
; back to the code that called Button in the first place. 
theAction       XOR     rb,#255
        ret

