skotl
Forum Supporter
- Messages
- 10,163
- Location
- Edinburgh, UK
@Chippie you should also add some physical debouncing to the circuit. Given its just a button press a 100nF capacitor to ground on the input pin would suffice. If you do this then I doubt you'd need to do any software debouncing at all.
The problem with having no physical debouncing is that if you decided in the future to change the button presses to be handled in interrupts then you may end in a situation where the processor locks up because all it's time is taken in servicing the interrupt. Software implementation should not be a replacement for good physical design. Software debouncing usually came about because the hardware was fixed and could not be changed in deployment and I feel that people now use it as a replacement when it's not. Electronic debouncing can do much more than prevent spurious events (such as protecting the input pin).
Handling debouce in interrupts is painless as long as the interrupt is doing nothing more than setting the state of the button (i.e. you don't respond to the button press). This is code I use with ESP32 that will also will also work with Arduino (replace the esp.h include with arduino.h):
ButtonHandler.h
//
// Created by Scott# on 11/12/2023.
//
#ifndef BUTTONHANDLER_H
#define BUTTONHANDLER_H
namespace Buttons {
class ButtonHandler {
private:
bool _buttonWasPressed = false;
public:
void begin();
void update();
bool wasPressed();
};
} // Buttons
#endif //BUTTONHANDLER_H
ButtonHandler.cpp
//
// Created by Scott on 11/12/2023.
//
#include "ButtonHandler.h"
#include <cstdint>
#include <esp32-hal-gpio.h>
#include <HardwareSerial.h>
#include <WString.h>
#include <freertos/portmacro.h>
#include <freertos/task.h>
#define BUTTONPIN 13
#define DEBOUNCETIME 10
namespace Buttons
{
volatile int numberOfButtonInterrupts = 0;
volatile bool lastState;
volatile uint32_t debounceTimeout = 0;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR handleButtonInterrupt()
{
portENTER_CRITICAL_ISR(&mux);
numberOfButtonInterrupts++;
lastState = digitalRead(BUTTONPIN);
debounceTimeout = xTaskGetTickCount(); //version of millis() that works from interrupt
portEXIT_CRITICAL_ISR(&mux);
}
void ButtonHandler::update()
{
uint32_t saveDebounceTimeout;
bool saveLastState;
int save;
portENTER_CRITICAL_ISR(&mux);
// so that value of numberOfButtonInterrupts, lastState are atomic - Critical Section
save = numberOfButtonInterrupts;
saveDebounceTimeout = debounceTimeout;
saveLastState = lastState;
portEXIT_CRITICAL_ISR(&mux); // end of Critical Section
bool currentState = digitalRead(BUTTONPIN);
if ((save != 0) //interrupt has triggered
&& (currentState == saveLastState) // pin is still in the same state as when intr triggered
&& (millis() - saveDebounceTimeout > DEBOUNCETIME))
{
if (currentState == LOW)
{
Serial.printf("Button is pressed and debounced, current tick=%d\n", millis());
}
else
{
Serial.printf("Button is released and debounced, current tick=%d\n", millis());
_buttonWasPressed = true;
}
Serial.printf("Button Interrupt Triggered %d times, current State=%u, time since last trigger %dms\n",
save, currentState, millis() - saveDebounceTimeout);
portENTER_CRITICAL_ISR(&mux); // can't change it unless, atomic - Critical section
numberOfButtonInterrupts = 0; // acknowledge keypress and reset interrupt counter
portEXIT_CRITICAL_ISR(&mux);
}
}
void ButtonHandler::begin()
{
pinMode(BUTTONPIN, INPUT);
pinMode(BUTTONPIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTONPIN), handleButtonInterrupt, CHANGE);
}
bool ButtonHandler::wasPressed()
{
const auto response = _buttonWasPressed;
_buttonWasPressed = false;
return response;
}
} // Buttons
...and consuming it in your main app:
#include <Buttons/ButtonHandler.h>
Buttons::ButtonHandler buttonHandler;
void setup() {
// ... other setup
buttonHandler.begin();
}
void loop() {
if (buttonHandler.wasPressed())
{
display.toggleDisplayMode();
refreshDisplay = true;
}
//... do other stuff
}