Debounce function for virtual buttons

I am currently working on some code that reads button states over an i2c signal using a custom library.

Now I need to debounce the signals because currently any button press will spam the button like 20 times.

I’ve checked like 10 different debounce libraries, but they all have a hard dependency on digital gpio inputs.
E.g. the libraries require me to pass a pin number the the library then uses digitalRead to read the given pin.

Unfortunately I can’t call digitalRead in order to read my button states. Instead I have to call “nesController.getButtonA();”, “nesController.getButtonB();” etc…

Has anyone written a more general debounce function that I could use for this?

Here’s my code:

#include <NesContorller.h>

Accessory nesController;

void setup() {
    Serial.begin(115200);
    Serial.println("Initiating...");
    nesController.begin();
}

void loop() {
    nesController.readData();
    int up = nesController.getPadUp();
    int down = nesController.getPadDown();
    int left = nesController.getPadLeft();
    int right = nesController.getPadRight();
    int start = nesController.getButtonPlus();
    int select = nesController.getButtonMinus();
    int a = nesController.getButtonA();
    int b = nesController.getButtonB();

    if (up) {
        Serial.println("up");
    }
    if (down) {
        Serial.println("down");
    }
    if (left) {
        Serial.println("left");
    }
    if (right) {
        Serial.println("right");
    }
    if (start) {
        Serial.println("start");
    }
    if (select) {
        Serial.println("select");
    }
    if (a) {
        Serial.println("a");
    }
    if (b) {
        Serial.println("b");
    }
}

I don't think the problem is bounce. I think the problem is that you don't do State Change Detection and repeat the message as quickly as you can for as long as the button is pressed.

I would use an array to track the state of each button. That would make it easier to detect state changes AND debounce. Let me know if you'd like the code changes.

For a single button it would be something like below. For 'n>1' buttons I would change ButtonPin to a const byte array called ButtonPins[n] and change buttonWasPressed to a global boolean array called ButtonWasPressed[n].

const byte ButtonPin = 2;
const unsigned long DebounceTime = 30;
unsigned long ButtonStateChangeTime = 0;


void setup()
{
  Serial.begin(115200);


  pinMode(LED_BUILTIN, OUTPUT);
  pinMode (ButtonPin, INPUT_PULLUP);  // Button between Pin and Ground
}


void loop()
{
  checkButton(); 
}


void checkButton()
{
  unsigned long currentTime = millis();
  boolean buttonIsPressed = digitalRead(ButtonPin) == LOW;  // Active LOW
  static boolean buttonWasPressed = false;


  // Check for button state change and do debounce
  if (buttonIsPressed != buttonWasPressed &&
      currentTime  -  ButtonStateChangeTime > DebounceTime)
  {
    // Button state has changed
    ButtonStateChangeTime = currentTime;
    buttonWasPressed = buttonIsPressed;




    if (buttonIsPressed)
    {
      // Button was just pressed
    }
    else
    {
      // Button was just released
    }
  }
}

Thank you, that sounds about right, but how would I do this for my buttons that don't have pin numbers?

They don’t have pin numbers but they have a current and previous state

felic: how would I do this for my buttons that don't have pin numbers?

Replace this line:

 boolean buttonIsPressed = digitalRead(ButtonPin) == LOW;  // Active LOW

with something like this:

 boolean buttonIsPressed = nesController.getPadUp();

Okay, but how would this work in terms of arrays?

felic: Okay, but how would this work in terms of arrays?

Did you begin implementing the suggestion in reply #1? Arrays are documented in the reference section on this site.

Right, so I put the pin numbers in an array... Oh wait, I still don't have pin numbers. The whole point of my question was to find a more abstract debounce function that doesn't rely on pin numbers and digital read. I also don't like the idea of having redundant code. E.g. by creating individual debounce routines and variables for every button.

Something like this would be nice:

Debouncer upBtn = new Debouncer();
Debouncer leftBtn = new Debouncer();

void loop() {
    nesController.readData();
    int up = nesController.getPadUp();
    int left = nesController.getPadLeft();
    upBtn.setState(up);
    leftBtn.setState(left);
    int upDebounced = upBtn.getDebouncedState();
    int leftDebounced = leftBtn.getDebouncedState();
...

Take a look at the Bounce2 Library. It uses 2 classes to separate the debouncing function from the hardware it's debouncing. So, you can write a new class that inherits from the Debouncer class and links it to reading the button states over I2C. Then, you can put instances of that class in an array.

That looks promising, but it seems like I would have to write my own class inheriting from the Debounce class. I don't think my C++ is good enough to do this. :(

felic: That looks promising, but it seems like I would have to write my own class inheriting from the Debounce class. I don't think my C++ is good enough to do this. :(

Actually, simple inheritance is not that hard in C++. Even I have done it. :)

Where did you get the nesController library from ?

I wrote that one myself a long time ago Heavily inspired by WiiChuck, but never found the time to polish it so I would feel comfortable releasing it.

I agree simple inheritance is probably not complicated, but there is just way too much code in the base class that I don’t understand. I mean I don’t even understand the constructor. I’m just not a real C++ guy.

I have now tried to come up with something on my own:

// Debouncer HEADER
class Debouncer {
public:
    Debouncer(int debounceDelay, int initialState);
    void setOnChangeCallback(void (*onChangeCallback)(int));
    void setState(int newState);
private:
    int debounceDelay;
    void (*onChange)(int);
    int previousState;
    unsigned long lastStateChange;
};
///////////////////////

// Debouncer SOURCE
Debouncer::Debouncer(int debounceDelay, int initialState) {
    debounceDelay = debounceDelay;
    previousState = initialState;
    lastStateChange = 0;
}
void Debouncer::setCallback(void (*onChangeCallback)(int)) {
    onChange = onChangeCallback;
}

void Debouncer::setState(int newState) {
    unsigned long now = millis();
    if (newState != previousState && now > lastStateChange+debounceDelay) {
        this->onChange(newState);
        lastStateChange = now;
    }
    previousState = newState;
}
///////////////////////

I would imagine to be using it somewhat like this

#include <NesContorller.h>
Accessory nesController;

const int debounceDelay = 10;

Debouncer upBtn(debounceDelay, LOW);
Debouncer leftBtn(debounceDelay, LOW);

void setup() {
    Serial.begin(115200);
    Serial.println("Initiating...");
    nesController.begin();
    
    upBtn.setOnChangeCallback(onChangeUpBtn);
    leftBtn.setOnChangeCallback(onChangeUpBtn);
}
void loop() {
    nesController.readData();
    
    int up = nesController.getPadUp();
    int left = nesController.getPadLeft();
    
    upBtn.setState(up);
    leftBtn.setState(left);
    
}
void onChangeUpBtn(int newState) {
    if (newState == HIGH) {
        Serial.println("Up button pressed!");
    }
}

void onChangeLeftBtn(int newState) {
    if (newState == HIGH) {
        Serial.println("Left button pressed!");
    }
}

Could that work? Do you see any flaws with this?

felic: Okay, but how would this work in terms of arrays?

Without seeing the "Accessory" class, my best guess would be a function that takes an array index and calls the right Accessory method:

enum NESButtonNames
{
  NES_UP, NES_DOWN, NES_LEFT, NES_RIGHT,
  NES_PLUS, NES_MINUS, NES_A, NES_B
};

boolean getButtonState(byte index)
{
  switch (index)
  {
    case NES_UP: return nesController.getPadUp();
    case NES_DOWN: return nesController.getPadDown();
    case NES_LEFT: return nesController.getPadLeft();
    case NES_RIGHT: return nesController.getPadRight();
    case NES_PLUS: return nesController.getButtonPlus();
    case NES_MINUS: return nesController.getButtonMinus();
    case NES_A: return nesController.getButtonA();
    case NES_B: return nesController.getButtonB();
  }
  return false;
}

@johnwasser Thank you, that's a very interesting approach.

After spending all day working on this, I finally have what I wanted:

class Debouncer {
public:
    Debouncer(int debounceDelay, int repeatDelay, int repeatFrequency, int initialState);
    void setOnChangeCallback(void (*onChangeCallback)(int));
    void setState(int newState);
private:
    void (*onChange)(int);
    int debounceDelay;
    int repeatDelay;
    int repeatFrequency;
    int previousState;
    unsigned long lastStateChange;
    unsigned long lastAutoRepeat;
    int lastAutoRepeatState;
};
Debouncer::Debouncer(int _debounceDelay, int _repeatDelay, int _repeatFrequency, int initialState) {
    debounceDelay = _debounceDelay;
    repeatDelay = _repeatDelay;
    repeatFrequency = _repeatFrequency;
    previousState = initialState;
    lastStateChange = 0;
    lastAutoRepeat = 0;
    lastAutoRepeatState = initialState;
}

void Debouncer::setOnChangeCallback(void (*onChangeCallback)(int)) {
    onChange = onChangeCallback;
}

void Debouncer::setState(int newState) {
    unsigned long now = millis();
    if (newState != previousState && now > lastStateChange+debounceDelay) {
        this->onChange(newState);
        lastStateChange = now;
    } else if (newState == HIGH && previousState == HIGH && now > lastStateChange+repeatDelay) {
        if (now > lastAutoRepeat+1000/repeatFrequency) {
            int newAutoRepeatState;
            if (lastAutoRepeatState == LOW) {
                newAutoRepeatState = HIGH;
            } else {
                newAutoRepeatState = LOW;
            }
            this->onChange(newAutoRepeatState);
            lastAutoRepeatState = newAutoRepeatState;
            lastAutoRepeat = now;
        }
    }
    previousState = newState;
}

Glad you got the Debouncer object working.

This is how I would have done it with arrays. I don’t have the “Accessory” library so I can’t compile it but it should be close.

#include <NesContorller.h>
Accessory nesController;


enum NESButtonIndexes
{
  NES_UP, NES_DOWN, NES_LEFT, NES_RIGHT,
  NES_PLUS, NES_MINUS, NES_A, NES_B,
  NES_count // One greater than the last button index
};




const char * NESButtonNames[NES_count] =
{
  "Up", "Down", "Left", "Right", "Plus", "Minus", "A", "B"
}


const unsigned long DebounceTime = 30;


boolean ButtonWasPressed[NES_count];  // Defaults to 'false'
boolean ButtonChangedState[NES_count];  // Defaults to 'false'


unsigned long ButtonStateChangeTime = 0; // Debounce timer common to all buttons


void setup()
{
  Serial.begin(115200);


  Serial.println("Initiating...");
  
  nesController.begin();
}


void loop()
{
  nesController.readData();
  checkButtons();
  actOnButtons();
}


boolean getButtonState(byte index)
{
  switch (index)
  {
    case NES_UP: return nesController.getPadUp();
    case NES_DOWN: return nesController.getPadDown();
    case NES_LEFT: return nesController.getPadLeft();
    case NES_RIGHT: return nesController.getPadRight();
    case NES_PLUS: return nesController.getButtonPlus();
    case NES_MINUS: return nesController.getButtonMinus();
    case NES_A: return nesController.getButtonA();
    case NES_B: return nesController.getButtonB();
  }
  return false;
}


void checkButtons()
{
  unsigned long currentTime = millis();


  // Update the buttons
  for (byte i = 0; i < ButtonCount; i++)
  {
    boolean buttonIsPressed = getButtonState(i);
    ButtonChangedState[i] = false;


    // Check for button state change and do debounce
    if (buttonIsPressed != ButtonWasPressed[i] &&
        currentTime  -  ButtonStateChangeTime > DebounceTime)
    {
      // Button state has changed
      ButtonStateChangeTime = currentTime;
      ButtonChangedState[i] = true;
      ButtonWasPressed[i] = buttonIsPressed;
    }
  }
}


void actOnButtons()
{
  // Act on the the buttons
  for (byte i = 0; i < ButtonCount; i++)
  {
    if (ButtonChangedState[i])
    {
      if (ButtonWasPressed[i])
      {
        // Button was just pressed
        Serial.print("Pressed: ");
      }
      else
      {
        // Button was just released
        Serial.print("Released: ");
      }
      Serial.println(NESButtonNames[i]);
    } // End if WasPressed
    ButtonChangedState[i] = false;
  } // End if ChangedState
}

Thank you @johnwasser I really appreciate it!