Detect short and long button press using millis

Different actions based on how long the user presses a button

detect short long button press using millis

To detect a short and long button press using millis can give your project more functionality without adding more buttons. In this line-by-line example, I show how to react to a user pressing a button for a short period (100ms) or a long period (over 500ms). My example changes the blink rate of an LED on short presses. A long button press turns off the LED.

In the code, I make use of a struct so that a single variable can be used to track multiple parameters. The benefit of this method is that adding multiple buttons is easy. You could create an array of these tyepdef’d variables.

Download At GitHub

Detect Short and Long Button Press using millis() code

Click expand to see the full code and copy into your sketch. Or you can also click the button below to grab the code from my GitHub.

// detectButtonPress
// Use millis to detect a short and long button press
// See baldengineer.com/detect-short-long-button-press-using-millis.html for more information
// Created by James Lewis

#define PRESSED LOW
#define NOT_PRESSED HIGH

const byte led = 13;

const unsigned long shortPress = 100;
const unsigned long  longPress = 500;

long blinkInterval = 500;
unsigned long previousBlink=0;
bool ledState = true;
bool blinkState = true;

typedef struct Buttons {
	const byte pin = 2;
	const int debounce = 10;

	unsigned long counter=0;
	bool prevState = NOT_PRESSED;
	bool currentState;
} Button;

// create a Button variable type
Button button;

void setup() {
  pinMode(led, OUTPUT);
  pinMode(button.pin, INPUT_PULLUP);
}

void loop() {
	// check the button
	button.currentState = digitalRead(button.pin);

	// has it changed?
	if (button.currentState != button.prevState) {
		delay(button.debounce);
		// update status in case of bounce
		button.currentState = digitalRead(button.pin);
		if (button.currentState == PRESSED) {
			// a new press event occured
			// record when button went down
			button.counter = millis();
		}

		if (button.currentState == NOT_PRESSED) {
			// but no longer pressed, how long was it down?
			unsigned long currentMillis = millis();
			//if ((currentMillis - button.counter >= shortPress) && !(currentMillis - button.counter >= longPress)) {
			if ((currentMillis - button.counter >= shortPress) && !(currentMillis - button.counter >= longPress)) {
				// short press detected. 
				handleShortPress();
			}
			if ((currentMillis - button.counter >= longPress)) {
				// the long press was detected
				handleLongPress();
			}
		}
		// used to detect when state changes
		button.prevState = button.currentState;
	} 

	blinkLED();
}

void handleShortPress() {
	blinkState = true;
	ledState = true;
	blinkInterval = blinkInterval / 2;
	if (blinkInterval <= 50)
		blinkInterval = 500;
}

void handleLongPress() {
	blinkState = false;
	ledState = false;
}

void blinkLED() {
	// blink the LED (or don't!)
	if (blinkState) {
		if (millis() - previousBlink >= blinkInterval) {
			// blink the LED
			ledState = !ledState;
			previousBlink = millis();
		}
	} else {
		ledState = false;
	}
	digitalWrite(led, ledState);
}

Download At GitHub

Global Variables

#define PRESSED LOW
#define NOT_PRESSED HIGH

Here I am using #define statements to create macros. Later in the code, these macros make reading if-statements error free. Since I am using a pull-up resistor, the logic is “inverted” from what most people expect. The pin defaults to HIGH. While pressing the button takes the pin LOW. Are you using a pull-down resistor? Then swap the HIGH and LOW on these two lines. (And disable the pull-up later in the code.)

const byte led = 13;

A constant variable for the pin with an LED. I could have used the Arduino constant LED_BUILTIN, which makes the code board independent. However, old habits die hard. If you’re using this code on an ESP8266, there probably isn’t a pin 13.

const unsigned long shortPress = 100;
const unsigned long  longPress = 500;

For your project or application, you may need to change these values. They determine how much time, in milliseconds, must pass to register as a short or long press. For example, “shortPress” might be large for the type of button you’re using. Using these as constants make it easier to tweak the code. Keep in mind, if you want to change them within the code, remove the “const” keyword.

long blinkInterval = 500;
unsigned long previousBlink=0;
bool ledState = true;
bool blinkState = true;

These variables have nothing to do with detecting the short and long button press. Instead, it is for my example of what to do once you detect them.

The variable “blinkInterval” controls the blink rate of the LED. Later in the example, you will see this change value on each short press. To track the time the LED is on or off, the variable “previousBlink” stores the current value from millis(). It is important to declare this value as an “unsigned long.”

The boolean variable “ledState” tracks whether the LED should be ON or OFF. While the “blinkState” variable determines if blinking should occur at all.

typedef struct Buttons {
   const byte pin = 2;
   const int debounce = 10;

   unsigned long counter=0;
   bool prevState = NOT_PRESSED;
   bool currentState;
} Button;

This block of code creates a struct. A struct, or structure, groups variables together under one name. Dot notation accesses parameters inside of the structure. For example, “button.pin” obtains the pin property of the object button. The debounce property enables a software debounce.

The next three variables are for the state tracking of the button. Variable “counter” keeps track of the millis() value when the button action occurs. While “currentState” and “prevState” are used to determine when the button transitions.

You might be wondering why is this a “typedef struct.” Long story short, this allows us to create objects (or variables) of the type “Button” easily. For more information, read this Stack Overflow on Difference between ‘struct’ and ‘typedef struct’ in C++.

A subtle point occurs on line 26. We’ve created a definition of a variable type called “Button.” Notice the uppercase.

Button button;

Why bother with all of the “struct” and “typedef” stuff? Line 29 illustrates the value. First, we’ve created an object or variable called “button.” Notice the lowercase. Each property of button can be accessed using dot notation, like “digitalRead(button.pin);” or “button.counter=0;”.

What if you wanted to use more than 1 button? Well, in that case, you could create an array of the “Button” type. Then you could access multiple buttons with something like “if (button[0].prevState)”. In this example, I stick to a single button, but extending to multiple buttons would be minimal effort.

void setup()

void setup() {
  pinMode(led, OUTPUT);
  pinMode(button.pin, INPUT_PULLUP);
}

Nothing special in setup() for this example. Set our LED pin to OUTPUT and enable the PULL-UP resistor on our input button. Again, the LED is not necessary for the short and long button press code; it is just part of my example.

void loop()

Inside of loop(), we have a very simple state machine. Let me quickly explain the logic I am using, and then I’ll go through each line/block of code. First I get the value of the button connected to the pin. Next, I check to see if the pin’s state has changed. If the pin shows a pressed button, we capture the current time. If the pin is showing a not-pressed button, then we check to see how long it had been pressed. Based on the time pressed, we call the functions “handleShortPress” and “handleLongPress.”

button.currentState = digitalRead(button.pin);

Nothing special here, just a digitalRead() of the pin property of our object button. Remember, you could create an array of buttons. In that case, you would use a for-loop to check the state of each button.

if (button.currentState != button.prevState) {

Later you’ll see we store the current state of the button. If the state changes we know the button was pressed or released. So next we find that out.

delay(button.debounce);

This line is a bit of a cheat. Even though I do not like using delay(), it is handy for debouncing a button. If your code is so time sensitive that a few milliseconds of de-bounce affects it, I would recommend using a hardware debounce. The delay time used, button.debounce, is a property of the button object. This extra property is in case you’re using different types of buttons that bounce differently. I have a debouncing using millis() or micros() tutorial if you want to avoid delay().

button.currentState = digitalRead(button.pin);

This line may not be needed, but I feel better double-checking the state of the pin after the bounce event.

if (button.currentState == PRESSED) {
   // a new press event occured
   // record when button went down
   button.counter = millis();
}

Very simple, we are checking to see if the button is in the “pressed” or “not pressed” state. This if-statement is why I use the #define. To me, it is natural to read this as “if the button is pressed.” When just checking “HIGH and LOW” the line would read “if the pin is low.” Then you would have to think whether the button is active-high or active-low.

The only action in this if-statement is to capture the current value of millis(). This value is used to detect whether there is a short or long button press later in the code. Keep in mind; we only capture this value when the button’s state changes. So this line only happens once per press.

if (button.currentState == NOT_PRESSED) {

If the button is not pressed, then we do a little bit more work. In this code, no action will happen until you release the button. Later I’ll give some ideas on how you can change this behavior. For now, focus on the next lines.

unsigned long currentMillis = millis();

Readers that have looked at my other millis() examples and even my Line-By-Line: Blink Without Delay, will recognize this line. We get a timestamp of the current value of millis(). The next couple of lines checks millis() multiple times. I wanted to make sure millis() is stable so that weird behavior at rollover can be avoided.

if ((currentMillis - button.counter >= shortPress) && !(currentMillis - button.counter >= longPress)) {

Here is the most complicated line of the entire example. The first part of the statement is probably obvious. We are looking to see if enough time for a “short press” occurred. However, a long press would mean a short press also occurred. The “&&” performs a logical-AND. Both statements must evaluate TRUE for the entire if-statement to be TRUE.

The second statement looks to see if the longPress time has been exceeded and then inverts the value. So if the press time is between 100 and 500 milliseconds, we get TRUE && TRUE.

Why not just use less-than? Because when millis() rollover occurs, there is going to be an imbalance in the logic. By keeping the logic the same, rollover is handled correctly.

handleShortPress();

To separate my blinking example from the code to detect a short and long button press, I made a function to call when a short press occurs.

if ((currentMillis - button.counter >= longPress)) {
	// the long press was detected
	handleLongPress();
}

Now we are checking to see if a long press happened. Again, no action will occur until the user releases the button. Just like on line 57, I made a function to handle the long press event.

button.prevState = button.currentState;

This code is the end of the original if-statement back on line 41. It stores the current value of the button press so that we can compare the new button value on each iteration of loop(). This line enables us to detect the state change.

blinkLED();

Calling “blinkLED()” is for my example and not related to the short and long button press millis() code.

blinkLED, handle short and long

Lines 71-96, I am not going to explain in this post. The code causes the LED to blink quicker on each short press. Once the blink rate is so fast that the LED is solid, the blink time is reset. A long button press will disable the blinking.

Couple of notes

As I said earlier, you could create an array of the button objects. This array would make handling multiple buttons a matter of modifying the above code to support arrays. (You could also brute force the code above. Maybe that is a future tutorial.)

If you want the code to react as soon as a short or long button press occurs, you could move the if-statements for the button.counter into the main loop(). In that case, I would add a “button.currentState == PRESSED” to the if-statement.

I would encourage you to move all of the button code into a function and call the function from loop(). It would make reading your main loop, or state machine, much easier.

Conclusion

Like most of my millis() examples, this one came from someone asking. What other types of basic behaviors would you like to see implemented in millis? Leave a comment.

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

Leave a comment

9 thoughts on “Detect short and long button press using millis

  1. i need a modification that the led go high if the pin went high for more than 5 sec and stay high …and the led go low when the pin be low for more than 5 sec … on other way i work with temp sensor i want it if became more than 35c and stabled for 5 sec turn relay on and if became less than 30 and stabled for 5 sec turn relay off ..this is it …and thank you alot your projects were great.

  2. Hello, this is great! I want to control 3 Leds with 6 buttons (up an down).
    In the example you say its easy to make an array to add multiple buttons.
    question: How do I do this? do you have an example somewhere?
    other question: do I need to change anything inside the struct? becouse it points to pin 2.
    (ps English is not my native language 🙂

    thanks in advance

    • If you don’t need to detect long and short presses, it is just a matter of creating arrays. One change to this code would be not making “pin” and “debounce” a constant. You would need to assign them in setup(). It would look something like this

      const byte numberOfButtons = 6;
      // an array for each button pin being used
      int buttonPins[numberOfButtons] = {2,3,4,5,10,11};
      // one constant for debounce, but could also make an array
      const int debounce = 10;
      // an array of button objects
      Button buttons[numberOfButtons];
      
      
      void setup() {
        pinMode(led, OUTPUT);
        // initialize each button in the array
        for (int x; x < numberOfButtons; x++) {
        	// assign the Pin to individual button
        	buttons[x].pin = buttonPins[x];
        	buttons[x].debounce = debounce;
        	pinMode(buttons[x].pin, INPUT_PULLUP);	
        }
       
      }
      
      void loop() {
      	// check the buttons
      	for (int x; x< numberofButtons; x++) {
      		buttons[x].currentState = digitalRead(buttons[x].pin);	
      	}
      	// rest of code continues like this... replacing button with buttons[x].
      
  3. So my programming experience runs more procedural than “structural” and I had a question. I see you define the attributes of the button as part of the structure. If I were looking at having more than one button would it make more sense to create the button instances then change the pin number (for example) or to change it to have some sort of constructor? (I’m pretty shaky on object oriented too :D) Thanks.