Use Arduino millis() with buttons to delay events

Create delayed actions without using delay()

One of the common questions related to using the millis() function in Arduino, is around timed events. After an event occurs, you want the code to wait for some time before doing the next step. But you don’t want to stop the program with delay().

Delayed Actions with Millis

In this example, we will use millis() to wait a few seconds after a pushbutton press to turn on an LED. Then a few seconds later, we will turn it off.  All without using delay().

Understanding millis()

The Arduino millis() function will let you accomplish this delayed action relatively easily. First, read through my multitasking with millis() tutorial and then look at some of my millis() cookbook examples I’ve already posted. Getting used to seeing this kind of code will make this line by line tutorial on timed events using millis() easier to follow.

The Full Code

(FYI, you can grab the complete code from pastebin if copy/paste doesn’t work here.)

//Global Variables
const byte BUTTON=2; // our button pin
const byte LED=13; // LED (built-in on Uno)

unsigned long buttonPushedMillis; // when button was released
unsigned long ledTurnedOnAt; // when led was turned on
unsigned long turnOnDelay = 2500; // wait to turn on LED
unsigned long turnOffDelay = 5000; // turn off LED after this time
bool ledReady = false; // flag for when button is let go
bool ledState = false; // for LED is on or not.

void setup() {
 pinMode(BUTTON, INPUT_PULLUP);
 pinMode(LED, OUTPUT);
 digitalWrite(LED, LOW);
}

void loop() {
 // get the time at the start of this loop()
 unsigned long currentMillis = millis(); 

 // check the button
 if (digitalRead(BUTTON) == LOW) {
  // update the time when button was pushed
  buttonPushedMillis = currentMillis;
  ledReady = true;
 }
 
 // make sure this code isn't checked until after button has been let go
 if (ledReady) {
   //this is typical millis code here:
   if ((unsigned long)(currentMillis - buttonPushedMillis) >= turnOnDelay) {
     // okay, enough time has passed since the button was let go.
     digitalWrite(LED, HIGH);
     // setup our next "state"
     ledState = true;
     // save when the LED turned on
     ledTurnedOnAt = currentMillis;
     // wait for next button press
     ledReady = false;
   }
 }
 
 // see if we are watching for the time to turn off LED
 if (ledState) {
   // okay, led on, check for now long
   if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay) {
     ledState = false;
     digitalWrite(LED, LOW);
   }
 }
}

Global Variables

const byte BUTTON=2; // our button pin
const byte LED=13; // LED (built-in on Uno)

Setup a constant for BUTTON and LED. (Read this post on why I am using const instead of #define.)

unsigned long buttonPushedMillis; // when button was released
unsigned long ledTurnedOnAt; // when led was turned on

These two variables will store the “current” value of millis() when their “event” occurs. Instead of trying to reset millis(), we will compare against itself later on.

unsigned long turnOnDelay = 2500; // wait to turn on LED
unsigned long turnOffDelay = 5000; // turn off LED after this time

These two values are arbitrary for this example. It’s how long we wait until we turn on the LED and then how long we wait to turn off the LED.

 

bool ledReady = false; // flag for when button is let go
bool ledState = false; // for LED is on or not.

Two flag variables to create the states for the LED. For this simple example, I didn’t create an enum to track the states, but that could have been an option too.

ledReady tells the Arduino to wait the amount of time in “turnOnDelay” before turning on the LED. Then it enables the next state, which is waiting to turn off the LED.

 

void setup()

void setup() {
 pinMode(BUTTON, INPUT_PULLUP);
 pinMode(LED, OUTPUT);
 digitalWrite(LED, LOW);
}

Nothing unique here. In my circuit, I am using the built-in pull-up resistor on my button pin. Just so I know what state the LED pin is in, I set it to LOW.

void loop()

void loop() {
 // get the time at the start of this loop()
 unsigned long currentMillis = millis(); 

Remember that loop() runs around and around. So each time loop() starts over, I store the current “time.” Back on the “blink without delay line by line” post, I used the analogy of looking at a watch. That’s what we are doing here, getting the current time.

Note: This is where beginners get tripped up. They get focused on the XY Problem of Resetting millis(), it isn’t necessary. Just check to see what time it is!

Check the button

Arduino Pushbutton with Pull-Up Resistor

  if (digitalRead(BUTTON) == LOW) {

Since the button uses a pull-up resistor, a LOW means the button is being pressed.

Keep in mind, the Arduino checks this single if-statement a THOUSAND times while a human being pushes the button.

Store the time

      buttonPushedMillis = currentMillis;

As long as the button is down we are updating buttonPushedMillis with the value in currentMillis. Another option would have been to determine when the button was released, but that would have made the code more complicated.

Even if the button bounces a little bit, this code works fine.

     ledReady = true;

This flag enables the next “state” which is to wait long enough to turn on the LED.

Turn on the led?

   if (ledReady) {

It might seem silly to set ledReady to TRUE and then immediately check to see if it is TRUE. None of this code “stops and waits.” We use this “flag” to let the Arduino know when we are ready to turn on the LED.

   if ((unsigned long)(currentMillis - buttonPushedMillis) >= turnOnDelay) {

Standard millis() check. We compare the current value of millis(), stored in currentMillis, to the value in buttonPushedMillis.

Keep in mind, buttonPushedMillis will keep getting updated as long as something is pressing the button down. So until you let go of the button, (currentMillis – buttonPushedMillis) is going to be zero (0).

The gap between currentMillis and buttonPushedMillis will grow after the button is released. Once it gets to 2500 or more, the if-statement becomes TRUE.

       digitalWrite(LED, HIGH);

Turn on our LED. Hopefully this one is obvious. 🙂

      ledState = true;

Get into the next state, which is waiting long enough to turn off the LED.

      ledTurnedOnAt = currentMillis;

Just like the button push, we store the time when the LED turned on. Again, we just look at the clock so we can compare how long the LED has been on.

Note: The variable you use needs to be an unsigned long

      ledReady = false;

If we don’t set this state to FALSE, then ledTurnedOnAt will keep getting updated, and the LED would never turn off.

When to turn Off LED

   if (ledState) {

Here’s our final state. We’re waiting to turn off the LED

if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay) {

The only similarity between this line and line 32 is that we are still comparing against currentMillis. LedTurnedOnAt and turnOffDelay are specific to this state.

       ledState = false;
       digitalWrite(LED, LOW);

Clear the flag that enabled this state. And turn off the LED.

Conclusion

That’s it. This code delays turning on the LED after you release the button, and then the LED stays on for a given time. No delay()s, so you could use the code to do other tasks.

Exercise for the reader

Look what happens if you press the button again while the LED is on. How long does it stay on? Why? Is there a way to “reset” the on-time each time the button is pressed?

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

ALL comments submitted with fake or throw-away services are deleted, regardless of content.

Don't be a dweeb.

Leave a comment

42 thoughts on “Use Arduino millis() with buttons to delay events

  1. Hello Jim
    I am very new to Arduino, I have a need for your millis() with buttons and delay events sketch. But I need to add a couple more buttons and relays. Is there anyway you show me how?

    • Hello
      Generally speaking, if you got the one button version woorking, adding extra buttons is simply a case of of adding extra instances.
      Say you have now Button, Interval, LastMillis (your names might be different), you add extra ones by defining, say Button1, Interval1, LastMillis!; Button2, Interval2, LastMillis2 and so on. The millis() funtion remains the same for all.
      Then you add extra digitalls for button inputs and extra outputs for your relays, like Button1 =4, Button2=5; Relay1=6, Relay2=7. Don’t forget pinModes in setup for each new digital. You might need pullup resistors for your buttons (external 10K) or use the internal ones with digitalWrite(Button1, HIGH). Don’t forget to put diodes across your relay coils

  2. How can I add a second or third push button and led with different times in the above sketch (Use Arduino millis() with buttons to delay events)?

  3. Hello James,
    I sent you a message that this code was good for my spot welder.
    The code controls the relay for a time of 200ms. Press once and the relay starts 200 ms with a delay of 5000ms. When I now connect the relay to 220V and I press once, the relay goes 3 to 4 times and sometimes 2 times in a row and then stops.
    what can I do any idea.

    • Exact circuit and code essential for helpful suggestions. A spot welder is going to be chucking out a lot of RF and other disturbances and your circuit is going to have to be well protected. Sensitive digital inputs aren’t going to like the stuff a welder sends out. I presume you have the basic protection, diode and possibly transistor on your outputs?

    • Tough to say. Could be an EMI problem. Make sure you are switching the “hot” and not the “neutral” connection. (Not sure if that is what they’re called in your region.) You might also consider adding an opts-isolator between the Arduino and relay, if the relay module doesn’t have one already.

  4. Hello sir,
    I want to thank you for the arduino code.
    I use this for controlling my spot welder via a relay.
    It works very well.
    Thanks again

  5. Hi sir.. The 18 void loop() {
    19 // get the time at the start of this loop()
    20 unsigned long currentMillis = millis();
    command must be placed only one time in the begging off the loop, or every time when we have two or more buttons in our project ?? . How must be our code when we have two buttons or more , to light two or more leds. ?? Thanks in advance and thank you very mach for your lessons ..

  6. Hello James. I`m struggling to figure how to modify your code in order to make the program to to wait a few seconds after a pushbutton press to turn on an LED. Then a few seconds later, the LED should turn off but while the button is pressed. After the button is released it will make the “same” loop but with other “delays”.
    I started from your code using flags and state machine but I still don`t figure out, what I`m missing.
    Here is my scketch (sorry that I`m using your code instead made a new one) :

    Thank you !

    Code :

    //Global Variables
    const byte BUTTON= 2; // button pin
    const byte LED= 7; // LED (built-in on Uno)
    
     
    unsigned long buttonPushedMillis; // when button was pushed
    unsigned long ledTurnedOnAt; // when led was turned on
    unsigned long turnOnDelay = 500; // wait to turn on LED
    unsigned long turnOffDelay = 3000; // turn off LED after this time
    
    unsigned long buttonReleasedMillis; // when button was released
    unsigned long ledTurnedOnAtRel; // when led was turned on
    unsigned long turnOnDelayRel = 1000; // wait to turn on LED
    unsigned long turnOffDelayRel = 4000; // turn off LED after this time
    
    
    
    bool ledReady = false; // flag for when button is let go
    bool ledState = false; // for LED is on or not.
    int buttonDown = 0;
    int buttonState =0;
    int lastButtonState =0;
    
    
     
    void setup() {
     pinMode(BUTTON, INPUT);
     pinMode(LED, OUTPUT);
     digitalWrite(LED, LOW);
     
     
    }
     
    void loop() {
     // get the time at the start of this loop()
     unsigned long currentMillis = millis(); 
    
     buttonState = digitalRead(BUTTON);
     // check the button
     if (buttonState != lastButtonState) {
     if (buttonState == HIGH && buttonDown == 0) {
      // update the time when button was pushed
      buttonDown = 1;
      if (buttonDown == 1) {
      buttonPushedMillis = millis();
      
      ledReady = true;
      }
     
     }
                
             
     
     // make sure this code isn't checked until after button has been let go
     if (ledReady) {
       //this is typical millis code here:
       if ((unsigned long)(currentMillis - buttonPushedMillis) >= turnOnDelay) {
         // okay, enough time has passed since the button was let go.
         digitalWrite(LED, HIGH);
         // setup our next "state"
         ledState = true;
         // save when the LED turned on
         ledTurnedOnAt = currentMillis;
         // wait for next button press
         ledReady = false;
         
        
       
       }
     }
      
     // see if we are watching for the time to turn off LED
     if (ledState) {
       // okay, led on, check for now long
       if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay) {
         ledState = false;
         digitalWrite(LED, LOW);
      
       
       }
     }
    
    //================================================
    
    else {
     if (buttonState == LOW && buttonDown == 1) {
      buttonDown = 0;
       if (buttonDown == 0) {
      buttonReleasedMillis = millis();
      ledReady = true;
       }
    }
    }
    
     if (ledReady) {
       
       if ((unsigned long)(currentMillis - buttonReleasedMillis) >= turnOffDelayRel) {
        
         digitalWrite(LED, HIGH);
        
         ledState = true;
        
        ledTurnedOnAtRel = currentMillis;
       
         ledReady = false;
        
       
       }
     }
      
    
     if (ledState) {
      
       if ((unsigned long)(currentMillis - ledTurnedOnAtRel) >= turnOffDelayRel) {
         ledState = false;
         digitalWrite(LED, LOW);
       
        
       }
     }
    
    
    lastButtonState = buttonState;
     }
    }
    
    • I don’t follow your explanation or the logic in your code. But I will try to offer some suggestions.

      First, you need to be using an external pull-down resistor. Otherwise, your button’s behavior isn’t predictible.

      Second, you should not be checking “leddReady” or “ledState” twice. You have not created a state machine, you have created a mess.

      Third, maybe it is how the comment got formatted. But you would do yourself a HUGE favor to hit “ctrl-t” in the IDE. It will format your code so that it is readable.

  7. Hello sir James.I am beginner with arduino.I would like to ask you how can we turn off the led not only with the expire of time (delay of 5 sec) but also with the same push button before the expire of 5 sec.I want to have 2 choices.Either by pushing the button so the led turns on and waits for 5 sec in order to turn off or by pushing again the button before the expire of 5 sec,so it will turn off immediately .

    • Sorry, but I won’t write your projects logic for you. Use the examples to solve the two states you want by themselvds: count up and count down. Then the push buttons can set flags that use that logic.

  8. Good morning. It’s a very nice analysis . But what if the Button in the beginning is pressed on and then depressed. Can you show me the change of routine into the loop ? Thank you …

  9. i want a timer which turn on the relay for some time (2min) when button is pressed then off but if i press the same button before 2 minute then the timer of 2 minute again starts counting from start without turn off the output relay…

    • That’s just a basic millis() timer. Each time the button is pressed, reset your wait time. Something like:

      void loop() {
        // react when the button is pressed. if using the internal pull-up, PRESSED should be #define as LOW
        // store the current value of millis, which "resets" the timer.
        if (digitalRead(button) == PRESSED) 
           buttonPushedMillis = millis();
      
        // see if "interval" amount of time has passed. set a global constant for 2000 ms for interval
        if (currentMillis - buttonPushedMillis) > interval) 
           digitalWrite(relayPin) = LOW;
      }