Long/short button press issue

Im trying to make a counter that can either count automatically upon a "short press" and manual pressing button increases counter after "long press" is activated. Here is my code: I am having trouble figuring out how to exit the "long press mode".

int LED1 = 14;
int LED2 = 13;
int button = 10;
int i = 0;
int previousMillis = 0;  
int bpm=500;

boolean LED1State = false;
boolean LED2State = false;

long buttonTimer = 0;
long longPressTime = 250;

boolean buttonActive = false;
boolean longPressActive = false;

int switchstate3 =0;
int prevswitchstate3=0;

void setup() {

  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(button, INPUT);
}

void loop() {

  if (digitalRead(button) == HIGH) {
    if (buttonActive == false) {
      buttonActive = true;
      buttonTimer = millis();
    }
    if ((millis() - buttonTimer > longPressTime) && (longPressActive == false)) {
      longPressActive = true;
      LED1State = !LED1State;
      digitalWrite(LED1, LED1State);
    }
  } else {
    if (buttonActive == true) {
      if (longPressActive == true) {
        longPressActive = false;
      } else {
        LED2State = !LED2State;
        digitalWrite(LED2, LED2State);
      }
      buttonActive = false;
    }
  }

  
while(longPressActive==true){
  switchstate3 = digitalRead(button);
  if(switchstate3!=prevswitchstate3){
    if(i < 8){
      if(digitalRead(button)==HIGH){
        delay(200);
        i=i+1;   
        Serial.println(i);
      }
    }else
    i=0;
  }
 }
  
if(longPressActive==false){
  unsigned long currentMillis = millis();
  if(i < 8){
    if(currentMillis - previousMillis > 60000/bpm) {
      previousMillis = currentMillis;  
      
      i=i+1;
      Serial.println(i);
    }
  }else
  i = 0;
}

}

You shouldn't lock yourself in a while in the first place :wink: The loop() is already looping, use that :slight_smile:

And you can make it already a bit easier by using state change detection and/or grabbing a library to do the button work for you like Bounce2.

After you get your sketch working, take a look at using the SwitchManager library from Nick Gammon.

example:

/*SwitchManager skeleton 
 LarryD
 
 This is sketch is to introduce new people to the SwitchManager library written by Nick Gammon
 
 The library handles switch de-bouncing and provides timing and state change information in your sketch.
 The SwitchManager.h file should be placed in your libraries folder, i.e.
 C:\Users\YouName\Documents\Arduino\libraries\SwitchManager\SwitchManager.h
 You can download the library at:
 http://gammon.com.au/Arduino/SwitchManager.zip    Thank you Nick!
 
 In this example we have 2 normally open (N.O.) switches connected to the Arduino - increment and decrement.
 The increment switch will also be used as a "Reset" switch if pressed for more than two seconds.
 The two switches are connected between GND (0 volts) and an Arduino input pin.
 The library enables pull-up resistors for your switch inputs.
 Pushing a switch makes its pin LOW. Releasing a switch makes its pin HIGH.
 
 The SwitchManager library provides 10ms de-bounce for switches. 
 i.e. enum { debounceTime = 10, noSwitch = -1 };
 If you need more time, edit the SwitchManager.h file
 i.e. enum { debounceTime = 50, noSwitch = -1 }; //here it is changed to 50ms
 */

#include <SwitchManager.h>             
//object instantiations
SwitchManager myIncSwitch;
SwitchManager myDecSwitch;

unsigned long currentMillis;
unsigned long heartBeatMillis;
unsigned long heartFlashRate  = 500UL; // time the led will change state       
unsigned long incShortPress   = 500UL; // 1/2 second
unsigned long incLongPress    = 2000UL;// 2 seconds 
unsigned long decShortPress   = 500UL; // 1/2 second

const byte heartBeatLED       = 13;
const byte incSwitch          = 4; //increment switch is on Arduino pin 4
const byte decSwitch          = 5; //decrement switch is on Arduino pin 5

int myCounter;

//======================================================================

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

  //gives a visual indication if the sketch is blocking
  pinMode(heartBeatLED, OUTPUT);  

  myIncSwitch.begin (incSwitch, handleSwitchPresses); 
  myDecSwitch.begin (decSwitch, handleSwitchPresses);
  //the handleSwitchPresses() function is called when a switch changes state

} //                   E N D  O F  s e t u p ( )

//======================================================================

void loop()
{
  //leave this line of code at the top of loop()
  currentMillis = millis();

  //***************************
  //some code to see if the sketch is blocking
  if (CheckTime(heartBeatMillis, heartFlashRate, true))
  {
    //toggle the heartBeatLED
    digitalWrite(heartBeatLED,!digitalRead(heartBeatLED));
  }

  //***************************
  //check to see what's happening with the switches
  //"Do not use delay()s" in your sketch as it will make switch changes unresponsive 
  //Use BlinkWithoutDelay (BWD) techniques instead.
  myIncSwitch.check ();  
  myDecSwitch.check (); 

  //***************************
  //put other non-blocking stuff here


} //                      E N D  O F  l o o p ( )


//======================================================================
//                          F U N C T I O N S
//======================================================================


//                        C h e c k T i m e ( ) 
//**********************************************************************
//Delay time expired function
//parameters:
//lastMillis = time we started
//wait = delay in ms
//restart = do we start again  

boolean CheckTime(unsigned long  & lastMillis, unsigned long wait, boolean restart) 
{
  //has time expired for this task?
  if (currentMillis - lastMillis >= wait) 
  {
    //should this start again? 
    if(restart)
    {
      //yes, get ready for the next iteration
      lastMillis = millis();  
    }
    return true;
  }
  return false;

} //                 E N D   o f   C h e c k T i m e ( )


//                h a n d l e S w i t c h P r e s s e s( )
//**********************************************************************

void handleSwitchPresses(const byte newState, const unsigned long interval, const byte whichPin)
{
  //  You get here "ONLY" if there has been a change in a switches state.

  //When a switch has changed state, SwitchManager passes this function 3 arguments:
  //"newState" this will be HIGH or LOW. This is the state the switch is in now.
  //"interval" the number of milliseconds the switch stayed in the previous state
  //"whichPin" is the switch pin that we are examining  

  switch (whichPin)
  {
    //***************************
    //are we dealing with this switch?
  case incSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The incSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= incShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter++;
        if(myCounter > 1000)
        {
          //limit the counter to a maximum of 1000
          myCounter = 1000; 
        }
        Serial.println(myCounter);
      }

      //was this a long press followed by a switch release
      else if(interval >= incLongPress) 
        //we could also have an upper limit
        //if incLongMillis was 2000UL; we could then have a window between 2-3 seconds
        //else if(interval >= incLongMillis && interval <= incLongMillis + 1000UL) 
      {
        //this could be used to change states in a StateMachine
        //in this example however, we will just reset myCounter
        myCounter = 0;
        Serial.print("My counter value is = ");
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW 
    else 
    {
      Serial.println("The incSwitch was just pushed");
    } 

    break; //End of case incSwitch

    //*************************** 
    //are we dealing with this switch?
  case decSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The decSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= decShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter--;
        if(myCounter < 0) 
        {
          //don't go below zero
          myCounter = 0;
        }
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW
    else 
    {
      Serial.println("The decSwitch switch was just pushed");
    } 

    break; //End of case decSwitch

    //*************************** 
    //Put default stuff here
    //default:
    //break; //END of default

  } //End switch (whichPin)

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )


//======================================================================
//                      E N D  O F  C O D E
//======================================================================

I did this SimplePress for multiple presses and a long press:

#include "SimplePress.h"

const byte buttonPin0 = 2;
const byte buttonPin1 = 3;
const byte buttonPin2 = 4;

SimplePress button0(buttonPin0, 500);         // half second capture for multi-presses; uses default 200ms debounce
                                              // a long press is defined as greater than the capture time (500ms in this example)
                                              // or...
SimplePress button1(buttonPin1, 500, 150);    // or specify custom debounce time in ms

SimplePress button2(buttonPin2, 500);

void setup() 
{
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  button0.begin();
  button1.begin();
  button2.begin();
}

void loop() 
{
  if(int pressCount = button0.pressed())  // check to see if there is a button press event
  {
    switch(pressCount)  // the event can be any positive integer (the number of button presses) or -1 in the case of a long press
    {
      case -1:
        Serial.println(F("Button 0 long Press"));
        longFlashPin13();
        break;
      case 1:
        Serial.println(F("Button 0 one Press"));
        flashPin13(1);
        break;
      case 2:
        Serial.println(F("Button 0 two Presses"));
        flashPin13(2);
        break;
    }
  }
  if(int press2count = button1.pressed())
  {
    switch(press2count)
    {
      case -1:
        Serial.println(F("Button 1 long Press"));
        break;
      case 1:
        Serial.println(F("Button 1 one Press"));
        break;
    }
  }


  if (button2.pressed()) // Simple press detection <<<<<<<<<
  {
    Serial.println(F("Button 2 Pressed!")); 
  }
}

void flashPin13(byte numTimes)
{
  for(byte i = 0; i < numTimes; i++)
  {
    digitalWrite(13, HIGH);
    delay(250);
    digitalWrite(13, LOW);
    if (i < numTimes - 1) delay(250);
  }
}

void longFlashPin13(void)
{
  digitalWrite(13, HIGH);
  delay(1000);
  digitalWrite(13, LOW);
}

and the header:

#ifndef SIMPLEPRESS_H
#define SIMPLEPRESS_H

#include <Arduino.h>

class SimplePress{
  public:
    SimplePress(int _pin, uint32_t _pressInterval, uint32_t _debouncePeriod = 200);
    bool begin();
    int8_t pressed();

  private:
    byte pressCount;
    byte lastState;
    byte pin;
    uint32_t lastMillis;
    uint32_t debouncePeriod;
    uint32_t pressInterval;
};

SimplePress::SimplePress(int _pin, uint32_t _pressInterval, uint32_t _debouncePeriod)
{
  pin = _pin;
  debouncePeriod = _debouncePeriod;
  pressInterval = _pressInterval;
}

bool SimplePress::begin()
{
  pinMode(pin, INPUT_PULLUP);
  lastState = HIGH;
  return true;
}

int8_t SimplePress::pressed()
{
  byte nowState = digitalRead(pin);
  if(nowState != lastState)
  {
    if(millis() - lastMillis < debouncePeriod) return 0;
    if(nowState == LOW)
    {
      lastMillis = millis();
      pressCount++;
    }
    else 
    {
      if (millis() - lastMillis > pressInterval) // a long press
      {
        lastState = nowState;
        pressCount = 0;
        return -1;
      }
    }
  }
  if(pressCount != 0)
  {
    if(millis() - lastMillis > pressInterval and nowState == HIGH)
    {
      int presses = pressCount;
      pressCount = 0;
      return presses;
    }
  }
  lastState = nowState;
  return 0;
}

#endif

Im trying to make a counter that can either count automatically upon a "short press" and manual pressing button increases counter after "long press" is activated.

Between your code and this statement I'm a little confused.

Is this what you want ?

A button press increments a count by 1.
If the button is released within 250 ms, the increment by one is all you get until you press the button again.
If the button is held down for more than 250 ms, the count increases by one every 300 ms thereafter while the button is being held down.
When the button is released the count incrementing stops.

If this is correct, it is very easy to do with the Bounce2 library as @septillion suggests.

In fact, there is a Bounce2 library example called "Retrigger" which shows how to take action on a button press, repeat something at an interval while it is pressed (but using a non blocking timer and an if statement), and take action on release. The example is written for INPUT_PULLUP and an led, but it should be very easy to adapt to a counter.

great, ill check that out! thanks

Just took a look at that "Retrigger" example and it is not quite what I want. Let me try to explain a little better.

-no button press >250ms
counter increases by 1 automatically at a rate set by "currentMillis - previousMillis > 60000/bpm"

-hold button for 250ms or more
counter freezes at its current value, short button presses increase the value

-hold button again for 250ms or more --> exit "long press mode", return to the automatic counter increaser

I have it almost working, however I cant seem to exit the long press mode.

-no button press >250ms

What does this mean?

It could be saying button presses over 250ms are ignored.

Please use full sentences.

-no button press >250ms
counter increases by 1 automatically at a rate set by "currentMillis - previousMillis > 60000/bpm"

-hold button for 250ms or more
counter freezes at its current value, short button presses increase the value

-hold button again for 250ms or more --> exit "long press mode", return to the automatic counter increaser

What are you trying to do with this interface. It seems backwards to the normal method of incrementing by one with a short press and release, and incrementing automatically with a longer hold.

larryd:
What does this mean?

It could be saying button presses over 250ms are ignored.

Please use full sentences.

Begin in with "long press" mode off. If there is a button press that is less than 250 ms, do not enter "long press" mode. instead continue counting automatically at a duration set by "currentMillis - previousMillis > 60000/bpm".

If the button is pressed for a period of 250ms or more, enter "long press" mode. In long press mode, the counter only advances when the button is pressed for a short duration of less than 250 ms. If, while you are in "long press" mode, and the button is held for 250ms or more, you exit long press mode and return to the automatic counter.

cattledog:
What are you trying to do with this interface. It seems backwards to the normal method of incrementing by one with a short press and release, and incrementing automatically with a longer hold.

I am creating a step sequencer that freezes the automatic counter upon long button press. When the sequencer has been frozen at its current step, it can be increased by pressing on the button for a short duration. It goes back to its normal auto increase when the button is again held for a long period of time.

I am creating a step sequencer that freezes the automatic counter upon long button press. When the sequencer has been frozen at its current step, it can be increased by pressing on the button for a short duration. It goes back to its normal auto increase when the button is again held for a long period of time.

I think this code does what you want. There is a timeout(not a button release) to enter the long press

#include <Bounce2.h>

#define buttonPin 5
#define shortTime 250
#define longTime 250

#define noEvent    0
#define shortPress 1
#define longPress  2

// Instantiate a Bounce object called button :
Bounce button = Bounce();

unsigned long buttonPressStartTimeStamp;
unsigned long buttonPressDuration;
boolean startTimeout = false;
boolean endTimeout = false;
byte event;

boolean shortPressEnabled = false;
boolean autoEnabled = true;
byte longPressCount = 0;
unsigned int counter = 0;
unsigned int lastCounter = 0;
unsigned long lastAutoIncrement = millis();
int bpm = 500;
unsigned int autoIncrementInterval = 60000UL / bpm;

void setup()
{
  Serial.begin(115200);
  pinMode(buttonPin, INPUT_PULLUP);
  button.attach(buttonPin);
  button.interval(20);//set debounce interval
}
void loop()
{
  event = checkButton();

  if (counter != lastCounter)
  {
    Serial.println(counter);
    lastCounter = counter;
  }

  switch (event)
  {
    case shortPress:
      if (shortPressEnabled)
      {
        counter++;
      }
      break;

    case longPress:
      longPressCount ++;
      if (longPressCount == 1)
      {
        Serial.println("long press freeze counter");
        Serial.println("use short press to increment counter or long press to restart autoIncrement");
        autoEnabled = false;
        shortPressEnabled = true;
      }
      if (longPressCount == 2)
      {
        Serial.println("long press restart counter");
        autoEnabled = true;
        lastAutoIncrement = millis();
        shortPressEnabled = false;
        longPressCount = 0;
      }
      break;

    case noEvent:
      if (autoEnabled == true)
      {
        if (millis() - lastAutoIncrement >= autoIncrementInterval)
        {
          lastAutoIncrement += autoIncrementInterval;
          counter++;
        }
      }
  }
}

byte checkButton()
{
  byte event = noEvent;
  // Update the Bounce instance, does digitalRead of button
  button.update();

  // Button press transition from HIGH to LOW)
  if (button.fell())
  {
    buttonPressStartTimeStamp = millis();
    startTimeout = true;
  }

  // Button release transition from LOW to HIGH) :
  if (button.rose())
  {
    buttonPressDuration = (millis() - buttonPressStartTimeStamp);
    startTimeout = false;
  }

  if (buttonPressDuration > 0 && buttonPressDuration <= shortTime)
  {
    event = shortPress;
    buttonPressDuration = 0;
  }

  if (startTimeout == true && (millis() - buttonPressStartTimeStamp) > longTime)
  {
    event = longPress;
    startTimeout = false;
    buttonPressDuration = 0;
  }
  return event;
}