debouncing int0/1, delay in ISR

Hello,
on my quest to learn and understand interrupts I thought of a method to debounce a push-button switch connected to int0 or int1.
(hardware or polling methods are preferred, I understand :wink: )
The idea is to set a state to active (HIGH) in the ISR until the change of state is acknowledged in the main loop.
Consecutive interrupts shouldn’t change anything this way.
I’m still not sure if I need to disable interrupts at certain places (see code), however, I tested several switches and other forms of electrical contacts and the practical tests appear to be working.
(I don’t like the dumb loop for the 10ms delay in the ISR (delay(), millis(), micros() etc. don’t work in ISRs, as I understood),
is there a more elegant way?)
I’m aware of the fact that this is not as immediate as a pure hardware int where true debouncing would be needed,
but from the non-practical software POV, could this be an approach or am I overlooking something?

// interrupt test 3.1 /mz
// interrupt sets key keystate to high, main loop reads and inverts LED state accordingly
// test routine counts number of interrupts executed

#define BAUDRATE  115200
#define iLED 13
#define BPin 3    // Pushbutton to GND for int1

volatile int keystate = LOW; // button to int0 pressed or not
int ledstate = LOW;       // LED lit or not, initially OFF
volatile int cntint = 0;  // interrupt counter
int lastcntint;


void setup()
{
  Serial.begin(BAUDRATE);        //Create a serial connection to display the data on the terminal.
  Serial.println();              // separate buffer garbage
  Serial.println(cntint);        // so, tell me!!  (test)
  pinMode(iLED, OUTPUT);
  pinMode(BPin, INPUT);
  digitalWrite(BPin, HIGH);	// set DigiInput 2 to PullUp mode
  attachInterrupt(1, change_keystate, FALLING);  // FALLING w/ pull_up == when button is being pressed
  digitalWrite(iLED, ledstate);
  Serial.println("-------------");     // setup done
}


void loop() 
{
  noInterrupts();    // do I need that? That should be done at once, IMO.
                     // if the INT-flag is set again the ISR will be executed by the end of this section
  if (cntint != lastcntint) {
    Serial.print(cntint-lastcntint);  // print increment of number of interrupts executed
    Serial.print(" - ");
    Serial.println(cntint);  // print total number of interrupts executed
    lastcntint = cntint;
  }
  if (keystate) {
    ledstate = !ledstate;  // invert LED
    digitalWrite(iLED, ledstate);
    keystate = LOW;
  }
  interrupts();
}


void change_keystate()
{
  volatile long c, d;  // something to waste time with (for loop delay)
  noInterrupts();      // do I need that? There's no int0 or other higher int
  for (c=0; c<3163; c++) { d=c; }  //dumb delay of about 10ms @ 16MHz, there must be a more elegant way!
  interrupts();

  keystate = HIGH;
  cntint++;    // incr. interrupt counter for monitoring // test !
}

Thanks a lot, Michael.

I haven't got time right now to look at the logic of this, but one thing is for sure you don't need/want to dick with the interrupt disable/enable while inside an ISR. They will be disabled until you leave it.


Rob

Graynomad: one thing is for sure you don't need/want to dick with the interrupt disable/enable while inside an ISR. They will be disabled until you leave it.

ok, that helps, thx. The whole thing is probably not worth the effort since there's no advantage over polling and debouncing in the main loop. Lesson learned. Well, it was a try ...

As a general rule of thumb, you don't ever want a delay of any kind in the ISR. You can't use delay() because the routine itself uses interrupts, and if you're already in an ISR, it won't trigger another interrupt to update the value. A loop will technically work, but good design practice is to acknowledge the interrupt and GTFO as quickly as possible so your main body of code can still get things done without all the CPU's time being spent inside ISRs.

This is closely related to what Graynomad was saying about not turning off interrupts inside an interrupt, since you won't be interrupted again anyway until you leave the ISR and execute at least one operation of your normal code.

If you want to software debounce, I would use a delay() loop in the main code before turning off the keystate flag. So, if keystate is HIGH, sit on it for a moment before setting it to LOW.

In this demonstration case, there's probably not much difference. But if you have, for e.g., a timer interrupt or a different pin change interrupt on another ISR, your delay loop won't interfere with those.

While millis() can't interrupt inside a ISR it's still a useful way to do software contact debouncing using interrupts. There have been posting on using that method with ISR routines.

Lefty

The method i have been using for interupts and debounce is on the first interupt its sets the millis(), it will then only rerun the commands

if (lastpress - millis() > 100){ lastpress = millis(); dosomething = 1; }

check for the flag somewhere in your loop that runs continuously

if (dosomething == 1) runroutine();

Although the interupt might be run a few times it will only accept an input if sufficient time has passed. Be aware that running interupts can affect the millis() counter as the millis() counter is disabled while in an interupt ... never use delays in an interupt. Setting a flag is probably one of the fastest ways to get in and out of an interupt, you want to be in and out as fast as possible!

Hi, thanks!

SirNickity: This is closely related to what Graynomad was saying about not turning off interrupts inside an interrupt, since you won't be interrupted again anyway until you leave the ISR and execute at least one operation of your normal code.

If the INT-flag is set due to bouncing while the ISR is being executed, can there be a gap between the current execution of the ISR and the next one?

If you want to software debounce, I would use a delay() loop in the main code before turning off the keystate flag. So, if keystate is HIGH, sit on it for a moment before setting it to LOW.

Originally I wanted to use INT0 as a soft-reset switch. So my test code above is useless anyway since if there's trouble the main routine probabyl wouldn't work as well. I'm trying to avoid delay() in the main loop while, funnily enough, it wouldn't hurt in this particular ISR. However, I'm totally with you: keeping an ISR as short and fast as possible is a good point!

In this demonstration case, there's probably not much difference. But if you have, for e.g., a timer interrupt or a different pin change interrupt on another ISR, your delay loop won't interfere with those.

I got a hang of this Metro-library. It allows many simulataneous intervalls and checking the states is pretty fast. Not sure if that works in an ISR, need to find out.

retrolefty: While millis() can't interrupt inside a ISR it's still a useful way to do software contact debouncing using interrupts. There have been posting on using that method with ISR routines.

yup, thanks - found that. Meanwhile I ditched the idea of having this INT0 soft reset switch. I'll do that all in the setup routine. If something goes wrong the standard reset shall work.

hotshotharry: Although the interupt might be run a few times it will only accept an input if sufficient time has passed. Be aware that running interupts can affect the millis() counter as the millis() counter is disabled while in an interupt ... never use delays in an interupt. Setting a flag is probably one of the fastest ways to get in and out of an interupt, you want to be in and out as fast as possible!

Thanks for that, I'm currently restructuring my software. :sweat_smile: