Manage short and long press for a counter

HI, I'm triyng to write the code for a simple counter.
I need this function.
Whit Short press of the button, value will be incremented by 0.01.

If I hold Down the button, after 500 ms value will be incremented always by 0.01, but i need a faster increment.

I can't figure out on how to implement this function.

Can you help me?

Many thanks

#include <LiquidCrystal.h>
 
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);           // select the pins used on the LCD panel

long prev = 0;
int buttonState = 0;  // 0 = not pressed   --- 1 = long pressed --- 2 short pressed
float value = 0;
#define button 2 
int longpresstime = 500;	

void setup(){
   lcd.begin(16, 2);               
   pinMode(button, INPUT_PULLUP);
}

void loop(){
  lcd.setCursor(0,0);             
  lcd.print(value);
  buttonState = 0;
  if(digitalRead(button) == LOW){
    prev = millis();
    buttonState = 1;
    while((millis()-prev)<=longpresstime){
      if((digitalRead(button))){
        buttonState = 2;
        break;
      }
    }
  }
  
  if(!buttonState){
    //do nothing
  }else if(buttonState == 1){
	  value = value + 0.01;
    // TODO button is pressed long
  }else if(buttonState == 2){
	  value = value + + 0.01;
  }
  
}
.

you want to add 0.01 to value at 2 different speeds

the fastest you can do this is every loop so the question is shouldn't you be trying to slow down how often 0.01 is added in the first 500ms as the fastest speed is already controlled by the loop speed

I don't think you need to monitor button state. really you just need 2 timers

if button is pressed add 0.01 every 100ms

after 500ms add 0.01 every 0ms

basically this is one timer (500ms) controlling the interval of the second timer (this one adds the 0.01 at either 100ms or 0ms)

I wrote a example using serial print. Is this what you are trying to do?

open serial monitor to see how it works

p.s slow speed is set at 3 seconds and slow count at 100ms per addition.

float value = 0;
#define buttonPin 2
unsigned long interval_1 = 100;
unsigned long interval_2 = 3000;

unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  unsigned long currentMillis = millis();

  byte button = digitalRead(buttonPin);//added this as it assists
  //with problem solving e.g i can print state of the button

  //below is a timer that only runs when button is pressed
  //try to understand that the time stamp is being written
  //when the button is high
  //every loop in the code so it acts like its being reset
  // previousMillis2 = currentMillis;


  if (currentMillis - previousMillis2 >= interval_2) {
    //timer2 is done
    interval_1 = 0;//this is for timer1
  } else {
    //timer2 not done
    interval_1 = 100;//this is for timer1
  }

  if (button == LOW) {//only do this with button pushed

    //add 0.01 based on timer1
    if (currentMillis - previousMillis1 >= interval_1) {
      value = value + 0.01;
      previousMillis1 = currentMillis;
    }
  } else {//button is high, not pressed
    previousMillis2 = currentMillis;
  }

  Serial.print(interval_1);
  Serial.print(" ");
  Serial.println(value);

}

made some changes to your original code. It doesn't work as you have said it would (count at 2 different speeds) but the state change seems to works now.

compare to your original code to see the changes made (mainly had to move the time stamp and change the >< as it was changing state from 0 to 2 then after 500ms to 1)

try to understand how the time stamp works when using millis() timers. taken a new time stamp every loop then comparing it on the next line is never going to work.

long prev = 0;
int buttonState = 0;  // 0 = not pressed   --- 1 = long pressed --- 2 short pressed
float value = 0;
#define button 2
int longpresstime = 3000;

void setup() {
  Serial.begin(9600);
  pinMode(button, INPUT_PULLUP);
}

void loop() {

  buttonState = 0;
  if (digitalRead(button) == LOW) {
    // prev = millis();
    buttonState = 1;
    while ((millis() - prev) >= longpresstime) {
      if (digitalRead(button == LOW)) {
        buttonState = 2;
        break;
      }
    }
  } else {
    prev = millis();
  }

  if (!buttonState) {
    //do nothing
  } else if (buttonState == 1) {
    value = value + 0.01;
    // TODO button is pressed long
  } else if (buttonState == 2) {
    value = value + + 0.01;
  }
  Serial.print(buttonState);
  Serial.print(" ");

  Serial.println(value);
}

I found this code which seems to deal nicely with different button push times.

...R
Edited to correct the link reference - apologies for inconvenience

thanks for your help.
Robin, I see your code in linked post.

btnVal = digitalRead(btnPin);
if (btnVal == HIGH) {    // assumes btn is LOW when pressed
   lastBtnPressMillis = millis();   // btn not pressed so reset clock
}
if (millis() - lastBtnPressMillis > = interval) {
   // button has been pressed for longer than interval
}

but I can't understand it.

Please, can you post the entire part of code regarding button short and long press?

Many thanks

Humble apologies. I gave you the wrong link. This is the correct link. I will update the earlier Reply.

...R

Great! Reading your link, I have almost solved my issue.
This is the code

#include <LiquidCrystal.h>
#include <Time.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define buttonPin 2 // digital input pin to use as a digital input

#define debounce 20 // ms debounce period to prevent flickering when pressing or releasing the button
#define holdTime 750 // ms hold period: how long to wait for press+hold event

// Button variables
int buttonVal = 0; // value read from button
int buttonLast = 0; // buffered value of the button's previous state
long btnDnTime; // time the button was pressed down
long btnUpTime; // time the button was released
boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered

//Value to increment
float value = 0;


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


void setup(){
	lcd.begin(16, 2);              //Start Initial Banner 
	// Set button input pin
	pinMode(buttonPin, INPUT_PULLUP);

}
//=================================================

void loop(){
	
	lcd.setCursor(0,0);
	lcd.print(value);

	// Read the state of the button
	buttonVal = digitalRead(buttonPin);

	// Test for button pressed and store the down time
	if (buttonVal == LOW && buttonLast == HIGH && (millis() - btnUpTime) > long(debounce)){
		btnDnTime = millis();
	}

	// Test for button release and store the up time
	if (buttonVal == HIGH && buttonLast == LOW && (millis() - btnDnTime) > long(debounce)){
		if (ignoreUp == false) event1();
		else ignoreUp = false;
		btnUpTime = millis();
	}

	// Test for button held down for longer than the hold time
	if (buttonVal == LOW && (millis() - btnDnTime) > long(holdTime)){
		event2();
		ignoreUp = true;
		btnDnTime = millis();
	}

	buttonLast = buttonVal;
}


//=================================================
// Events to trigger by click and press+hold

void event1(){
	value = value + 0.01;
}

void event2(){
	value = value + 0.10;
}

So, now with a Short press the value is incremented by 0.01 while when a press+hold is detected, the value is incremented by 0.10.

I see that holdTime affect the increment speed. So, if the hold time is 750ms, during press+hold the value is incremented by 0.10 every 750 ms.
My goal is:

  • wait 750ms to start press+hold function
  • run function every 300 ms in order to get a faster increment

is that possible?

mik.

Why not just use the guy's checkButton() function and adjust the longHoldTime to suit your need?

...R

Robin2:
Why not just use the guy's checkButton() function and adjust the longHoldTime to suit your need?

...R

I see the checkbutton() function and tested it.
the main issue is that executes the press+hold function only one time until button is released.

mikseven:
I see the checkbutton() function and tested it.
the main issue is that executes the press+hold function only one time until button is released.

I am not really familiar with that code and I don't understand the exact problem you are dealing with.

Can you explain exactly what you want to happen.

And then explain the shortcomings in that code.

I find it much easier to expand from a known working position than to start with a complex solution.

...R

Thanks.
Well, what I want to happen is this:

  • Short press - Increment by 0.01
  • Press + Hold - wait 750 ms and then increment by 0.10 with a frequency of 350ms until I release the button. For this function also a 0.01 increment with a frequency of 35 ms is accepted

In the first code holdTime affect also the frequency of increments.

#include <LiquidCrystal.h>
#include <Time.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define buttonPin 2 // digital input pin to use as a digital input

#define debounce 20 // ms debounce period to prevent flickering when pressing or releasing the button
#define holdTime 350 // ms hold period: how long to wait for press+hold event

// Button variables
int buttonVal = 0; // value read from button
int buttonLast = 0; // buffered value of the button's previous state
long btnDnTime; // time the button was pressed down
long btnUpTime; // time the button was released
boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered

//Value to increment
float value = 0;


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


void setup(){
	lcd.begin(16, 2);              //Start Initial Banner 
	// Set button input pin
	pinMode(buttonPin, INPUT_PULLUP);

}
//=================================================

void loop(){
	
	lcd.setCursor(0,0);
	lcd.print(value);

	// Read the state of the button
	buttonVal = digitalRead(buttonPin);

	// Test for button pressed and store the down time
	if (buttonVal == LOW && buttonLast == HIGH && (millis() - btnUpTime) > long(debounce)){
		btnDnTime = millis();
	}

	// Test for button release and store the up time
	if (buttonVal == HIGH && buttonLast == LOW && (millis() - btnDnTime) > long(debounce)){
		if (ignoreUp == false) event1();
		else ignoreUp = false;
		btnUpTime = millis();
	}

	// Test for button held down for longer than the hold time
	if (buttonVal == LOW && (millis() - btnDnTime) > long(holdTime)){
		event2();
		ignoreUp = true;
		btnDnTime = millis();
	}

	buttonLast = buttonVal;
}


//=================================================
// Events to trigger by click and press+hold

void event1(){
	value = value + 0.01;
}

void event2(){
	value = value + 0.10;
}

in the second code, achieved from link you posted, all function related to button are executed only on button release, so if I press and hold the button the increment will be execute only one time at button release.
I have inserted variuos increment values for any event, in order to monitor behavior.

#include <LiquidCrystal.h>
#include <Time.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define buttonPin 2 // analog input pin to use as a digital input


// Button timing variables
int debounce = 20; // ms debounce period to prevent flickering when pressing or releasing the button
int DCgap = 250; // max ms between clicks for a double click event
int holdTime = 750; // ms hold period: how long to wait for press+hold event
int longHoldTime = 1000000; // ms long hold period: how long to wait for press+hold event

// Other button variables
boolean buttonVal = HIGH; // value read from button
boolean buttonLast = HIGH; // buffered value of the button's previous state
boolean DCwaiting = false; // whether we're waiting for a double click (down)
boolean DConUp = false; // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true; // whether it's OK to do a single click
long downTime = -1; // time the button was pressed down
long upTime = -1; // time the button was released
boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false; // when held, whether to wait for the up event
boolean holdEventPast = false; // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already

//value to increment
int value = 0;

void setup(){
  lcd.begin(16, 2);              //Start Initial Banner 
  // Set button input pin
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop(){
  lcd.setCursor(0,0);
  lcd.print(value);
// Get button event and act accordingly
int b = checkButton();
if (b == 1) clickEvent();
if (b == 2) doubleClickEvent();
if (b == 3) holdEvent();
if (b == 4) longHoldEvent();
}

//=================================================
// Events to trigger by click and press+hold

void clickEvent() {
value++;
}

void doubleClickEvent() {
value = value +2;
}

void holdEvent() {
value = value + 10;
}

void longHoldEvent() {
value = value + 50;
}




int checkButton()
{ 
int event = 0;
// Read the state of the button
buttonVal = digitalRead(buttonPin);
// Button pressed down
if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce) {
downTime = millis();
ignoreUp = false;
waitForUp = false;
singleOK = true;
holdEventPast = false;
longHoldEventPast = false;
if ((millis()-upTime) < DCgap && DConUp == false && DCwaiting == true) DConUp = true;
else DConUp = false;
DCwaiting = false;
}
// Button released
else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce) { 
if (not ignoreUp) {
upTime = millis();
if (DConUp == false) DCwaiting = true;
else {
event = 2;
DConUp = false;
DCwaiting = false;
singleOK = false;
}
}
}
// Test for normal click event: DCgap expired
if ( buttonVal == HIGH && (millis()-upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true) {
event = 1;
DCwaiting = false;
}
// Test for hold
if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
// Trigger "normal" hold
if (not holdEventPast) {
event = 3;
waitForUp = true;
ignoreUp = true;
DConUp = false;
DCwaiting = false;
//downTime = millis();
holdEventPast = true;
}
// Trigger "long" hold
if ((millis() - downTime) >= longHoldTime) {
if (not longHoldEventPast) {
event = 4;
longHoldEventPast = true;
}
}
}
buttonLast = buttonVal;
return event;
}

I can see now why there is a limitation in the other guy's code.

All I can suggest is a few more hours thinking about the problem and its solution.

Maybe the answer is as simple as recording millis() when the button is pressed (after being released) and doing various things at intervals thereafter.

When the button is released and if the time was short do whatever is appropriate for a short press.

...R

did you ever test the code I posted in reply#2

gpop1:
did you ever test the code I posted in reply#2

ops, I totally missed your post.

Just Tried (added some changes and display). Yes now I'm almost near the target.

With short press i have 0.01 increments.
If i hold the button it increments by 0.01 at frequency of 200 until it reach the interval 2. Then it increments by 0.01 at frequency of 30.
It's almost ok, but i see some debouncing on short press.

I'm thinking a solution to avoid it

#include <LiquidCrystal.h>
#include <Time.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

float value = 0;
#define buttonPin 2
unsigned long interval_1 = 30;
unsigned long interval_2 = 1000;

unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;

void setup() {
  lcd.begin(16, 2);              //Start Initial Banner 
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  unsigned long currentMillis = millis();

byte  button = digitalRead(buttonPin);//added this as it assists
  //with problem solving e.g i can print state of the button

  //below is a timer that only runs when button is pressed
  //try to understand that the time stamp is being written
  //when the button is high
  //every loop in the code so it acts like its being reset
//previousMillis2 = currentMillis;


  if (currentMillis - previousMillis2 >= interval_2) {
    //timer2 is done
    interval_1 = 30;//this is for timer2
  } else {
    //timer2 not done
    interval_1 = 200;//this is for timer1
  }

  if (button == LOW) {//only do this with button pushed

    //add 0.01 based on timer1
    if (currentMillis - previousMillis1 >= interval_1) {
      value = value + 0.01;
      previousMillis1 = currentMillis;
    }
  } else {//button is high, not pressed
    previousMillis2 = currentMillis;
  }

  Serial.print(interval_1);
  Serial.print(" ");
  Serial.println(value);
  lcd.setCursor(0,0);
  lcd.print(value);
}

just add another timer that says the button has to be low for 25ms before it is accepted as low

carnt compile as I have a different lcd library but I think this works

#include <LiquidCrystal.h>
#include <Time.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

float value = 0;
#define buttonPin 2
unsigned long interval_1 = 30;
unsigned long interval_2 = 1000;

unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
unsigned long previousMillis3 = 0;

void setup() {
  lcd.begin(16, 2);              //Start Initial Banner
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  unsigned long currentMillis = millis();
 byte  button = digitalRead(buttonPin); 
if (currentMillis - previousMillis2 >= interval_2) {
    //timer2 is done
    interval_1 = 30;//this is for timer2
  } else {
    //timer2 not done
    interval_1 = 200;//this is for timer1
  }

  if (button == LOW) {//only do this with button pushed
    if (currentMillis - previousMillis3 >= 25) {//has button been low for 25ms
    //yep do this
      if (currentMillis - previousMillis1 >= interval_1) {
        value = value + 0.01;
        previousMillis1 = currentMillis;
      }
    }
  } else {//button is high
    previousMillis2 = currentMillis;
    previousMillis3 = currentMillis;
  }
  Serial.print(interval_1);
  Serial.print(" ");
  Serial.println(value);
  lcd.setCursor(0,0);
  lcd.print(value);
}

gpop1:
did you ever test the code I posted in reply#2

Seems like you had a better sense of what the OP actually needed.

I had been assuming the OP had studied your suggestion and found it unsuitable.

...R

Robin2:
Seems like you had a better sense of what the OP actually needed.

I had been assuming the OP had studied your suggestion and found it unsuitable.

...R

Sorry, what is OP?

:slight_smile:

mikseven:
Sorry, what is OP?

:slight_smile:

original poster.

gpop1:
original poster.

Ops.

So i'm studying on both code because anyone has a characteristic that i'm looking.