Simultaneous short and long presses

Hi guys,

Trying to get more functionality from a simple two button controller. So far the individual button long and short press functions work well.

However after when an individual long press is released (on either button) i'd like to serial print 'normal'.

And for some reason the simultaneous short press is triggered straight away without chance of a the simultaneous long press being triggered and after it's been triggered, no other button presses work.

If anyone has any help it would be greatly appreciated as i'm struggling through this project. Thanks!

#include <ezButton.h>

const int SHORT_PRESS_TIME = 1000; // 1000 milliseconds
const int LONG_PRESS_TIME  = 1000; // 1000 milliseconds

ezButton button1(2);  // create ezButton object that attaches to pin 2
ezButton button2(3);  // create ezButton object that attaches to pin 3

unsigned long pressedTime1  = 0;
unsigned long releasedTime1 = 0;
bool isPressing1 = false;
bool isLongDetected1 = false;

unsigned long pressedTime2  = 0;
unsigned long releasedTime2 = 0;
bool isPressing2 = false;
bool isLongDetected2 = false;

void setup() {
  Serial.begin(9600);
  button1.setDebounceTime(50); // set debounce time to 50 milliseconds for button1
  button2.setDebounceTime(50); // set debounce time to 50 milliseconds for button2
}

void loop() {
  button1.loop(); // MUST call the loop() function for button1 first
  button2.loop(); // MUST call the loop() function for button2 first

  static bool simultaneousShortPress = false;
  static bool simultaneousLongPress = false;

  // Individual button 1 handling
  if (button1.isPressed()) {
    pressedTime1 = millis();
    isPressing1 = true;
    isLongDetected1 = false;
  }

  if (button1.isReleased()) {
    isPressing1 = false;
    releasedTime1 = millis();

    long pressDuration1 = releasedTime1 - pressedTime1;

    if (pressDuration1 < SHORT_PRESS_TIME && !simultaneousShortPress && !simultaneousLongPress) {
      Serial.println("Up");
      delay(200);
    }
  }

  if (isPressing1 && !isLongDetected1 && !simultaneousShortPress && !simultaneousLongPress) {
    long pressDuration1 = millis() - pressedTime1;

    if (pressDuration1 > LONG_PRESS_TIME) {
      Serial.println("Double");
      isLongDetected1 = true;
    }
  }

  // Individual button 2 handling
  if (button2.isPressed()) {
    pressedTime2 = millis();
    isPressing2 = true;
    isLongDetected2 = false;
  }

  if (button2.isReleased()) {
    isPressing2 = false;
    releasedTime2 = millis();

    long pressDuration2 = releasedTime2 - pressedTime2;

    if (pressDuration2 < SHORT_PRESS_TIME && !simultaneousShortPress && !simultaneousLongPress) {
      Serial.println("Down");
      delay(200);
    }
  }

  if (isPressing2 && !isLongDetected2 && !simultaneousShortPress && !simultaneousLongPress) {
    long pressDuration2 = millis() - pressedTime2;

    if (pressDuration2 > LONG_PRESS_TIME) {
      Serial.println("Half");
      isLongDetected2 = true;
    }
  }

// Simultaneous press handling
if (isPressing1 && isPressing2 && !simultaneousShortPress && !simultaneousLongPress) {
  long pressDuration1 = millis() - pressedTime1;
  long pressDuration2 = millis() - pressedTime2;

  if (pressDuration1 < SHORT_PRESS_TIME && pressDuration2 < SHORT_PRESS_TIME) {
    Serial.println("Looper");
    simultaneousShortPress = true;
    delay(200); // This delay may not be necessary, you may remove it if it interferes with your logic
  }

  if (pressDuration1 > LONG_PRESS_TIME && pressDuration2 > LONG_PRESS_TIME) {
    Serial.println("Kill dry");
    simultaneousLongPress = true;
  }
}

// Reset simultaneous press flags and other flags
if (button1.isReleased() && button2.isReleased()) {
  simultaneousShortPress = false;
  simultaneousLongPress = false;
  isLongDetected1 = false;
  isLongDetected2 = false;
}
}

You call the delay(); function, which takes responsiveness away from button reading. Are they needed?

I can't study or run your code, but I see

const int SHORT_PRESS_TIME = 1000; // 1000 milliseconds
const int LONG_PRESS_TIME  = 1000; // 1000 milliseconds

and wonder what roll they play in it, and whether a long press should be, um, longer in duration than a short press.

a7

Kinda' like this?

-jim lee

Yeah in practice with footswitches a debouncing delay was needed to stop one press skipping

Yeah a bit like this! But seems like this project only has individual short and long press functions which work on mine too, it’s just the both button short and long press functionality I can’t get to work properly

Aye, they're normally 250 and 1500 but the outcome is still the same!

use the OneButton library instead and short and long press are managed for you

Ah thanks I’ll try this! Do you reckon it’ll help with the simultaneous presses?

"Simultaneous" is a real time notion hard to define ➜ do you need to detect if both of them were pressed at the exact same millisecond, microsecond, nanosecond.... or you are just interested in the fact that both buttons are held down?

1 Like

The buttons themselves are footswitches so pressing at exactly the same time is quite difficult so I was hoping to have a little leeway to make it easier to trigger. Maybe as long as they're both clicked within 100-250ms of each other or something.

with most button libraries it's easy to know when the button is pressed or if it is currently pressed or not.

The Toggle library for example makes that easy

what's the actual requirement? what type of event do you need to track?

It's a midi controller, wanted to get the most functionality using just two footswitches. Just trying to serial print what the buttons will do for now (to get them working right) and then i'll put the midi program and control messages in.

Button 1 - short press (Scroll UP through settings 1-8),
Button 2 - short press (Scroll DOWN through settings 1-8),
Button 1 & 2 - short press (Activates a LOOPER with a control change message),
Button 1 - long press (DOUBLES the speed with a control change message, would like the speed to return to normal when button is released)
Button 2 - long press (HALVES the speed with a control change message, would like the speed to return to normal when button is released)
Button 1 & 2 - long press (KILL DRY signal with a control change message)

that's the tricky part to code.

As you said, both buttons won't be pressed exactly at the same time so If you detect a short press on button 1 you don't want to trigger "Scroll UP" right away to get a chance to catch a potential short press on button 2 in which case you want to send LOOPER instead

You also need to decide if the press is going to turn into a long presss, so you can't react to the button going down, you need to wait long enough to know if it's turning into a long press

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

Yeah so the way it works currently is short presses are all detected on the release of the button (when it's under the short press time and the long presses are detected after the set time has passed and not when released.

Thank you for the reading recommendation i'll definitely do that!

Here's some code that was not the solution to another inquiry, but may be useful here.

I do not use long and short presses, but I think the general approach to same-time could be exploited with short and long added.

Any solution will necessarily need to delay action on one whilst waiting to see if the other is going to be added in.

That's a basic UI issue. Normally we would not ask someone to press two buttons at the same time to mean something different to just pressing them both, or one after the other.

In the case of big computers, for example, a double click should always be what a single click does, plus something.

That way, the code can get started on what a single click means, then check at a certain time to see if the extended action is required (a spatially and temporally congruent click is received).

This code and demo sees either button, waits a bit for the other or triggers the "both" immediately it knows we in a both sitch. Triggers the sole button's relay when it has waited long enough for the other button. Turns off all relays after a duration you can set.

Try it here.


Wokwi_badge UA Switch Off Relays


// https://forum.arduino.cc/t/programming-help-switch-off-relays/1239207
// https://wokwi.com/projects/393250806386976769

# include <ezButton.h>

# define WINDOW     1000  // for "simultaneous"
# define DURATION   2000  // active period for relays

const byte ledPins[] = {12, 11, 10};  // A, B, BOTH
const byte buttonPins[] = {A1, A2};

ezButton buttonA(buttonPins[0]);  // create ezButton object that attach to pin 7;
ezButton buttonB(buttonPins[1]);  // create ezButton object that attach to pin 7;

void setup()
{
  Serial.begin(115200);
  Serial.println("\nJello Whirled!\n");

  buttonA.setDebounceTime(20);
  buttonB.setDebounceTime(20);
}

unsigned long now;
unsigned long durationTimer;
unsigned long bothTimer;

bool needA;
bool needB;
bool awaitin;

void loop()
{
  now = millis();

  buttonA.loop();
  buttonB.loop();

  bool pressA = buttonA.isPressed();
  bool pressB = buttonB.isPressed();

  if (pressA) {
    Serial.println("A request");
    needA = true;
    if (!awaitin) {
      bothTimer = now;
      awaitin = true;
    }
  }

  if (pressB) {
    Serial.println("B request");
    needB = true;
    if (!awaitin) {
      bothTimer = now;
      awaitin = true;
    }
  }

  if ((awaitin && now - bothTimer > WINDOW) || (needA && needB)) {
    if (needA && needB) {
      digitalWrite(ledPins[2], HIGH);
    }
    else if (needA) {
      digitalWrite(ledPins[0], HIGH);
    }
    else digitalWrite(ledPins[1], HIGH);

    durationTimer = now;
    needA = false;
    needB = false;
    awaitin = false;
  }

  if (now - durationTimer > DURATION) {
    digitalWrite(ledPins[0], LOW);
    digitalWrite(ledPins[1], LOW);
    digitalWrite(ledPins[2], LOW);
  }
}

I'd use the same idea, flags and waiting for adjustable times, tune when deployed. 1000 milliseconds is the current window, just too large but there for testing. I found 100 milliseconds yet sluggish but for me needed to be that long to press two buttons at the "same" time. Not operating with my feet, they prolly need more time?

a7

1 Like

I wonder if you could solve the problem by ANDing the button1 and Button2 state and emit it on another pin, and then read the pin back in as button3.

But one would still have to resolve the logic between the interactions between the paired button behavior and their component buttons, just as in the #1 case. If in a paired button state, act when one of them releases, and ignore buttons events until both are released?

A state machine is the way to go--map out all the possible pressed state combinations (perhaps enum PressedState {None,Pressed1,Pressed2, PressedBoth, HalfReleased} pressState=None; ) and handle the transitions between them. In particular Pressed1->PressedBoth, Pressed2 -> PressedBoth, and PressedBoth->HalfReleased could easily handle ignoring the paired button release events

I agree with @alto777 that your use of multiple modality for your buttons makes the user interface quite complicated and error prone.

double clicks are easier to handle than long press and button independence would be better.

1 Like

@aaron04051 - I've said I don't use long and short. It's even worse, I don't use double clicking or chords or anything.

Nothing is more certain to raise my blood pressure than messing with something that woukd be way easier with a few more buttons…

That said, it seems like N distinct outcomes should be easily possible with two buttons.

if a short press is detected on button A
   
   if button B is down
      ONE do short A with B
   else
      TWO do short A

if a long press is detected on button A
   
   if button B is down
      THREE do long A with B
   else
      FOUR do long A

and symmetry using B first pressed gives four more perfectly distinguished modes of reaction,: short B, long B, short B with A, long B with A. Now which means "disarm" and which means "launch"...

It appeals to the musician in me, kinda like playing a flam right-handed or left-handed

one of the basic patterns of drumming consisting of a stroke preceded by a grace note.

Although it feels more like the first foot would be the stroke and the second foot would be gracing it if present.

a7

1 Like

Perhaps something like the state machine in this modified ezbutton multiple button example could help:

/*
   Created by ArduinoGetStarted.com
   modified for state machine and Wokwi for
   https://forum.arduino.cc/t/simultaneous-short-and-long-presses/1240315/16

   This example code is in the public domain

   Tutorial page: https://arduinogetstarted.com/tutorials/arduino-button-library
   Github: https://github.com/ArduinoGetStarted/button/blob/master/examples/05.MultipleButtonAll/05.MultipleButtonAll.ino

   This example:
     + uses debounce for multiple buttons.
     + reads state of multiple buttons
     + detects the pressed and released events of multiple buttons
       uses a state machine to manage simultaneous button1&2 events
*/

#include <ezButton.h>

const int verbose = 0;

enum PressedState {None, Pressed1, Pressed2, PressedBoth, HalfReleased} pressedState = None;
uint32_t lastPressTime = 0;
ezButton button1(6);  // create ezButton object that attach to pin 6;
ezButton button2(7);  // create ezButton object that attach to pin 7;
ezButton button3(8);  // create ezButton object that attach to pin 8;

void setup() {
  Serial.begin(9600);
  button1.setDebounceTime(50); // set debounce time to 50 milliseconds
  button2.setDebounceTime(50); // set debounce time to 50 milliseconds
  button3.setDebounceTime(50); // set debounce time to 50 milliseconds
}

void loop() {
  button1.loop(); // MUST call the loop() function first
  button2.loop(); // MUST call the loop() function first
  button3.loop(); // MUST call the loop() function first

  int btn1State = button1.getState();
  int btn2State = button2.getState();
  int btn3State = button3.getState();
  if (verbose) {
    Serial.print("button 1 state: ");
    Serial.println(btn1State);
    Serial.print("button 2 state: ");
    Serial.println(btn2State);
    Serial.print("button 3 state: ");
    Serial.println(btn3State);
  }
  if (button1.isPressed())
    Serial.println("The button 1 is pressed");

  if (button1.isReleased())
    Serial.println("The button 1 is released");

  if (button2.isPressed())
    Serial.println("The button 2 is pressed");

  if (button2.isReleased())
    Serial.println("The button 2 is released");

  if (button3.isPressed())
    Serial.println("The button 3 is pressed");

  if (button3.isReleased())
    Serial.println("The button 3 is released");

  switch (pressedState) {
    case None:
      if (button1.isPressed()) {
        lastPressTime = millis();
        pressedState = Pressed1;
      }
      if (button2.isPressed()) {
        lastPressTime = millis();
        pressedState = Pressed2;
      }
      break;

    case Pressed1:
      if (button1.isReleased()) {
        pressedState = None;
        Serial.print("Button1_time:");
        Serial.println(millis() - lastPressTime);
      }
      if (button2.isPressed()) {
        pressedState = PressedBoth;
        lastPressTime = millis();
      }
      break;
    case Pressed2:
      if (button2.isReleased()) {
        pressedState = None;
        Serial.print("Button2_time:");
        Serial.println(millis() - lastPressTime);
      }
      if (button1.isPressed()) {
        pressedState = PressedBoth;
        lastPressTime = millis();
      }
      break;
    case PressedBoth:
      if (button1.isReleased() || button2.isReleased() ) {
        pressedState = HalfReleased;
        Serial.print("Both_time:");
        Serial.println(millis() - lastPressTime);
      }
      break;
    case HalfReleased:
      if (button1.isReleased() || button2.isReleased() ) {
        pressedState = None;
        Serial.print("Both_time:");
        Serial.println(millis() - lastPressTime);
      }
      break;
  }
  report();
}

A state machine is great for managing independence between features.