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?

Your questions, comments, and even corrections are encouraged and very much appreciated! However. I have zero-tolerance for inappropriate or harassing comments. I try to reply to everyone... -James

Leave a comment

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

  1. Very nice, just what I was looking for.
    Needed a push button to initiate a timed output to reset a real time clock at midnight
    Thank you

  2. What if you wanted to change this code so that it starts the timer as soon as the button is pressed instead of released? In my case, a switch will turn on and stay on for the entire loop. I want the LED to turn on as soon as the switched is set HIGH.

      •  unsigned long currentMillis = millis();
          if(buttonState != lastButtonState); //compare to previous state
            if (buttonState == HIGH){
              buttonPushedMillis = currentMillis;
              co2ValveReady = true;
            }
           if(co2ValveReady){
             if((unsigned long)(currentMillis - buttonPushedMillis) >= turnOnDelay){
              digitalWrite(co2Valve, HIGH);
              co2ValveState = true;
              co2ValveOnAt = currentMillis;
              digitalWrite(co2Off, LOW);
              co2ValveReady = false;
            }
             lastButtonState = buttonState;
        
        

        This doesn’t seem to work correctly. Need it to turn the LED on for 3 seconds as soon as the button is pressed, and then off – although the button will remain pressed until reset.

        • I don’t have time to look in depth, but add an AND statement to your button check to ignore it if co2ValveReady is true. There’s no reason to check the button at that point (and you keep updating the buttonPushedMillis) in that case.

  3. I amend code to blink when you press button and at the end of setup time, but when ever you press button within this time led is blinking again and I want to switch it off this behavior.
    something like this:
    press the button –> LED (on and off)
    time is ticking
    press the button again –> LED stay off only millis() time is reset to begin
    time is off –> LED (on and off)