Button short and long press without library

I'm trying to detect independently short and long presses of a button with debounce and not using a library. Long press is detected while button held, not on release. I've simulated the following code in Wokwi and sometimes get a short indication after a long indication. Is there a simpler or more efficient way of doing this?

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT_PULLUP); // button to gnd
}

void loop() {
  // button hold with debounce
  static unsigned long  holdTime;
  static bool press, hold;
  if ( digitalRead(2) == LOW && press == false) {
    // one time on button press - on bounce first low
    holdTime = millis();
    press = true;
  }
  // only after bounce period check for button release
  if (millis() - holdTime > 300) {
    if (digitalRead(2) == HIGH && press == true && hold == false) {
      holdTime = millis();
      press = false;
      Serial.println("Short");
    }
  }
  // if button remains held for period
  if (millis() - holdTime > 1000) {
    if (digitalRead(2) == LOW && press == true && hold == false) {
      press = false;
      hold = true;
      holdTime = millis();
      Serial.println("Long");
    }
    // inhibit retrigger
    if (digitalRead(2) == HIGH && hold == true && millis() - holdTime > 1000) {
      press = false;
      hold = false;
    }
  }
}

Wokwi simulation

Imagine that I am the flash and I press the button but only for 50ms.

The first if statement will catch and set press to true and start holdTime. Then after 300ms, press is still true even though I've let go of the button 250ms ago. So it still registers as a short press.

This is happening on the last bounce when you release your long press. You need to set press back to false if you read HIGH on the button at any time.

This is for "long"

This is for "short"

1000 > 300 and when holdtime > 1000...

This allows the press release to be registered as "short"

Imagine something like this:

// if button is NOT pressed
if(digitalRead(pin) == HIGH) {
    if(longPress) {
          // handle your long press end
    } else if (press) {
          // handle short press end
    }     
    press = false;
    longPress = false;
    buttonPressStart = millis();  // constantly being updated until a press starts
}
if(millis() - buttonPressStart > 300) {
   press = true;
}
if(millis() - buttonPressStart > 1000) {
   longPress = true;
}

As counter intuitive as it sounds, you want to set the button start time to millis any time the button is NOT pressed. The start of a press is the last moment that it wasn't pressed. That makes the time easier to manage because you know that while the button isn't pressed, the elapsed time will always be zero. So now the elapsed time is all you have to check for.

1 Like

Hello mazellan

Welcome back :slight_smile:

Check and test this small button manager. This sketch has to be modified to your project needs.

enum ButtonStates {Released, Pressed};
enum TimerStates {Halt, Run};
uint32_t currentMillis = millis();
constexpr uint32_t DebounceInterval = 20;
uint32_t debouncePrevious = millis();
constexpr uint8_t ButtonPin = A0;
uint8_t stateOld = LOW;
constexpr uint32_t ClickInterval = 1000;
uint32_t clickPrevious = 20;
uint8_t clickControl = LOW;
void setup()
{
  Serial.begin(9600);
  Serial.println("Button short and long press without library");
  pinMode (ButtonPin, INPUT_PULLUP);
}
void loop()
{
  currentMillis = millis();
  if (currentMillis - debouncePrevious >= DebounceInterval)
  {
    debouncePrevious = currentMillis;
    uint8_t stateNew = digitalRead(ButtonPin) ? LOW : HIGH;
    if (stateOld != stateNew)
    {
      stateOld = stateNew;
      switch (stateNew)
      {
        case Pressed:
          Serial.println("Pressed");
          clickPrevious = currentMillis;
          clickControl = Run;

          break;
        case Released:
          Serial.println("Released");
          switch (clickControl)
          {
            case Halt:
              break;
            case Run:
              clickControl = Halt;
              Serial.println("pressed short");
              break;
          }
          break;
      }
    }
  }
  if (currentMillis - clickPrevious >= ClickInterval  and clickControl)
  {
    clickControl = Halt;
    Serial.println("pressed long");
  }
}

Have a nice day and enjoy coding in C++.

1 Like

Now that's a confusing construct. It just reverses the return value from digitalRead? That's an interesting level of obfuscation. Are you just trying to confuse the newbie?

Couldn't you just swap these:

Isn't that the whole purpose of extracting the values to an enum?

1 Like

Is your middle name Stefan.
I share knowledge so that the TO becomes curious to learn new things.
That's the purpose of this forum.
And if you don't like it, just ignore my well-intentioned programme examples!

1 Like

Thanks for that, it does exactly what I want, and I learnt some new C++ stuff too!

1 Like