millis() Tutorial: Arduino Multitasking

After working through these exercises, check out this article on how to avoid rollover or reset mills().

After learning how to flash a single LED on your Arduino, you are probably looking for a way to make cool patterns, but feel limited by the use of delay(). If you ask in the forums, you get told to look at the “Blink Without Delay” example. This example introduces the idea of replacing delay() with a state machine. If you’re confused how to use it, this tutorial is setup to take you from blinking two LEDs with delay, to using an alternate method, right down to how you can use millis().

The millis() function is one of the most powerful functions of the Arduino library. This function returns the number of milliseconds the current sketch has been running since the last reset. At first, you might be thinking, well that’s not every useful! But consider how you tell time during the day. Effectively, you look at how many minutes have elapsed since midnight. That’s the idea behind millis()!

Instead of “waiting a certain amount of time” like you do with delay(), you can use millis() to ask “how much time has passed”? Let’s start by looking at a couple of ways you can use delay() to flash LEDs.

Example #1: Basic Delay

You are probably already familiar with this first code example The Arduino IDE includes this example as “Blink.”

void setup() {
   pinMode(13, OUTPUT);
}

void loop() {
   digitalWrite(13, HIGH);   // set the LED on
   delay(1000);              // wait for a second
   digitalWrite(13, LOW);    // set the LED off
   delay(1000);              // wait for a second
}

Reading each line of loop() in sequence the sketch:

  1. Turns on Pin 13’s LED,
  2. Waits 1 second (or 1000milliseconds),
  3. Turns off Pin 13’s LED and,
  4. Waits 1 second.
  5. Then the entire sequence repeats.

The potential issue is that while you are sitting at the delay(), your code can’t be doing anything else. So let’s look at an example where you aren’t “blocking” for that entire 1000 milliseconds.

Example #2: Basic Delay with for() loops

For our 2nd example, we are only going to delay for 1ms, but do so inside of a for() loop.

void setup() {
   pinMode(13, OUTPUT);
}

void loop() {
   digitalWrite(13, HIGH);   // set the LED on
   for (int x=0; x < 1000; x++) {     // Wait for 1 second
      delay(1);
   }
   digitalWrite(13, LOW);   // set the LED on
   for (int x=0; x < 1000; x++) {     // Wait for 1 second
      delay(1);
   }
}

This new sketch will accomplish the same sequence as Example #1. The difference is that the Arduino is only “delayed” for one millisecond at a time. A clever trick would be to call other functions inside of that for() loop. However, your timing will be off because those instructions will add additional delay.

Example #3: for() loops with 2 LEDs

In this example, we’ve added a second LED on Pin 12 (with a current limiting resistor!). Now let’s see how we could write the code from Example #2 to flash the 2nd LED.

void setup() {
   pinMode(13, OUTPUT);
   pinMode(12, OUTPUT);
}

void loop() {
   digitalWrite(13, HIGH);   // set the LED on
   for (int x=0; x < 1000; x++) {             // wait for a secoond
      delay(1);
      if (x==500) {
         digitalWrite(12, HIGH);
      }
   }
   digitalWrite(13, LOW);    // set the LED off
   for (int x=0; x < 1000; x++) {             // wait for a secoond
      delay(1);
      if (x==500) {
            digitalWrite(12, LOW);
      }
   }
}

Starting with the first for() loop, while Pin 13 is high, Pin 12 will turn on after  500 times through the for() loop.  It would appear that Pin 13 and Pin 12 were flashing in Sequence.  Pin 12 would turn on 1/2 second after Pin 13 turns on.  1/2 second later Pin 13 turns off, followed by Pin 12 another 1/2 second.

This code is pretty complicated, isn’t it?

If you wanted to add other LEDs or change the sequence, you have to start getting ingenious with all of the if-statements. Just like example #2, the timing of these LEDs is going to be off. The if() statement and digitalWrite() function all take time, adding to the “delay()”.

Now let’s look at how millis() gets around this problem.

The millis() Function

Going back to the definition of the millis() function: it counts the number of milliseconds the sketch has been running.

Step back and think about that for a second. In Example #3, we are trying to flash LEDs based on a certain amount of time. Every 500ms we want one of the LEDs to do something different. So what if we write the code to see how much time as passed, instead of, waiting for time to pass?

Example #4: Blink with millis()

This code is the same “Blink” example from #1 re-written to make use of millis(). A slightly more complicated design, because you have to include a couple of more variables. One to know how long to wait, and one to know the state of LED on Pin 13 needs to be.

unsigned long interval=1000; // the time we need to wait
unsigned long previousMillis=0; // millis() returns an unsigned long.

bool ledState = false; // state variable for the LED

void setup() {
 pinMode(13, OUTPUT);
 digitalWrite(13, ledState);
}

void loop() {
 unsigned long currentMillis = millis(); // grab current time

 // check if "interval" time has passed (1000 milliseconds)
 if ((unsigned long)(currentMillis - previousMillis) >= interval) {
 
   ledState = !ledState; // "toggles" the state
   digitalWrite(13, ledState); // sets the LED based on ledState
   // save the "current" time
   previousMillis = millis();
 }
}

Let’s look at the code from the very beginning.

unsigned long interval=1000;     // the time we need to wait
unsigned long previousMillis=0;  // millis() returns an unsigned long.

millis() returns an unsigned long.

When using variables associated with millis() or micros(), ALWAYS declare them as an unsigned long

The variable interval is the amount of time we are going to wait. The variable previousMillis is used so we can see how long it has been since something happened.

bool ledState = false; // state variable for the LED

This code uses a variable for the state of the LED. Instead of directly writing a “HIGH” or “LOW” with digitalWrite(), we will write the value of this variable.

The setup() function is pretty standard, so let’s skip directly to the loop():

void loop() {
   unsigned long currentMillis = millis(); // grab current time

Each time loop() repeats, the first thing we do is grab the current value of millis(). Instead of repeated calls to millis(), we will use this time like a timestamp.

void loop() {
   if ((unsigned long)(currentMillis - previousMillis) >= interval) {

Holy cow does that look complicated! It really isn’t, so don’t be afraid to just “copy and paste” this one. First, it is an if()-statement. Second the (unsigned long)  isn’t necessary (but I’m not going to sit around for 49 days to make sure). Third “(currentMillis – previousMillis) >= interval)” is the magic.

What it boils down to, this code will only become TRUE after millis() is at least “interval” larger than the previously stored value of millis, in previousMillis. In other words, nothing in the if()-statement will execute until millis() gets 1000 milliseconds larger than previousMillis.

The reason we use this subtraction is that it will handle the roll-over of millis. You don’t need to do anything else.

  ledState = !ledState;

This line of code will set the value of ledState to the corresponding value. If it is true, it becomes false. If it is false, it becomes true.

digitalWrite(13, ledState); // sets the LED based on ledState

Instead of writing a HIGH or LOW directly, we are using a state variable for the LED. You’ll understand why in the next example.

previousMillis = millis();

The last thing you do inside of the if-statement is to set previousMillis to the current time stamp. This value allows the if-statement to track when at least interval (or 1000ms) has passed.

Example #5: Adding a 2nd LED with millis()

Adding a second flashing LED is straightforward with the millis() code. Duplicate the if-statement with a second waitUntil and LEDstate variable.

// each "event" (LED) gets their own tracking variable
unsigned long previousMillisLED12=0;
unsigned long previousMillisLED13=0;

// different intervals for each LED
int intervalLED12 = 500;
int intervalLED13 = 1000;

// each LED gets a state varaible
boolean LED13state = false;     // the LED will turn ON in the first iteration of loop()
boolean LED12state = false;     // need to seed the light to be OFF

void setup() {
   pinMode(13, OUTPUT);
   pinMode(12, OUTPUT);
}
void loop() {
   // get current time stamp
   // only need one for both if-statements
   unsigned long currentMillis = millis();

   // time to toggle LED on Pin 12?
   if ((unsigned long)(currentMillis - previousMillisLED12) >= intervalLED12) {
      LED12state = !LED12state;
      digitalWrite(12, LED12state);
      // save current time to pin 12's previousMillis
      previousMillisLED12 = currentMillis;
   }

// time to toggle LED on Pin 13?
  if ((unsigned long)(currentMillis - previousMillisLED13) >= intervalLED13) {
      LED13state = !LED13state;
      digitalWrite(13, LED13state);
      // save current time to pin 13's previousMillis
      previousMillisLED13 = currentMillis;
  }
}

This code has some small changes from the previous example. There are separate previousMillis and state variables for both pins 12 and 13.

To get an idea of what’s going on with this code change the interval values for each. Both LEDs will blink independently of each other.

Other Examples

Check out this list of millis() examples: https://www.baldengineer.com/millis-cookbook.html
There are examples to create a Police Light Strobe Effect, Time your Code, Change the ON-time and OFF-time for an LED, and others.

Conclusion

At first it seems that using the millis() function will make a sketch more complicated than using delay(). In some ways, this is true. However, the trade-off is that the sketch becomes significantly more flexible. Animations become much simpler. Talking to LCDs while reading buttons becomes no problem. Virtually, you can make your Arduino multitask.

After working through these exercises, check out this article on how to avoid rollover or reset mills().

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

122 thoughts on “millis() Tutorial: Arduino Multitasking

  1. hi, nice tutorial there,

    a question:

    supposedly i have this two function, one is way faster one is slower, will reading the (unsigned long)(currentMillis – previousMillisLED12-intervalLED12) signify how late that execution is late?

  2. Thank you for the guide. How do i use millis for longer periods, for example 60 minutes? My example is sending data to thingspeak every hour?

    • The biggest mistake people make is something like this:

      unsigned long interval = 1000 * 60 * 60; // 1000 ms times 60 seconds times 60 minutes = 1 hour
      

      This method is a problem because the compiler does all the math before storing it into the variable. The values “1000” and “60” are both small enough to fit into an integer, so it treats the entire math problem as an integer. The 1000 times 60 might be okay, but when multiplied again the integer will overflow. So you have to cast to a larger variable like this:

      unsigned long interval = 1000UL * 60UL * 60UL;
      

      The “UL” tells the compiler to treat the integers as unsigned long integers, so the math will be treated as an unsigned long the entire time. (Really you only need to cast one of the constants, but I added it all for this example.)

      unsigned long interval = 3600000;
      

      Just using the amount of milliseconds as a constant would work too. Personally, I find that hard to read sometimes.

  3. Hi,

    Thanks for your detailed tutorial.
    I want to sound a buzzer for 10sec when the state of a input pin changes. I have written following code using millis() function. There are no compilation errors. But when I am changing the pin state buzzer is not giving any sound. Can you please have a look at the code and what is wrong?

    CODE:

    int b;
    const int a = 8;
    int c;
    unsigned long previousMillis = 0;
    const long interval = 1000;
    void setup() {
    // put your setup code here, to run once:
    pinMode(a, INPUT);
    pinMode(13, OUTPUT);
    Serial.begin(9600);

    }

    void loop() {

    unsigned long currentMillis = millis();

    Serial.println(“previousMillis”);
    Serial.println(previousMillis);
    Serial.println(“currentMillis”);
    Serial.println(currentMillis);
    if(currentMillis – previousMillis >= interval) {
    b = digitalRead(a);
    Serial.println(“b”);
    Serial.println(b);
    previousMillis = currentMillis;
    c = digitalRead(a);
    if(b != c) {
    digitalWrite(13, HIGH);
    delay(10000);
    }
    else {
    digitalWrite(13, LOW);
    }
    Serial.println(“c”);
    Serial.println(c);
    }
    delay(1000);
    }

    Thanking you,
    Yoga

    • Make sure the buzzer is the type that “self excites.” Which means a DC signal will make a sound. Otherwise, you need to do something like a PWM waveform. The delay()s in your code makes using millis() totally pointless.

      • Hi James!
        This is to just to give something back, since your page helped me. The following code works and I post it as an example for your readers, I did it as a prop, to help my daughter in a school presentation.

        The code lights up a series of 8 LEDs connected (via BUFFERED outputs) to digital pins 2 through 9 of my Arduino Nano. Once all the 8 LEDs are lit up, a further button press will switch off all the LEDs and start again.

        The 8 LEDs light up in sequence and each time a button (connected to digital input 13) is pressed — I actually us an RF relay switch, but it may as well be a button — and extra LED lights up.

        A long button press will switch off the all the LEDs and start the sequence again (in case my daughter is halfway through, makes a mistake and needs to start again). This is where I need the millis function.

        Note: I put in delays of 50ms here and there to avoid some transient noise from being interpreted as multiple presses.

        CODE:

        const int ButtonPin = 13;
        const unsigned long longPressInterval=1000;

        int i;
        unsigned int ActiveLEDs=0;
        int buttonState = LOW;
        int previousState = LOW;
        unsigned long previousMillis;

        void setup() {
        // Initialize the output pins. One per LED:
        for (i=2; i= 8) {
        //All the LEDs are on, so we can’t light up any more. Instead we switch them all off.
        for (i=2; i longPressInterval) {
        // Long Press detected. Reset the LEDs.
        for (i=2; i<10; i++) digitalWrite(i, LOW);
        ActiveLEDs=0;
        // Let's wait until the button is released before going any further.
        while ((buttonState=digitalRead(ButtonPin)) == HIGH) delay(50);
        }

        previousState=buttonState;
        delay(50);
        }

        • Something gobbled up part of the code, above.
          Here it is again.
          const int ButtonPin = 13;
          const unsigned long longPressInterval=1000;

          int i;
          unsigned int ActiveLEDs=0;
          int buttonState = LOW;
          int previousState = LOW;
          unsigned long previousMillis;

          void setup() {
          // Initialize the output pins. One per LED:
          for (i=2; i= 8) {
          //All the LEDs are on, so we can’t light up any more. Instead we switch them all off.
          for (i=2; i longPressInterval) {
          // Long Press detected. Reset the LEDs.
          for (i=2; i<10; i++) digitalWrite(i, LOW);
          ActiveLEDs=0;
          // Let's wait until the button is released before going any further.
          while ((buttonState=digitalRead(ButtonPin)) == HIGH) delay(50);
          }

          previousState=buttonState;
          delay(50);
          }

  4. Hi,

    Will you be able to post an example of how I would use the millis function with an IR remote. What I am trying to do is make an LED blink when a remote button is pressed and also stop when the same button is pressed.

      • I am unable to use the MillisTimer library with IRremote. When I combine the two, the MillisTimer apparently locks (the LCD indicates the timer quits advancing). If I rem out the receiver.enableIRIn() line, the timer advances as advertised. What might the conflict be and is there a resolution?

        I am using pin 7 for the receiver pin for the IR sensor and pins 2-5 and 11-12 for the LCD display if that makes any difference.

        I have been a VBA developer for nearly 20 years so my general programming acumen is not particularly lacking, but I’m a newb in the Arduino/C/C++ world.

        • I have never used the MillisTimer library. Or the IR library really. So I don’t know how they might be conflicting. The IR library may be modifying the hardware timers directly, which may cause the other one issues. I have no idea.

          None of the “millis libraries” really solved a problem for me. I found just doing my own if-millis check is sufficient and straightforward.

  5. Hello. Thank you for the helpful articles. Would you mind if you give me the reference of millis() project.

  6. i am working with a Spo2 probe sensor and I am supposed to alternate the switching on of two LEDs(red and ir).How should i go about it using the millis() function?

  7. Hi James. thanx for very informative posts. I have 2 questions.I have an array of 12 Leds and would like to sequence them from the centre outwards, to the right and to the left at the same time. the second problem is how do you sequence like 3leds together at the same time in a row. …………
    Thanks in advance

  8. Hey James!

    I am trying to use a potentiometer to turn on an LED on, 5 seconds after reaching high travel(1000). However the program counts down from 5 seconds from reset/upload and then turns the LED on instead of waiting for the pot to reach 1000 and then count down from 5 and then turn the LED on.

    Heres part of the code(Void setup is fine):
    ***********************************************************************************************************************************

    const byte BluePin = 3;
    const byte GreenPin = 4;
    const byte LevelPin = A0; //potentiometer pin
    int waterLevel;

    unsigned long savedStartTime=0;
    unsigned long timeToPass=5000;
    unsigned long start;

    void loop() {
    waterLevel = analogRead(LevelPin); //Read the voltage from the Potentiometer pin

    if (waterLevel >= 1000) {
    start = millis();
    savedStartTime=start;
    digitalWrite(BluePin, HIGH);

    if((millis() – savedStartTime) >= timeToPass ){

    digitalWrite(GreenPin, HIGH);
    }
    **************************************************************************************************************************************
    When the pot is at 1000 the first LED(BluePin) comes on but the countdown doesn’t start there and then. GreenPin which I want to come on 5 seconds after reaching 1000 with the pot starts 5 seconds from reset/upload to arduino regardless Am I missing some code?.

    If your interested in providing a little advice this question is also here:

    https://forum.arduino.cc/index.php?topic=451429.0

    • You are constantly “resetting” start. Each iteration of loop, the “if waterLevel” is checked and found to be true. So start never really changes much. I would add a flag variable that i set false in that if statement. Then i would do “if waterLevel > 1000 and flag==true” so you only “set” start once.

      The millis() if should be outside of that check, on its own.

  9. const int outputpin = 4;
    int pin4State = HIGH;
    unsigned long previousMillis = 0;
    unsigned long interval = 10000;
    void setup() {
      pinMode(2, INPUT_PULLUP);
      pinMode(3, INPUT_PULLUP);
      pinMode(13, OUTPUT);
      pinMode(4, OUTPUT);
    
    }
    
    void loop() {
      if (digitalRead(2) == LOW) {
        digitalWrite(13, HIGH);
      }
      else {
        digitalWrite(13, LOW);
      }
      digitalWrite(4, HIGH);
      unsigned long currentMillis = millis();
      if ((pin4State == HIGH) && (digitalRead(3) == HIGH) && (unsigned long)(currentMillis - previousMillis >= 10000)) {
        pin4State = LOW;
        digitalWrite(4, LOW);
      }
      else {
        digitalWrite(4, HIGH);
      }
    
    }
    

    Sir in above code I am controlling output pin 13 on input pin 2. I want to start time when pin 13 & pin 4 goes high. If time interval is greater than 10 sec & if pin 3 remains high then I am expecting pin 4 should go low. With above code I am not getting expected result. What is reason. What mistake I am making. I am using arduino uno .
    Regards,
    N.N.Joglekar
    Nasik , Maharashtra State, India.

    • You never “reset” previousMillis, so this code will only work once. It should be updated inside of the if-statement. This code will turn the LED back on on the next cycle of loop when the if-statement is false. So you won’t actually see the LED on pin 4 ever turn off.

      You need a better state machine, with more states. You might also want to look at this delayed event millis example.

  10. Gone through it, few understand , few not. actually i want to learn how to make function and call it when ever required.
    Background- I connect 20 nos. LED with Arduino UNO and able to address it individually. also able to write different pattern, but to repeat the pattern I need to write the same program lines again. I know this is immature programing,
    Now I want to write this patterns as different functions so that I call them as required
    let say I write 5 typed of pattern for LED, now I call them -lets say 1st pattern 2 times 2nd pattern 4 times 3 pattern 3 times etc and when last pattern executed again first pattern will start and cycle will be repeated.
    Now please help me how I write LED patterns as functions and how to call them for n time repetition and finally complete cycle will be repeated again and again.