I'm doing an LED strip lighting controller with an Arduino. It will have several pushbuttons to switch modes.

I'm debouncing the buttons with code similar to the below code. This is Arduino code, but really my question is a general question that would apply to any programming language.

Code:

// Global variables
const int buttonPin3 = 19;
const unsigned long buttonDebounceMillis = 200;
volatile unsigned long buttonDebounce;
volatile bool lightsAreOn;
volatile int colorSelection; 

// "Setup" is called once at Arduino bootup
void setup()
{
  Serial.begin(115200);
  buttonDebounce = millis();
  pinMode(buttonPin3, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin3), ProcessButton3, FALLING);
  colorSelection = 4;
  lightsAreOn = false;
}

// Pressing button 3 turns on the lights and randomizes the color pallete.
// "ProcessButton3" is called automatically when the hardware triggers an
// interrupt when the button state goes from high to low (pressed==low in
// this case because the button is configured with a built in pullup resistor).
void ProcessButton3()
{
  if ((millis() - buttonDebounce) < buttonDebounceMillis)
  {
    // If the last time that a button falling happened very recently, interpret
    // it as additional noise and return from the function without doing anything.
    return;
  }
  else
  {
    // Accept this button press as valid. Update our debounce timer to the current time.
    buttonDebounce = millis();
  }

  // Complete this button's tasks if the button press was valid.
  lightsAreOn = true;
  colorSelection = (int)random(1,5);

  // Log the button press so that I can see whether the debounce worked.
  Serial.println("Button 3 pressed.");
}


The code above works successfully to debounce the "press" of the button. However there is something very interesting that happens on the "release" of the button. Sometimes the release of the button has noise too, which causes the button to go low/high/low/high really fast. The initial low/high doesn't trigger the interrupt, but the next high/low does.

If the user pressed and then released the button very briefly (within the 200ms debounce time) then the function still ignores the noise because the debounce code counts it within the 200ms range. But if the user presses the button and then releases it slowly, then there is a chance that the release of the button will cause the guts of the routine to unintentionally fire a second time during the release.

I don't want to increase the 200ms debounce time. In fact, 200ms is still too slow. There will be several buttons on this controller, and some of them might do things where I want to press the button quickly multiple times in a row (such as turning the brightness up and down). Usually, the actual debounce noise happens at time scales well under 10ms, and really I'd love to set the value down that low. But I've found that if I want to get rid of the "release" noise too, then I have to increase the value to 500ms or more, so that the release is still within its debounce range when I release the button.

Arduino's own example debounce code doesn't use interrupts, it just repeatedly polls the switch, but, I think their code might have the same problem (not sure): https://docs.arduino.cc/built-in-examples/digital/Debounce - My guess is that their code, since it depends on polling instead of hardware interrupts, will still have the problem but it will surface more rarely because the polling interval will be significantly slower than the hardware interrupt.

Anyway, are there any "best practices" for handling this situation? I'm sure that keyboard key handlers do this all the time, and I'm wondering how they do it.
_________________________
Tony Fabris