This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Switch Button debouncing

Based on Dr Marty’s work, which was declared by himself to be the best switch debounce routine ever, I developed one for my project, for which a push button is used to control program flow. In order to make the control fine a push of the button is distinguished as long or short depending on the length of the push.

 

1. Create two uint32_t variables: buttonPressed and buttonReleased, set their initial values to zero.

 

2. configure the watchdog timer as interval timer that is enabled to generate continuous overflow interrupts and do time counting.

 

3. initialize the pin P3.1 as input, connect a push button to it, enable interrupts for the pin, and set its interrupt on lowto-high transition. 

 

4. implement the interval timer ISR by one command: timeCounting ++, one does not need to clear interupt flag as documents say that it will be cleared automatically once the ISR is executed.

 

5. implement the P3 ISR:

  1. clear interrupt flag.
  2. if a low-to-high interrupt was first detected (buttonPressed = 0), XOR P3 interupt edge, and set buttonPressed = timeCounting
  3. else a high-to-low interrupt was detected (buttonPressed >0), just set buttonReleased = timeCounting.

6. get time of button pressing plus bouncing = buttonReleased – buttonPressed, then a long or short button press can be determined.

 

It sounds good, right!

But I do not know if it will really works or not.  Below is the code, please take a look and give it a evaluation. 

uint32_t buttonPressed=0,buttonReleased=0;
bool longButton;

int main(void)
{
    //set interval timer
    WDTCTL = WDTPW | WDTHOLD;   
    WDTCTL = WDTPW | WDTSSEL__ACLK | WDTTMSEL | WDTCNTCL | WDTIS_5;     //250 ms a count
    __enable_interrupt();
    NVIC_ISER0 = 1 << ((INT_WDT_A - 16) & 31);
    
    //set P3.1 
    P3DIR &= ~(uint8_t) BIT1;
    P3REN |= BIT1;                          // Enable pull-up resistor (P3.1 output high)
    P3SEL0 = 0;
    P3SEL1 = 0;
    P3IFG &= ~BIT1;                         // Clear all P3 interrupt flags
    P3IE = BIT1;                            // Enable interrupt for P3.1
    P3IES &= ~BIT1;                         // Interrupt on low-to-high transition
    NVIC_ISER1 = 1 << ((INT_PORT3 - 16) & 31);

    for(;;) {
    MAP_PCM_gotoLPM3();                 //go into LPM3, wake up when button pressed
    while ((P3IN & BTN1) == ~BTN1);     // wait for button bouncing stable
        
    buttonReleased -= buttonPressed;    //How long it takes from the first low-to-high transition to the last high-to-low. 
    if (buttonReleased>15) continue;    //too long should be excluded as probably the first low-to-high is a noice
    if (buttonReleased<1) continue;     //too short should be excluded too.
    longButton=buttonReleased>5;        //>5, at least 6 time counts, 6*0.25 s = 1.5 s, < 15*0.25 = 3.75 s
    P3IES &= ~BIT1;                                 //low-to-high transition for next button press
    
    ... ...                             //do some things here for long or short press
    
    buttonPressed = 0;                  //reset it to zero for next one
    }
}
   
void WdtIsrHandler(void)
{
	timeCounting ++;    //do not need cleaning interrupt flag, see comments above   
}

void Port3Handler(void)
{   
    P3IFG &= ~BIT1;         //clear port3 interrupt
    if (buttonPressed==0) {
        P3IES ^= BIT1;      //change interrupt edge 
        buttonPressed = timeCounting;}
    else 
        buttonReleased = timeCounting;
}

 

  

  • I guess that is not the whole code?
    ISR are functions, don't put call to functions inside them even if it just one call.

    Flipping edge should be avoided at all possibility as:
    You could miss flipping it in time, so after flipping it you could wait "forever" for that edge to be seen again.
    You will probably invoke a IFG flag when you are doing the flipping, clearing IFG after the edge-toggle (if done inside it's IRQ) will take care of that.

  • Thank you very much for joining the discussion. 

    Tony Philipsson said:
    I guess that is not the whole code?

    No, it is not, the complete part of debouncing is:

    #include "driverlib.h"
    #include "SJ.h"
    
    uint32_t buttonPressed=0,buttonReleased=0;
    bool longButton;
    
    int main(void)
    {
        //set interval timer
        WDTCTL = WDTPW | WDTHOLD;   
        WDTCTL = WDTPW | WDTSSEL__ACLK | WDTTMSEL | WDTCNTCL | WDTIS_6;     //15.625 ms a count
        __enable_interrupt();
        NVIC_ISER0 = 1 << ((INT_WDT_A - 16) & 31);
        
        //set P3.1 
        P3DIR &= ~(uint8_t) BIT1;
        P3REN |= BIT1;                          // Enable pull-up resistor (P3.1 output high)
        P3SEL0 = 0;
        P3SEL1 = 0;
        P3IFG &= ~BIT1;                         // Clear all P3 interrupt flags
        P3IE = BIT1;                            // Enable interrupt for P3.1
        P3IES &= ~BIT1;                         // Interrupt on low-to-high transition
        NVIC_ISER1 = 1 << ((INT_PORT3 - 16) & 31);
    
        for(;;) {
            MAP_PCM_gotoLPM3();                 //go into LPM3, wake up when button pressed
            while ((timeCounting-buttonReleased)<2);     //it is stable if no bounce was recorded within 2*15.625=31.5 ms after system waked up
            buttonReleased -= buttonPressed;    //the time took from the first low-to-high transition to the last high-to-low. 
            if (buttonReleased>250) continue;   //too long should be excluded as the first low-to-high possibly is a noice. 250*0.015625~3.9 s
            if (buttonReleased<15) continue;    //too short, 15*0.015625=0.234 s, sure it's a bounce
            longButton=buttonReleased>89;       //long press must be at least 90, 90*0.015625 s = 1.4 s
            P3IES &= ~BIT1;                     //set to low-to-high transition for next button press
            buttonPressed = 0;                  //reset it to zero for next 
            ... ...                              
        }
    }
       
    void WdtIsrHandler(void)
    {
    	timeCounting ++;    //do not need cleaning interrupt flag, see comments above   
    }
    
    void Port3Handler(void)
    {   
        P3IFG &= ~BIT1;         //clear port3 interrupt
        if (buttonPressed==0) {
            P3IES ^= BIT1;      //change interrupt edge 
            buttonPressed = timeCounting;}
        else 
            buttonReleased = timeCounting;
    }
    

    Tony Philipsson said:
    You could miss flipping it in time, so after flipping it you could wait "forever" for that edge to be seen again.

    I suppose you mean the high-to-low edge by "that edge", but I think the edge will be seen because at beginning it catches interrupts of low-to-high, i.e. button pressing, in the service routine then changed to catch high-to-low edge, i.e. button releasing. Even a short button press usually takes about 300 ~ 400 ms, a flipping should not take that much time so the edge will be seen.

    Tony Philipsson said:
    You will probably invoke a IFG flag when you are doing the flipping,

    I'm not sure. So moving the cleaning IFG flag statement fron the beginning of the routine to the end, i.e. change the ISR to

    void Port3Handler(void)
    {   
        if (buttonPressed==0) {
            P3IES ^= BIT1;      //change interrupt edge 
            buttonPressed = timeCounting;}
        else 
            buttonReleased = timeCounting;
        P3IFG &= ~BIT1;         //clear port3 interrupt
    }

    will be safer. What do you think about it?

    Thank you again.

    Su

     

     

  • As a switch creates a bunch of noise, many edges can happen faster than the irq can serve and flip the edge.
    If the code you use have some type of timeout or settling it may work.

    But it's simpler just to keep it to one irq edge and have the timer/wdt decide what should be considered settled and how long max button press is.

  • Yes, I agree you that just to use one edge interrupt, for example the rising edge, is possible:

    As showing above there could be a lot of rising edges, but we can recognize the first one right after button press, and the first one just after its release, then using the time counting difference of the two to determine a long or short button press. But in addition to complication we may not be able to pick up the other edge interrupt, look at:   

    if the button is so good that has no bounce at all. Am I right?

    Thank you very much.

    Su

  • As an example, assume that the push-button is normally open and momentarily short to ground when pushed. This is what I would do:

    (a) The port pin is configured as digital input with a weak pull-up. Falling edge pin interrupt is enabled. The voltage level will stay high indefinitely until the button is pushed.

    (b) At that time, the pin interrupt is triggered and the ISR disables further pin interrupts. In addition, this ISR configures a Timer overflow interrupt which will generate an interrupt after, e.g., 100 msec. The current ISR exits and no more interrupts until the time-out expires.

    (c) The time-out interrupt ISR first check the pin voltage level. If it is high, we will go back to (a) above. If the level is low, we will proceed.

    (c’) To configure the Timer to count time without generating any interrupts. We also enable the pin interrupt to detect rising edge. This will last until the button is released.

    (d) At that time, the pin interrupt is triggered and the ISR disables further pin interrupts. In addition, the Timer is stopped and the count is read. The current ISR exits and no more interrupts until the time-out expires.

    (e) The time-out interrupt ISR first check the pin voltage level. If it is low, we will go back to (c’) above. If the level is high, we will report that the button was pressed for the duration of Timer counts we read in (d) above, we then proceed to (a) above,

  • Thank you very much for joining the discussion. No doubt the described routine is a good one, but in comparison with my one I think it is 

    (1) too complicated. In addition to falling and rising edge interrupt, it must service timer overflow interrupt, too.

    (2) lack of scalability. It is really hard to guess a good timer overflow period, a paper says buttons of the same type still bounce for quite different time period, even the same button could show not the same debouncing behavior depending on the pressing. Once, for example, 100 msec or any other values was set, it is not possible to make any adjustment to it unless you modify, recompile and reload your code. 

    Thank you very much again.

    Su

  • There are a few ways that is correct and one can be right for task, depending on the task.

    Weak pull up (internal in msp is OK), a switch connects the trace to gnd.
    A falling edge irq detects it and issues an event to another task right away.
    It turns off its own IE.
    It turns on a 32ms interval timer.

    The timer irq checks if the button pin is now high again (e.g switch released) and sets a flag if so.
    And on the next isr return: if flag and pin is both high, confirms that switch is now steady high and clear pin IFG & enable pin IE again.
    turn off timer irq.

    The above assumes that the button is not held down for hours or can get faulty stuck in down position.

  • Hi Su,

    May I suggest putting a 4.7 uF capacitor across the switch? I don't know if you are breadboarding or your MCU is already on a PCB, but I highly suggest putting a capacitor across the switch if you can, as this tends to be extremely effective.

    Anil
  • Thank you. Right now I just discuss the software debouncing theoretically, I do not know in practice if use both soft and hardware means simultaneously would be better, or only either one is necessary.
  • Thank you. Actually my routine is just a bit different from yours in that (1) yours turns on a 32 ms interval timer by the first falling edge irq, mine starts a 15.625 ms interval timer by the main early and uses it as a counter; (2) your timer irq checks button pin's status, this may need to be done repeatedly ( at least two) as, I have tested it, a human being button press generally lasts 300 ~ 400 ms. Mine just replaces falling edge irq time counting continually until no more falling edge is detected.
    Su

**Attention** This is a public forum