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.
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:
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.
**Attention** This is a public forum