Go Down

Topic: The Infamous problem of switch debouncing (Read 370 times) previous topic - next topic

Tech-Guy

Hello all!
I have a simple project with one switch. I would like to tell a short press from a long press and process data accordingly.

I thought I had a solution to debouncing by turning off the interrupt after the very first change in state was detected and waiting a period before reenabling that ISR. Initially I tried utilizing noInterrupts() and the complementary interrupts() but what the information pages dont tell you is that after an interrupt vector finishes the system calls interrupts() for you to avoid conflicts and all. This is not my desired operation...

This next try I am trying to disable that specific interrupt routine instead of the global flag for all Interrupt vectors. while this seems to have less oscillations per change in state it still seems to "take time" to disable that interrupts vector flag due to still having oscillations. I feel like I must be missing something very technical or blatantly obvious for this specific approach to not work.

I am trying to learn about Interrupts and their routines, which is why I have taken this seemingly more complex route.

I am on an arduino nano and also using the "Encoder" library which uses different interrupts (I am supposing?).

Here is some example code
Code: [Select]

#define DebounceTimer 4
#define ButtonPin 3


  unsigned long currentMillis = millis();
  volatile bool shortPress = false;
  volatile bool longPress = false;

  volatile bool wasPressed = false; 
  unsigned long previousMillisForDebounce = 0; //software debounce
 
  volatile long buttonPressed = millis();
  volatile long buttonReleased = millis();

void setup() {

  pinMode(ButtonPin,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ButtonPin), buttonStateChanged, CHANGE);
 Serial.begin(115200);
}
void buttonStateChanged(){
  detachInterrupt(digitalPinToInterrupt(ButtonPin));
    if(digitalRead(ButtonPin)) {
      buttonReleased = currentMillis;
      Serial.print("Up,");
    } else { 
      buttonPressed = currentMillis;
      Serial.print("Down,");
    }
    wasPressed = true;
}

void loop() {
  currentMillis = millis();
if (wasPressed){   
 
  if (currentMillis - previousMillisForDebounce >= DebounceTimer) {
    attachInterrupt(digitalPinToInterrupt(ButtonPin), buttonStateChanged, CHANGE);

    if (buttonReleased > 0L){
 
      if (buttonReleased - buttonPressed <= 200){
          longPress = true;
        } else {
          shortPress = true;
        }
        buttonPressed = 0L;
        buttonReleased = 0L;
      }
   
      if (shortPress){
    shortPress = false;
    basicDisplayString = String(0000, DEC);

  }
      if (longPress){
    longPress = false;
    basicDisplayString = String(9999, DEC);
    }
  }
  }

}

pylon

Don't use interrupts to detect button presses. A button press is so slow the Arduino should always be able to read it fast enough to not loose that press even if you simply poll inside the loop() routine. And the whole debouncing and press time acquisition is much easier if you don't have to fiddle with interrupt context and it's restrictions.

dougp

Don't know if it'll help but, you can get a bunch of search results with 'debouncing with interrupts' plugged into the magnifying glass in the upper right of the page.

Tech-Guy

#3
Aug 09, 2017, 04:53 pm Last Edit: Aug 09, 2017, 05:08 pm by Tech-Guy
thank you pylon!
Would you know if there a reason the interrupt isn't getting turned off inside the interrupt function?


It seems as if I am constantly running into the undocumented intricacies of the arduino platform and would like to learn why this behaves this way

PaulS

Quote
Would you know if there a reason the interrupt isn't getting turned off inside the interrupt function?
The interrupt is ignored while the interrupt handler is running. But, it still gets queued. The handler will be called again as soon as it ends.

If you persist in using an interrupt with a human-pressed switch, you need to decide, in the ISR, whether it is reasonable (how long has it been since the interrupt last fired?) to deal with THIS interrupt, or not.
The art of getting good answers lies in asking good questions.

Tech-Guy

The interrupt is ignored while the interrupt handler is running.
Thank ya PaulS,

Isnt there only one interrupt flag for that particular interrupt vector? So that would mean that it would only ever call the ISR two times in a row. This is obviously not what is happening but the documentation says one interrupt goes with one flag (plus maybe a global one).


~I would prefer to learn the things I don't know than be complacent in things I do~

PaulS

Quote
This is obviously not what is happening
How do you know? If the ISR is fast, the switch may still be bouncing (or may only bounce once) after the ISR completes.
The art of getting good answers lies in asking good questions.

Tech-Guy

Thanks for  your continued support PaulS,

If there is only one interrupt flag per interrupt pin then no matter how many times the hardware sets that flag, before or during an ISR, it will only be in the "trigger an interrupt" state until the ISR is ran. At that time the ISR will be ran again but Since the code turns off the interrupt after it is detected then there would only ever be one extra pulse.

In my example sketch I have a serial print for every change in state of the switch for documenting purposes. In my testing there is always more than two changes in state printed out.

Is there some sort of hardware memory built into the arduino to let it know how many times an interrupt was called while other interrupts are working or is it just a single flag to say a particular ISR needs to run now?

thanks again

Tech-Guy

and as a side note I got it to properly work, and for those interested it was a matter of handling debouncing inside of the ISR. A Thanks to PaulS again for the knowledge:
... you need to decide, in the ISR, whether it is reasonable [...] to deal with THIS interrupt, or not.
Interrupt code looks like:
Code: [Select]

void buttonStateChanged(){

  if (currentMillis - previousMillisForDebounce >= DebounceTimer) { //important debouncing stuff
  previousMillisForDebounce = currentMillis;                                   //goes here
 
    if(digitalRead(ButtonPin)) {
      buttonReleased = currentMillis;
      Serial.print("Up,");
    } else { 
      buttonPressed = currentMillis;
      Serial.print("Down,");
    }
    wasPressed = true;
  }
}

pylon

Quote
In my example sketch I have a serial print for every change in state of the switch for documenting purposes. In my testing there is always more than two changes in state printed out.
Calling any print/write method of the Serial object inside a ISR is quite dangerous and may end in a freezing Arduino (that's because interrupts are disabled during the execution of an ISR).

Quote
If there is only one interrupt flag per interrupt pin then no matter how many times the hardware sets that flag, before or during an ISR, it will only be in the "trigger an interrupt" state until the ISR is ran. At that time the ISR will be ran again but Since the code turns off the interrupt after it is detected then there would only ever be one extra pulse.
According to the datasheet the interrupt flag is cleared when the ISR is executed. So it's possible to set it again (because an interrupt happened) during the ISR. If you want to disable that you can clear the flag by writing a 1 to that register at the end of your ISR.

Quote
Is there some sort of hardware memory built into the arduino to let it know how many times an interrupt was called while other interrupts are working or is it just a single flag to say a particular ISR needs to run now?
It's one single flag. So if your ISR is running for a to long time (many edges on the interrupt pin) you'll loose interrupts because only one call will happen when your ISR ends. That's why an ISR should be as short as possible.

Tech-Guy

Wonderful Pylon! Thank you very much!

Calling any print/write method of the Serial object inside a ISR is quite dangerous and may end in a freezing Arduino (that's because interrupts are disabled during the execution of an ISR).
I figured this had some bad mojo to it, didnt know how else to see what was happening

According to the datasheet the interrupt flag is cleared when the ISR is executed. So it's possible to set it again (because an interrupt happened) during the ISR. If you want to disable that you can clear the flag by writing a 1 to that register at the end of your ISR.
Aha! I was trying to do this so I minimize the number of times the ISR is called(thus limit cpu clocks wasted).

Without manipulating actual machine registers does the environment have a way to do this in higher level code? I was under the impression that calling the detachInterrupt(<#here>); would prevent the interrupt flag from being set again at all (save for the circumstance that it catches a change before interrupt was done computing).
This is the confusing part for me because I am getting several changes detected even though that pins' flag should be off. I would understand if it was giving me a single other interrupt execution after the initial one was fired but its not, the serial output gave me several at a time.

It's one single flag. So if your ISR is running for a to long time (many edges on the interrupt pin) you'll loose interrupts because only one call will happen when your ISR ends. That's why an ISR should be as short as possible.
The fact that it is a single flag seems even more likely that it should be doing as I describe above. I was hoping to capitalize on the single flag nature of the interrupts in this manner for this particular application.

 

I am trying to learn coding practices for a wide variety of projects and architectures, not just for this project, which is why I am being so nitty gritty, I apologize.

pylon

Quote
I was under the impression that calling the detachInterrupt(<#here>); would prevent the interrupt flag from being set again at all (save for the circumstance that it catches a change before interrupt was done computing).
I would expect the same. It probably disables the Interrupt Enable flag, so a new interrupt is not catched. But why is that important in your case?

Quote
The fact that it is a single flag seems even more likely that it should be doing as I describe above. I was hoping to capitalize on the single flag nature of the interrupts in this manner for this particular application.
I guess it does but you reactivate the interrupt within 4ms, which is a quite short time. It might be that I misunderstood your intention but I still think that using interrupts to capture button events is a bad idea.

Tech-Guy

hey pylon, sorry about the delay, classes are starting back up >_>

It probably disables the Interrupt Enable flag, so a new interrupt is not catched. But why is that important in your case?
In my brain, I was thinking this would accomplish two things;
1) If it disabled the interrupt then in the tens of milliseconds that the switch is bouncing it would not fire the interrupt again and cause oscillations in the code,
2) if the interrupt gets disabled, then the biggest advantage is the cpu is not wasting any clock cycles on detecting a trigger nor running its associated ISP. Even if I make my isp super small (and only continue if past a certain amount of time since last interrupt) then that is still doing a computation for every single bounce, and eating up those precious 20 MHz clocks, in what could be a much larger project than this.

Again, I am only trying to learn good coding practices for allllll of my projects, not just this one.

On two side notes, it feels as if I keep hitting these little discrepancy walls in my <simple> projects: does this happen to you too?
and, I sort of feel like people warn to stay away from using buttons on interrupts only because there aren't easier ways to deal with this scenario. In my mind this is still the most efficient route for multiple advantages above, less time on cpu and instant response, and no complicated debouncing things if all you have to do is:
execute isp{
disable interrupt flag
run code}
see if (time >= debounce constant){
reactive interrupt flag}

that last bit of code can be ran very, very slowly to use up as little time possible while still being responsive

I guess it does but you reactivate the interrupt within 4ms, which is a quite short time. It might be that I misunderstood your intention but I still think that using interrupts to capture button events is a bad idea.
Oh I forgot to mention that even a 50 ms time on that didnt seem to affect it at all.. so I dont understand what is actually going on. Does it take a hugely large amount of time for the arduino to attach and detach an interrupt?


Thanks to pylon and all others for helping me wrap my brain around this issue! I appreciate you guys!

Tech-Guy

So does it take a really long time to detach interrupts?

pylon

Quote
So does it take a really long time to detach interrupts?
No, it doesn't. This is done in about a microsecond.

Quote
I sort of feel like people warn to stay away from using buttons on interrupts only because there aren't easier ways to deal with this scenario.
No, there are much easier ways but many people undervalue the complexity the interrupt subsystem incorporates. Polling for button states is definitely the easier way than to use interrupts, even if you take debouncing into concern. Interrupts make sense for this purpose only if the processor is doing complicated calculations almost constantly and there is no "main loop" that is at least about every 20-30ms.

I guess your problem arises from the fact that clearing the interrupt enable flag doesn't call the ISR but nevertheless sets the interrupt flag. Once the interrupt enable flag is activated again without first clearing the interrupt flag, the ISR will be called immediately (see section 7.1.1 of the datasheet).

Go Up