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. Hello ,
    my name is Niko and i am from Greece.
    I am after millis implementation using fastled patterns and i have to admitt it is pain…..
    inserting inside a for loop….
    below is a demo for loop which cannot work with millis,
    what it does is waiting for the interval time i set and then passes all leds at very fast speed.
    the purpose i want is to use BLYNK platform which does not like delays…..
    any help will be much apreciated……

    #include "FastLED.h"
    #define NUM_LEDS 54
    #define DATA_PIN 6
    CRGB leds[NUM_LEDS];
    
    unsigned long interval = 100;
    unsigned long previousMillis = 0;
    
    
    void setup() {
      LEDS.addLeds(leds, NUM_LEDS);
      LEDS.setBrightness(255);
    }
    
    void fadeall() {
      for (int i = 0; i = interval) {
        previousMillis = millis();
    
        for (int i = 0; i &lt; NUM_LEDS; i++) {
          leds[i] = CHSV(hue++, 255, 255);
          FastLED.show();
          fadeall();
        }
      }
    }
    

    Thanks in advance

    • this is the right code

      #include "FastLED.h"
      #define NUM_LEDS 54
      #define DATA_PIN 6
      CRGB leds[NUM_LEDS];
      
      unsigned long interval = 100;
      unsigned long previousMillis = 0;
      
      
      void setup() {
        LEDS.addLeds(leds, NUM_LEDS);
        LEDS.setBrightness(255);
      }
      
      void fadeall() {
        for (int i = 0; i = interval) {
          previousMillis = millis();
          for (int i = 0; i &lt; NUM_LEDS; i++) {
            leds[i] = CHSV(hue++, 255, 255);
            FastLED.show();
            fadeall();
          }
        }
      }
      
      • void loop() {
          static uint8_t hue = 0;
          if ((unsigned long)(millis() - previousMillis) &gt;= interval) {
            previousMillis = millis();
            for (int i = 0; i &lt; NUM_LEDS; i++) {
              leds[i] = CHSV(hue++, 255, 255);
              FastLED.show();
              fadeall();
            }
          }
        }
        

        this is the loop..i hope it pastes ok…..

  2. Hi James!
    When you are starting with millis magic, each time you repeate: “if ((UNSIGNED LONG)(currentMillis – previousMillisLED13) >= intervalLED13)”
    currentMillis is already set as UNSIGNED LONG, so why you repeat it all the time? Is it necessery?

    Why not to write just like this: “if ((currentMillis – previousMillisLED13) >= intervalLED13)” ?

    I wrote quite long but very simple code (as I am total begginer) and I am using 8 timers which work with millis() using this method and I am still afraid about rollover. I have read your tutorial about this problem but I do not get it for 100% :/
    Any chance you can take a look for this code and say if rollover will not mess upt the timers?

    Thanks, Michal

    • When you are starting with millis magic, each time you repeate: “if ((UNSIGNED LONG)(currentMillis – previousMillisLED13) >= intervalLED13)”
      currentMillis is already set as UNSIGNED LONG, so why you repeat it all the time? Is it necessery?

      I have had conflicting research that says it is necessary. So I leave it in, just in case.

  3. This was an absolute great article . Thanks to you, i could build my multi-tasking project , and stop using the delay() function . Thank you for this shared knowledge !!

  4. Hi great tutorial, I have situation where I have set of Neopixels light a different colour when something happens (IFTTT trigger it calls a colorwipe function). I want them to stay this colour for say 60-90 seconds. Delay will work but not ideal,where would I use millis() for this situation. It can’t be used in the loop as the strip stay the same colour and only change for certain events. Thanks

  5. James, I cannot get ex 5 to run! It compiles and uploads, but nothing is happening on my Uno. I tried through Codebender’s interface and the Arduino IDE (1.0.6). What gives? The board is working fine.

  6. Hi,

    First of all, thanks for the tutorial, I’m sure many people finds it helpful!
    I might miss the magic, but I think #5 example is wrong, it won’t survive the millis() rollover.
    I have to admit, I didn’t wait for ~50 days to be sure, but if you’re using this
    if(millis()>=waitUntil)
    style, it will fire every time after waitUntil wraps around, and waitUntil will climb extremely fast (by flashDelay each time). After that either waitUntil wraps around again, or millis() does, and it won’t activate again (freeze) until it reaches waitUntil’s highly inflated value, which might be a few days or weeks.
    If you agree with me, please correct that example, as it’s a millis() tutorial after all. If I’m wrong, please explain why.

  7. Hi James i have been having some issues with delay() function. Your tutorial was very useful thanks. Am working on a traffic light with sensor . The sensing part is where am got stuck. I have this code that i want to rewrite without using delay() function
    Basically, i wanted a delay before the LDR (photocell) sensor sense a car. if the delay is 10sec , and a car did not spend up to 10sec nothing should happen but if = 10sec turn pin 5 HIGH. your help will be highly appreciated .
    1 to write the code without using delay () function
    or
    2 10secs timer without using delay () function
    thanks here is my code

    int sensorPin = A0;            // select the input pin for the ldr
    unsigned int sensorValue = 0;  // variable to store the value coming from the ldr
    int LDRLED  = 5;            // select the input pin for the ldr
    
    void setup()
    {
      pinMode(LDRLED, OUTPUT);
      //Start Serial port
      Serial.begin(9600);        // start serial for output - for testing
    }
    
    void loop()
    {
      // read the value from the ldr:
      sensorValue = analogRead(sensorPin);     
      if(sensorValue<300)
      {
        delay (3000);
        sensorValue = analogRead(sensorPin);     
        if(sensorValue<300)
          digitalWrite(LDRLED, HIGH);       
    
      }
      else 
      {
        digitalWrite(LDRLED, LOW);   // set the LED off
    
      }
      //For DEBUGGING - Print out our data, uncomment the lines below
      Serial.print(sensorValue, DEC);     // print the value (0 to 1024)
      Serial.println("");                   // print carriage return  
      delay(500);  
    }

    thanks .

  8. cant you help me how to me after 20 sec forward and reverse motor will stop automaticly

    unsigned long interval = 100; // the time we need to wait
    unsigned long previousMillis = 120;
    int E1 = 5;
    int M1 = 4;
    int Led1 = 9;
    int Led2 = 10;
    int Led3 = 11;
    int x;
    int i;
    
    String readString;
    #include
    //#include
    //LiquidCrystal_I2C lcd(0x20,16,2);
    
    char command = ”;
    int pwm_speed; //from 0 – 255
    
    void setup() {
      pinMode(Led1, OUTPUT);
      pinMode(Led2, OUTPUT);
      pinMode(Led3, OUTPUT);
      pinMode(M1, OUTPUT);
      Serial.begin(9600);
    
      // lcd.init(); // initialize the lcd
      // lcd.backlight();
      // lcd.home();
    }
    
    void loop()
    {
      forward();
      reverse();
      Stop();
    }
    
    void forward() {
    
      char *help_menu = “\’e\’ go forward\n\’d\’ stop\n\’s\’ left\n\’f\’ right\n\’c\’ go backward\n”;
    
      if (Serial.available() > 0)
      {
        command = Serial.read();
      }
    
      switch (command)
      {
        case 'e': //go forward
          Serial.println(“Go!!\r\n”);
          digitalWrite(M1, LOW);
          if ((unsigned long)(millis() – previousMillis) >= interval) {
            previousMillis = millis();
          }
          digitalWrite(Led1, HIGH);
          digitalWrite(Led2, LOW);
          digitalWrite(Led3, LOW);
          // lcd.setCursor(0, 0);
          // lcd.print(“forward”);
          analogWrite(E1, 255); //PWM speed control
          command = ”; //reset command
          break;
    
      }
    }
    
    void reverse() {
      char *help_menu = “\’e\’ go forward\n\’d\’ stop\n\’s\’ left\n\’f\’ right\n\’c\’ go backward\n”;
    
      if (Serial.available() > 0)
      {
        command = Serial.read();
      }
    
      switch (command)
      {
        case 'c': //backward
          Serial.println(“Back!!\r\n”);
          digitalWrite(M1, HIGH);
          digitalWrite(Led1, LOW);
          digitalWrite(Led2, HIGH);
          digitalWrite(Led3, LOW);
          // lcd.setCursor(0, 0);
          // lcd.print(“backward”);
          analogWrite(E1, 255); //PWM speed control
          command = ”; //reset command
          break;
      }
    }
    
    void Stop() {
      char *help_menu = “\’e\’ go forward\n\’d\’ stop\n\’s\’ left\n\’f\’ right\n\’c\’ go backward\n”;
    
      if (Serial.available() > 0)
      {
        command = Serial.read();
      }
    
      switch (command)
      {
        case 'd': //stop
          Serial.println(“Stop!\r\n”);
          analogWrite(E1, 0); //PWM speed control
          digitalWrite(Led1, LOW);
          digitalWrite(Led2, LOW);
          digitalWrite(Led3, HIGH);
          // lcd.setCursor(0, 0);
          // lcd.print(“stop “);
          command = ”; //reset command
          break;
      }
    }
    
    • Untested, but I’d do something like this. Basically, save the current time in previousMillis(). In loop, constantly check if you ever had a 20 second wait since the previous command. If so, issue stop, anyway.

      Also, I tried to clean up your code. You should only need to check for a serial command once, then run the function related to that command.

      unsigned long interval = 20000UL; // 20,000 ms = 20s
      unsigned long previousMillis = 0;
      int E1 = 5;
      int M1 = 4;
      int Led1 = 9;
      int Led2 = 10;
      int Led3 = 11;
      int x;
      int i;
      
      String readString;
      //#include
      //LiquidCrystal_I2C lcd(0x20,16,2);
      
      char command = '\0';
      int pwm_speed; //from 0 – 255
      
      void setup() {
        pinMode(Led1, OUTPUT);
        pinMode(Led2, OUTPUT);
        pinMode(Led3, OUTPUT);
        pinMode(M1, OUTPUT);
        Serial.begin(9600);
      
        // lcd.init(); // initialize the lcd
        // lcd.backlight();
        // lcd.home();
      }
      
      void loop()
      {
        char *help_menu = "\’e\’ go forward\n\’d\’ stop\n\’s\’ left\n\’f\’ right\n\’c\’ go backward\n";
      
        if (Serial.available() > 0)
        {
          command = Serial.read();
        
          switch (command)
          {
            case 'e': //go forward
            forward();
            break;
      
            case 'c':
            reverse();
            break;
      
            case 'd':
            Stop();
            break;
          }
          command = '\0'; //reset command
        }
        if ((unsigned long)(millis() - previousMillis) >= interval) {
          Stop();
        }
      }
      
      void forward() {
        Serial.println("Go!!\r\n");
        digitalWrite(M1, LOW);
        digitalWrite(Led1, HIGH);
        digitalWrite(Led2, LOW);
        digitalWrite(Led3, LOW);
        // lcd.setCursor(0, 0);
        // lcd.print("forward");
        analogWrite(E1, 255); //PWM speed control
        previousMillis = millis();
      }
      
      void reverse() {
        Serial.println("Back!!\r\n");
        digitalWrite(M1, HIGH);
        digitalWrite(Led1, LOW);
        digitalWrite(Led2, HIGH);
        digitalWrite(Led3, LOW);
        // lcd.setCursor(0, 0);
        // lcd.print("backward");
        analogWrite(E1, 255); //PWM speed control
        previousMillis = millis();
      }
      
      void Stop() {
        analogWrite(E1, 0); //PWM speed control
        digitalWrite(Led1, LOW);
        digitalWrite(Led2, LOW);
        digitalWrite(Led3, HIGH);
        Serial.println("Stop!\r\n"); // usually you want to stop immediately
        // lcd.setCursor(0, 0);
        // lcd.print("stop ");
      }
      
  9. Hello James.

    I read all of your examples and I got a few questions.

    I want to make codes which control LED.
    I intended to turn on/off the LED depending on the distance.(I use ultrasonic sensor to check distance)

    For example
    if d is between 10cm and 20cm, LED will be on. (d=distance)
    And if d is between 10cm and 20cm for 5 seconds, LED will be off though it is in ‘ON-STATE’ distance.

    My question is how I can do it by using ‘Millis’.
    plz help, especially ‘And if ‘ line .

  10. Hello dear Sir,

    First of all compliments for the tutorial.

    But i am still wondering of with some quiestions, I am working on a simple RGB light. And for now i just want the primair colors to go on one after another.

    With your code for 2 led lights i did manage to get it to work. But now i am trying to add the 3th led, and now i get stuck. Somehow my code doesnt work.

    i am totally new in programming, and i was always using the delay function, i really want to get that away from my programming.

    long sequenceDelay2 = 2000;
    long sequenceDelay = 1000;
    long flashDelay = 1000;
    long flashDelay2 = 2000;
    
    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
    boolean LED11state = false;
    
    long waitUntil13 = 0;
    long waitUntil12 = sequenceDelay; // the seed will determine time between LEDs
    long waitUntil11 = sequenceDelay + sequenceDelay2; // the seed will determine time between LEDs
    
    void setup() {
      pinMode(13, OUTPUT);
      pinMode(12, OUTPUT);
      pinMode(11, OUTPUT);
    }
    void loop() {
      digitalWrite(13, LED13state); // each iteration of loop() will set the IO pins,
      digitalWrite(12, LED12state);
      digitalWrite(11, LED11state);
      // checking to see if enough time has elapsed
      if (millis() >= waitUntil13) {
        LED13state = !(LED13state);
        waitUntil13 += flashDelay;
        // this if-statement will not execute for another 1000 milliseconds
      }
      // keep in mind, waitUntil12 was already seeded with a value of 500
      if (millis() >= waitUntil12) {
        LED12state = !(LED12state);
        waitUntil12 += flashDelay;
      }
      //keep in mind, waitUntil12 was already seeded with a value of 500
      if (millis() >= waitUntil11) {
        LED11state = !(LED11state);
        waitUntil11 += flashDelay2;
      }
    }
    
    • I’m uncertain what you mean “doesn’t work”. The code works exactly as written.

      Pin 13 will flash on and off once per second
      Pin 12 will flash on and off once per second, starting 1 second after Pin 13
      pin 11 will flash on and off once every 2 seconds, starting 3 second after Pin 13

      You probably need to add independent on and off times. For example, if your first LED is on for 1 second you need to keep it off for the next 2 seconds. That will give time for the 2nd LED to turn on for 1 second and then led 3 to turn on for 1 second.

      So the code works fine. Your logic doesn’t is wrong.