Bounce2 library modification

I have a nice project that has about 12 button inputs, and sends musical notes over midi BLE. Latency is very important as the human ear can detect latency as slow at 30-50ms.
I just realized I can greatly reduce my latency, if I were to remove the 25ms debounce time from my buttons. I still need debounce, but I don't need it on the button push, if that makes sense. In other words, as soon as the code were to detect a button press, it would be okay to signal it as a button press. It could debounce the release of the button. maybe another way to explain it would be that the debounce would only monitor the state change of the release. So, the instant the button is pressed, it will fire a button flag, but then debounce the button after that. It seems like it wouldn't be too hard to maybe just use the same debounce2 library, but add in a line of two that would do something like:
If the button hasn't been pushed in awhile (no state change for the debounce time 25ms), then go ahead a pass it as Pressed. I think I can press this, but it seems a bit mind boggling. Maybe I'd be better to write my own little ButtonPressed function rather than using the library. There's so much coding in the library I don't understand the syntax of, as I'm just a self taught arduino tinkerer that was born before the age of computers. If anyone has a suggestion to the best way to go about this, I think the current 25ms debounce time would be greatly noticed if it were gone.
thanks
Here is my setup for my buttons, using the bounce2 library.

#include <Bounce2.h>


#define BUTTON_A_PIN 1
#define BUTTON_B_PIN 26
#define BUTTON_C_PIN 19
#define BUTTON_D_PIN 9
#define BUTTON_E_PIN 17
#define BUTTON_F_PIN 16
#define BUTTON_G_PIN 18
#define BUTTON_7th_PIN 11
#define BUTTON_MINOR_PIN 12
#define BUTTON_MENU_SINGLE_PIN 15
#define BUTTON_SELECT_SINGLE_PIN 14

Bounce2::Button ButtonA = Bounce2::Button();
Bounce2::Button ButtonB = Bounce2::Button();
Bounce2::Button ButtonC = Bounce2::Button();
Bounce2::Button ButtonD = Bounce2::Button();
Bounce2::Button ButtonE = Bounce2::Button();
Bounce2::Button ButtonF = Bounce2::Button();
Bounce2::Button ButtonG = Bounce2::Button();
Bounce2::Button Button7th = Bounce2::Button();
Bounce2::Button ButtonMinor = Bounce2::Button();
Bounce2::Button ButtonMenu = Bounce2::Button();
Bounce2::Button ButtonSelect = Bounce2::Button();


void setup_buttons() {
  ButtonA.attach(BUTTON_A_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonA.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonA.setPressedState(LOW);
  ButtonB.attach(BUTTON_B_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonB.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonB.setPressedState(LOW);
  ButtonC.attach(BUTTON_C_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonC.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonC.setPressedState(LOW);
  ButtonD.attach(BUTTON_D_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonD.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonD.setPressedState(LOW);
  ButtonE.attach(BUTTON_E_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonE.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonE.setPressedState(LOW);
  ButtonF.attach(BUTTON_F_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonF.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonF.setPressedState(LOW);
  ButtonG.attach(BUTTON_G_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonG.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonG.setPressedState(LOW);
  Button7th.attach(BUTTON_7th_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  Button7th.interval(25); // Use a debounce interval of 25 milliseconds
  Button7th.setPressedState(LOW);
  ButtonMinor.attach(BUTTON_MINOR_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonMinor.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonMinor.setPressedState(LOW);
  ButtonMenu.attach(BUTTON_MENU_SINGLE_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonMenu.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonMenu.setPressedState(LOW);
  ButtonSelect.attach(BUTTON_SELECT_SINGLE_PIN, INPUT_PULLUP); // Attach with INPUT_PULLUP mode
  ButtonSelect.interval(25); // Use a debounce interval of 25 milliseconds
  ButtonSelect.setPressedState(LOW);
}

void update_Buttons() {
  ButtonA.update(); // Update the button instance
  ButtonB.update(); // Update the button instance
  ButtonC.update(); // Update the button instance
  ButtonD.update(); // Update the button instance
  ButtonE.update(); // Update the button instance
  ButtonF.update(); // Update the button instance
  ButtonG.update(); // Update the button instance
  Button7th.update(); // Update the button instance
  ButtonMinor.update(); // Update the button instance
  ButtonMenu.update(); // Update the button instance
  ButtonSelect.update(); // Update the button instance
}

Here is the main coding in the debounce2 library. It's a very good button library.

// Please read Bounce2.h for information about the liscence and authors


#include "Bounce2.h"

//////////////
// DEBOUNCE //
//////////////

Debouncer::Debouncer():previous_millis(0)
    , interval_millis(10)
    , state(0) {}

void Debouncer::interval(uint16_t interval_millis)
{
    this->interval_millis = interval_millis;
}

void Debouncer::begin() {
	 state = 0;
    if (readCurrentState()) {
        setStateFlag(DEBOUNCED_STATE | UNSTABLE_STATE);
    }

	#ifdef BOUNCE_LOCK_OUT
    previous_millis = 0;
#else
    previous_millis = millis();
#endif
}

bool Debouncer::update()
{

    unsetStateFlag(CHANGED_STATE);
#ifdef BOUNCE_LOCK_OUT
    
    // Ignore everything if we are locked out
    if (millis() - previous_millis >= interval_millis) {
        bool currentState = readCurrentState();
        if ( currentState != getStateFlag(DEBOUNCED_STATE) ) {
            previous_millis = millis();
            changeState();
        }
    }
    

#elif defined BOUNCE_WITH_PROMPT_DETECTION
    // Read the state of the switch port into a temporary variable.
    bool readState = readCurrentState();


    if ( readState != getStateFlag(DEBOUNCED_STATE) ) {
      // We have seen a change from the current button state.

      if ( millis() - previous_millis >= interval_millis ) {
	// We have passed the time threshold, so a new change of state is allowed.
	// set the STATE_CHANGED flag and the new DEBOUNCED_STATE.
	// This will be prompt as long as there has been greater than interval_misllis ms since last change of input.
	// Otherwise debounced state will not change again until bouncing is stable for the timeout period.
		 changeState();
      }
    }

    // If the readState is different from previous readState, reset the debounce timer - as input is still unstable
    // and we want to prevent new button state changes until the previous one has remained stable for the timeout.
    if ( readState != getStateFlag(UNSTABLE_STATE) ) {
	// Update Unstable Bit to macth readState
        toggleStateFlag(UNSTABLE_STATE);
        previous_millis = millis();
    }
    
    
#else
    // Read the state of the switch in a temporary variable.
    bool currentState = readCurrentState();
    

    // If the reading is different from last reading, reset the debounce counter
    if ( currentState != getStateFlag(UNSTABLE_STATE) ) {
        previous_millis = millis();
         toggleStateFlag(UNSTABLE_STATE);
    } else
        if ( millis() - previous_millis >= interval_millis ) {
            // We have passed the threshold time, so the input is now stable
            // If it is different from last state, set the STATE_CHANGED flag
            if (currentState != getStateFlag(DEBOUNCED_STATE) ) {
                previous_millis = millis();
                 

                 changeState();
            }
        }

    
#endif

		return  changed(); 

}

// WIP HELD
unsigned long Debouncer::previousDuration() const {
	return durationOfPreviousState;
}

unsigned long Debouncer::currentDuration() const {
	return (millis() - stateChangeLastTime);
}

inline void Debouncer::changeState() {
	toggleStateFlag(DEBOUNCED_STATE);
	setStateFlag(CHANGED_STATE) ;
	durationOfPreviousState = millis() - stateChangeLastTime;
	stateChangeLastTime = millis();
}

bool Debouncer::read() const
{
    return  getStateFlag(DEBOUNCED_STATE);
}

bool Debouncer::rose() const
{
    return getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE);
}

bool Debouncer::fell() const
{
    return  !getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE);
}

////////////
// BOUNCE //
////////////


Bounce::Bounce()
    : pin(0)
{}

void Bounce::attach(int pin) {
    this->pin = pin;
    
    // SET INITIAL STATE
    begin();
}

void Bounce::attach(int pin, int mode){
    setPinMode(pin, mode);
    this->attach(pin);
}




Sounds like you want the "PROMPT DETECTION" mode that Bounce2 already supports. It's described in the library's README.

Oh! I think I'm already getting an instant read on the buttons without delay. In my code when I check the buttons, I use something like this:
if (ButtonA.isPressed()) return keyNote[keySignature][0];

and I see this in the library, which looks like it doesn't check for debounce, it just returns that button state. So, I guess my buttons aren't adding latency. Which is good. Or bad, in the sense that I was hoping to improve upon the latency.
thanks.

@brief Returns true if the button is currently physically pressed.
  */
  inline bool isPressed() const {
    return read() == getPressedState();
  };

I don't think so. According to the README, the default mode is "STABLE INTERVAL". So, that's what you get unless you configure the library for "WITH PROMPT DETECTION".

I think that comment may be a little misleading. From my quick read of the source code, the result of the isPressed() function is still subject to the debouncing mode selected per the README.

Thanks! I’ll have to look into it some more.

Charlie

what about this code which only imposes on delay (using millis()) for each button and doesn't delay recognizing a different button

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };


struct But  {
    const byte    pin;
    byte          state;
    unsigned long msecLst;
};

But buts [] = { {A1}, {A2}, {A3}};
#define N_BUT   (sizeof(buts)/sizeof(But))

// -----------------------------------------------------------------------------
#define DebounceMsec    10
int
chkButtons ()
{
    unsigned long  msec = millis ();

    But *b = & buts [0];
    for (unsigned n = 0; n < sizeof(pinsBut); n++, b++)  {
        if (b->msecLst && (msec - b->msecLst) < DebounceMsec)
            continue;

        b->msecLst = 0;

        byte but = digitalRead (pinsBut [n]);

        if (b->state != but)  {
            b->state = but;
            b->msecLst  = msec ? msec : 1;      // make sure non-zero

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (buts [n].pin, INPUT_PULLUP);
        buts [n].state = digitalRead (buts [n].pin);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

This.

If you care about 25 milliseconds, and I get that, you want to completely own what is going on and how fast.

Take a vacay from developing your project and do a deep dive into debouncing. End up either with your own perfect debouncing code, or discover it on your journey.

I've looked at many button libraries, there are those that provide for extremely fast recognition. There are also many that are flawed.

On some AVR, processors, a simple hardware technique can be used effectively.

Do you need the best response for button release as well?

The key to reacting rapidly is realizing that pushbuttons do not appear closed unless they are on the way to being fully closed, nor do they appear open unless on the way to being fully open.

The rest is software. Some code looks plausible but woukd disappoint, for example, if several buttons were pressed at once.

Some will say 10 milliseconds is nothing. Obvsly they have never worked with percussionists. :expressionless:

Even if you end up finding a good enough or even a great library, and go into the future using it blindly, it will have been good to figure out two or three of the many ways you will come across if you take the time.

I use a library, never mind which, when the code is meant to be shared for any purpose, and none of that ever cares too much about 10 or 20 ms. For my own stuff, I use my own button object which does exactly what I need, no more, no less.

a7

Should have

and

Read like you understand it until you do. Those two guys never are a waste of your time.

a7

While there’s nothing wrong with learning about switch debouncing and writing your own code as suggested above by several folks, it depends on how you want to spend your time now.

I dug deeper into the Bounce2 library, and it will indeed do exactly what you want. All you have to do is uncomment this line in Bounce2.h:

// Uncomment the following line for "BOUNCE_WITH_PROMPT_DETECTION" debounce method
//#define BOUNCE_WITH_PROMPT_DETECTION

Then, a button change will be recognized immediately … assuming that the last button change happened longer ago than the timeout period. The length of the timeout (in ms) can be set with the ‘interval()’ function.

1 Like

Thanks!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.