Help needed with push buttons

Discussion in 'Embedded Systems and Microcontrollers' started by Carl03, Nov 19, 2012.

  1. Carl03

    Thread Starter New Member

    Oct 27, 2012
    I'm building a binary clock and I have been working on this project for a while now, I have completed all programming up until the buttons. I have timer0 interrupt going for state machine/multiplexing and timer2 interrrupt going for the actual time keeping. I'm using assembler to code this and I don't know where the button coding would come.

    Here is my interrupt routine:
    Code ( (Unknown Language)):
    1.     ;**************** INTERRUPT SERVICE *********************
    2.     ORG 0x0004
    3. isr:  
    4.         movwf   W_TEMP            ; save off current W register contents
    5.         movf    STATUS,W          ; move status register into W register
    6.         movwf   STATUS_TEMP       ; save off contents of STATUS register
    7.         movf    PCLATH,W          ; move pclath register into W register
    8.         movwf   PCLATH_TEMP       ; save off contents of PCLATH register
    10. ; Test to see if the interrupt was generated by TMR0 or TMR2
    12.         btfsc   INTCON,TMR0IF    ; TMR0 interrupt?
    13.         goto    tmr0int          ; Yes
    14.         btfsc   PIR1, TMR2IF
    15.         goto    tmr2int
    16.         goto    intout
    19. tmr0int:
    20.         bcf     INTCON, TMR0IF   ; clear interrupt flag (HAVE TO DO THIS)
    22. state_machine:
    23.         movlw   .0
    24.         xorwf   current_state, w
    25.         btfsc   STATUS, Z
    26.         goto    state_machine_hours
    27.         movlw   .1
    28.         xorwf   current_state, w
    29.         btfsc   STATUS, Z
    30.         goto    state_machine_min
    31.         goto    state_machine_sec
    33. state_machine_hours:
    34.         bcf     SEC_EN
    35.         comf    hour, w
    36.         movwf   PORTB
    37.         bsf     HOUR_EN
    38.         incf    current_state, f
    39.         goto    state_machine_end
    40. state_machine_min:
    41.         bcf     HOUR_EN
    42.         comf    min, w
    43.         movwf   PORTB
    44.         bsf     MIN_EN
    45.         incf    current_state, f
    46.         goto    state_machine_end
    47. state_machine_sec:
    48.         bcf     MIN_EN
    49.         comf    sec, w
    50.         movwf   PORTB
    51.         bsf     SEC_EN
    52.         clrf    current_state
    53. state_machine_end:
    54.         goto    intout
    57. tmr2int:
    58.         bcf     PIR1, TMR2IF     ; clear interrupt flag (HAVE TO DO THIS)
    59. sec_s:
    60.         banksel sec
    61.         incf    sec_split, f
    62.         movf    sec_split, w
    63.         sublw   d'250'
    64.         skpweq
    65.         goto    intout
    66. every_sec:
    67.         clrf    sec_split
    68.         incf    sec, f
    69.         movf    sec, w
    70.         sublw   d'60'
    71.         skpweq
    72.         goto    intout
    74.         ;gets here every minute
    75. every_min:
    76.         clrf    sec
    77.         incf    min, f
    78.         movf    min, w
    79.         sublw   d'60'
    80.         skpweq
    81.         goto    intout
    83.         ;gets here every hour
    84. every_hour:
    85.         clrf    min
    86.         incf    hour, f
    87.         movf    hour, w
    88.         sublw   d'24'
    89.         skpweq
    90.         goto    intout
    92. clear:
    93.         clrf    sec
    94.         clrf    min
    95.         clrf    hour
    97. intout:
    98.         movf    PCLATH_TEMP,W     ; retrieve copy of PCLATH register
    99.         movwf   PCLATH            ; restore pre-isr PCLATH register contents
    100.         movf    STATUS_TEMP,W     ; retrieve copy of STATUS register
    101.         movwf   STATUS            ; restore pre-isr STATUS register contents
    102.         swapf   W_TEMP,F
    103.         swapf   W_TEMP,W          ; restore pre-isr W register contents
    104.         retfie                    ; return from interrupt          
    Does anyone have a code snippet of debouncing a button?
    Any help would be greatly appreciated.
  2. t06afre

    AAC Fanatic!

    May 11, 2009
  3. Carl03

    Thread Starter New Member

    Oct 27, 2012
    My hardware has already been built. Where should the button code go? How do I turn off the timer2 interrupt?
  4. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    I handle my buttons inside the ISR and pass the results in a global variable (as they typically all are anyway in assembler).

    In my latest app I have an ISR that trips every millisecond. I test my buttons every 25 mS, which makes the app responsive while avoiding any bouncing issues.

    I keep several variables:

    KeyDelay: Counts every 1 mS, reset at 25 mS.
    LastKeys: Last reading of buttons.
    Keys: Current debounced button state.

    So every ms I increment KeyDelay, and when it's 25 I reset it and read the current buttons, and compare to the previous read in LastKeys. If they are the same I have stable buttons, and that information is saved in the Keys variable. The current button state is then saved in LastKeys for the next time thru the loop.

    The main loop of code monitors Keys, and can take action when pressed. Note the pressed action code needs something to wait till the button is released least it just do the function over and over until the button is released.

    Note sometimes that action is desired to say have the UP button keep incrementing some parameter, and that can be added at a later time if you so wish.

    I would post my code but A) it's written in C, and 2) it's somewhat specific as my buttons share the data lines with an LCD display so there is a lot of extraneous code that doesn't apply here.

    Generally I try to not write anything in assembler. <grin> Nothing wrong with it, I'm just more productive when using C.
  5. JohnInTX


    Jun 26, 2012
    The debounce routine here was lifted from a project and is implemented as a macro which can be invoked multiple times for multiple switches. The resulting debounce routines are called by the timer IRQ routine and work by incrementing a dedicated RAM byte (1 per switch) when the switch is closed and decrementing it when its open. Counting up is limited when a selected bit in the count register goes to '1' and limited the other way when it counts back to '0' so it debounces both ways.

    Of note is the output. Like ErnieM's, it posts a global status flag so that the main routines only have to check the flag to see what the switch status (TRUE/FALSE) is. I also incorporate a second flag (defined in DebInputReqs) that is set ONCE when the switch gets fully debounced. Its useful for things like counting switch closures as main does not have to remember what the state of the switch is, it just reads then CLEARS the flag and waits for the next ..Req(uest)flag.

    In use, just call the macro-generated debouncers on each timer IRQ. Your main routine has only to examine the various status flags, never the switches directly.

    The macro is general purpose as it allows specifying whether the input is active high or low to be logically TRUE. You also specify the port,bit and RAM active and request flags in the macro invocation.

    I like macros for something like this. All you have to do is invoke it once and it makes your code for you. Adding switches is as simple as defining them and invoking the macro again with the new defs. The downside is that it can take up more ROM but building and passing parameters to a subroutine does so as well so pick your poison. A lot of switches might require another approach.

    If you are uncomfortable with macros, maybe you can use it as a model.

    Have fun.

    BTW: skpz and skpnz are built in macros for testing the Z flag i.e. skpz = btfss STATUS,Z
    Also, ignore any references to rambanks. That' up to you.

    Code ( (Unknown Language)):
    1.           ;*********************  BUTTON  DEBOUNCER  ***********************
    3.           ; Debounces button inputs active low or hi. Debounces both ways
    4.           ;  port,bit is the input line
    5.           ;  swreg is the XXXdeb ram byte/counter
    6.           ;  debK is the bit to count up to to be debounced (2^^debK)*timer tik
    7.           ;  flagbit is the bit in the DebInputs regs that indicates cond.
    8.           ;  active 0/1 for active low/hi input
    9.           ; Expects rambank 0 to see ports
    12. debounce  macro   port,bit,swreg,debK,flagbit,active
    13.           local   false,exit
    14.           expand
    16.           if (active==0)          ; active low or hi
    17.            btfsc   port,bit ; TRUE or FALSE?
    18.           else
    19.            btfss   port,bit
    20.           endif
    21.           goto    false
    23.           btfsc   swreg,debK      ; iff bit set, switch is already TRUE, thazzit
    24.           goto    exit
    26.           incf    swreg,F         ; else, inc debounce reg
    27.           btfss   swreg,debK      ; now.. iff just done..
    28.           goto    exit            ; it aint
    30.                           ; ..else, set button-held and request (just changed) flags
    31.           bsf     DebInputs,flagbit
    32.           bsf     DebInputReqs,flagbit
    33.           goto    exit            ; done
    35. false:  
    36.           movf    swreg,F         ; switch input FALSE, count already Z?
    37.           skpz                    ; yup
    38.           decf    swreg,F         ; else, dec it
    40.           skpnz                   ; and iff Z now, clear the SW active flag
    41.           bcf     DebInputs,flagbit
    42. exit:
    43.           noexpand
    44.          endm
    46.     ; *********** IO and FLAG BIT DEFINITIONS  ***************
    47. #define POWERSW_    PORTC,0 ; switch connected to RC0
    48. #define TESTSW_        PORTC,1    ; RC1
    49. #define PanelDebK     5    ; count up to bit 5 (2^5 tiks) to debounce
    51. #define PWRbit        0    ; bit in status regs corresponding to PWR switch
    52. #define TESTbit        1    ; bit in status regs corresponding to TEST switch
    54.     ;************* RAM DECLARATIONS  *************************
    56.     cblock 20h
    58.     PWRdeb:        1    ; counter debounces PWR sw
    59.     TESTdeb:    1    ; counter debounces TEST sw
    60.     DebInputs:    1    ; switch held status bits
    61.     DebInputReqs:    1    ; switch just closed status bits
    63.     endc
    65. ;************* SWITCH STATUS FOR MAIN TO USE  **********************
    66. #define PWR_SW_TRUE    DebInputs,0
    67. #define PWR_SW_CHANGED    DebInputReqs,0
    69. #define TEST_SW_TRUE    DebInputs,1
    70. #define TEST_SW_CHANGED DebInputReqs,1
    73. ;****************** MACRO INVOCATIONS FOR TWO SWITCHES *******
    75.           ; Invoked via IRQ every 4 msec. These generate the debounce code.
    77.     ; Make code to debounce PWR switch active low, 2^5 timer tiks to debounce
    78.     ; Switch is on RC0, Active and Changed flags on bit 0 in respective registers.
    80. debouncePOWERSW:
    81.           debounce POWERSW_,PWRdeb,PanelDebK,PWRbit,0
    82.           return
    84.     ; Make code to debounce TEST switch active low, 2^5 timer tiks to debounce
    85.     ; Switch is on RC1, Active and Changed flags on bit 1 in respective registers.
    87. debounceTESTSW:
    88.           debounce TESTSW_,TESTdeb,PanelDebK,TESTbit,0
    89.           return
    92.     ;******************* INIT CODE  **********************
    93.     ; Call once to init the debouncers
    94. initDebounce:
    95.     clrf    PWRdeb
    96.     clrf    TESTdeb
    97.     clrf    DebInputs
    98.     clrf    DebInputReqs
    99.     return
    This is handled above by the flags in the DebInputReqs register. When the flag is TRUE (indicating that the switch just completed debouncing), take action and clear the flag.

    I personally prefer assembler <stupid grin> for midrange PICs (after the macros are written..) but have also used this approach in C.
    Last edited: Nov 19, 2012
  6. Carl03

    Thread Starter New Member

    Oct 27, 2012
    I'm writing my code in assembler. My buttons are to set the hours and minutes. My hours set button is connected to RA3 and minutes set button is connected to RA4 and this also resets the seconds. I've written a bit of code but when I start the program, it scrolls through the hours fast (very fast) until I press the hours set button does the same with minutes. I would post the code but at the moment I'm replying via my mobile. Any ideas what my problem might be?
  7. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    <resisting urge to just reply with just the single word "yes.">

    First off your buttons are working backwards, as if you have pull up resistors, switch to ground, so a press makes a zero in but your code tests for a one as a press.

    Next since they increment over and over you never test for a button release, so the increment routine just keeps on going and going and going...

    Last you test hours before minutes, so when you actually press hours the code stops sensing the button down (it's reversed remember?) so then it tests the minutes resulting in a similar bug as the hours has.

    Knowing this the solutions should be fairly evident.
  8. Carl03

    Thread Starter New Member

    Oct 27, 2012
    This is the code I have

    Code ( (Unknown Language)):
    1. check_delay:
    2.     incf    debounce, f
    3.     movf    debounce, w
    4.     sublw   .255
    5.     skpweq
    6.     goto    check_delay
    7.     clrf    debounce
    8.     goto    check_buttons
    10. check_buttons:
    11.     movf    PORTA, w
    12.     movwf   buttons
    13.     btfss   buttons,4
    14.     goto    check_end
    15.     btfss   buttons, 3
    16.     btfss   buttons, 4
    17.     goto    set_hours
    18.     goto    set_min
    20. set_hours:
    21.     btfss   buttons, 3
    22.     goto    check_end
    23.     incf    hour, f
    24.     movf    hour, w
    25.     sublw   d'24'
    26.     skpweq
    27.     goto    show_hour
    28.     clrf    hour
    29. show_hour:
    30.     movf    hour, w
    31.     movwf   hour
    32.     bsf     T2CON, TMR2ON      
    33.     return
    35. set_min:
    36.     incf    min, f
    37.     movf    min, w
    38.     sublw   d'60'
    39.     skpweq
    40.     goto    show_min
    41.     clrf    min
    42. show_min:
    43.     movf    min, w
    44.     movwf   min
    46. check_end:
    47.     bsf     T2CON, TMR2ON
    48.     return
    Have no idea what I'm doing.