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.)

// line 9
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.

// line 11
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, that if you want to change them within the code, remove the “const” keyword.

// line 14
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 to 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 the 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()

// line 31
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.”

// line 38
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.

// line 41
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.

// line 42
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 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().

// line 44
button.currentState = digitalRead(button.pin);

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

// line 45
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 about 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 that we only capture this value when the button’s state changes. So this line only happens once per press.

// line 51
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.

// line 53
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.

//line 55
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.

// line 57
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.

// line 59
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.

// line 65
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.

// line 68
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 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.

Author

Fan of making things beep, blink and fly. Created AddOhms. Stream on Twitch. Video Host on element14 Presents and writing for Hackster.IO. Call sign KN6FGY.

19 Comments

  1. Thank you for taking your time to post these tutorials. They have helped loads. However, Millis is driving me bonkers. I want to delay the response to an input from a temp sensor. If the temp goes above 70 for 4 minutes I want to turn on a fan. But I want the fan to NOT turn on if the temp drops below 70 within that 4 minutes. Then reset. It seems “easy” to tell something to happen at a timed interval but it’s madding to get the code put together to jump on the millis train and then if necessary get off before the station. I’ve tried “if” and “”&&” to bring both parameters together. I’ve tried tweaking your long/short button push thinking that might work but no joy. If this is better suited for another command besides millis() please point me in that direction. Thank you!

    *

    I brought the time down to 4 seconds for debugging. The (tooHot) yellowLed works as supposed, but in the first code below the blueLed comes on with the yellowLed. There is no delay.

    unsigned long previousMillis = 0

    void setup(){

    void loop() {

    if (tooHot){digitalWrite(yellowLed, HIGH);}
    else {digitalWrite(yellowLed, LOW);}

    unsigned long currentMillis = millis();
    previousMillis = currentMillis;

    FIRST ALTERNATE (blueLed comes on with yellowLed)

    if (tooHot) {
    ((currentMillis – previousMillis) >= 4000);{
    digitalWrite(blueLed, HIGH);

    }}

    ALTERNATE (no blueLed at all)

    if ((tooHot) && (currentMillis – previousMillis) >= 4000UL){
    digitalWrite(blueLed, HIGH);}
    else {digitalWrite(blueLed, LOW);}

    ALTERNATE (no blueLed at all)

    if (tooHot){
    if (currentMillis – previousMillis >= 4000UL){
    digitalWrite(blueLed, HIGH);}
    else {digitalWrite(blueLed, LOW);}
    }

    I’ve also assigned the “unsigned long hotWait = 4000UL” just in case the formula didn’t like mixing words and numbers. No joy.

    • The logic I would use is to keep doing the millis() checks regardless of the fan’s state. Then use a seperate variable to track if the fan should be off, on, or whatever. So inside of the millis() checks, you are then checking to see what to do with the fan.

  2. Adrián Veiga López Reply

    Hi.

    I am trying that a strip of LEDs behave in one way or another depending on the input by 2 pins (ESTADO_LUZ and ESTADO_MARCHA) the problem I have is that one of them (ESTADO_MARCHA) from time to time receives voltage from the circuit who is connected and creates false positives.

    I have thought that only the output is activated when ESTADO_MARCHA is for example 1 second high, but I can not get it to work correctly.

    Could you help me?

    That´s is the part of my code that should do this:

    else if (ESTADO_LUZ == HIGH && ESTADO_MARCHA == HIGH)
    {
    // LED´s in white
    unsigned long tiempoTranscurrido=millis()-Tiempo;
    if(tiempoTranscurrido>=1000)
    {
    Rear_Gear_ON();
    BANDERA = 1;
    }
    else
    {
    Tiempo=millis();
    Turn_ON();
    }
    }

  3. Viorel Irimia Reply

    Very nice code and I loved the idea of using a structure for the button.
    Thank you!
    I’ve made a simplified version of it –
    void checkButton() {
    button.currentState = digitalRead(button.pin);
    if (button.currentState != button.prevState) {
    delay(button.debounce);
    button.currentState = digitalRead(button.pin);
    if (button.currentState == PRESSED) {
    button.counter = millis();
    } else {
    unsigned long currentMillis = millis();
    if (shortPress <= currentMillis – button.counter) {
    handleShortPress();
    }
    if (longPress <= currentMillis – button.counter) {
    handleLongPress();
    }
    }
    button.prevState = button.currentState;
    }
    }

  4. Hello and thanks for all the nice sketches here!
    This sketch for long-press-detection is working good.
    I need it for a LCD-Menu to get into the “Settings Mode”, and your sketch reacts only IF Button is Up,
    so i don’t know when the 1000ms is over.

    What must a change in the code to detect the 1000ms wihile i keep the button pressed?
    Thank you

      • >Take out the if-statement
        If i take those lines out with /*… */ the sketch does not react on any button press.

        Do you have another idee now i can jump into
        void handleLongPress(){}
        while i stay on the pressed button?

        • If i take those lines out with /*… */ the sketch does not react on any button press.

          Well of course, then you are commenting out the button checking. I didn’t say to comment out the block, I said the if-statement. You need to remove lines 51 and 63, not everything in between. You will also need to decide if you want to detect the short button press or not. If not, remove that. Lastly, you probably need to add a flag variable that stops checking the button press since it’ll keep firing on every loop iteration while the button is down.

  5. abdullah mahmoud Reply

    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.

  6. JE Staalduinen Reply

    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

    • James Lewis Reply

      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

      [arduino firstline=”28″]
      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].
      [/arduino]

  7. 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.

    • Ah good point. I need to change the example. You are correct. The pin (and debounce value) should be an undefined attribute that gets assigned after the object is created. Keeping them a constant is only valid for a single button.

Write A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.