arduino Time Management


Syntax

  • unsigned long millis()

  • unsigned long micros()

  • void delay(unsigned long milliseconds)

  • void delayMicroseconds(unsigned long microseconds)

  • See the elapsedMillis header for constructors and operators of that class. In short:

    • elapsedMillis elapsedMillisObject; creates an object to keep track of time since it was created or since some other explicitly set point in time
    • elapsedMillisObject = 0; reset the time tracked by the object to "since now"
    • unsigned long deltaT = elapsedMillisObject; lets us look at the tracked time
    • elapsedMillisObject += and -= these work as expected

Remarks

Blocking vs. non-blocking code

For very simple sketches, writing blocking code using delay() and delayMicroseconds() can be appropriate. When things get more complex, using these functions can have some drawbacks. Some of these are:

  • Wasting CPU time: More complex sketches might need the CPU for something else while waiting for an LED blinking period to end.
  • unexpected delays: when delay() is called in subroutines that are not obviously called, for example in libraries you include.
  • missing events that happen during the delay and are not handled by an interrupt handler, for example polled button presses: A button might be pressed for 100 ms, but this might be shadowed by a delay(500).

Implementation details

millis() usually relies on a hardware timer that runs at a speed that's much higher than 1 kHz. When millis() is called, the implementation returns some value, but you don't know how old that actually is. It's possible that the "current" millisecond just started, or that it will end right after that function call. That means that, when calculating the difference between two results from millis(), you can be off by anything between almost zero and almost one millisecond. Use micros() if higher precision is needed.

Looking into the source code of elapsedMillis reveals that it indeed uses millis() internally to compare two points in time, so it suffers from this effect as well. Again, there's the alternative elapsedMicros for higher precision, from the same library.

blocking blinky with delay()

One of the most straight forward way of making an LED blink is: turn it on, wait a bit, turn it off, wait again, and repeat endlessly:

// set constants for blinking the built-in LED at 1 Hz
#define OUTPIN LED_BUILTIN
#define PERIOD 500

void setup()
{
  pinMode(OUTPIN, OUTPUT);      // sets the digital pin as output
}

void loop()
{
  digitalWrite(OUTPIN, HIGH);   // sets the pin on
  delayMicroseconds(PERIOD);        // pauses for 500 miliseconds      
  digitalWrite(OUTPIN, LOW);    // sets the pin off
  delayMicroseconds(PERIOD);        // pauses for 500 milliseconds

  // doing other time-consuming stuff here will skew the blinking
}

However, waiting as done in the example above wastes CPU cycles, because it just sits there in a loop waiting for a certain point in time to go past. That's what the non-blocking ways, using millis() or elapsedMillis, do better - in the sense that they don't burn as much of the hardware's capabilities.

Non-blocking blinky with the elapsedMillis library (and class)

The elapsedMillis library provides a class with the same name that keeps track of the time that passed since it was created or set to a certain value:

#include <elapsedMillis.h>

#define OUTPIN LED_BUILTIN
#define PERIOD 500

elapsedMillis ledTime;

bool ledState = false;

void setup() 
{                
  // initialize the digital pin as an output.
  pinMode(OUTPIN, OUTPUT);     
}

void loop()
{
    if (ledTime >= PERIOD) 
    {                
        ledState = !ledState;
        digitalWrite(OUTPIN, ledState);
        ledTime = 0;
    }
    // do other stuff here
}

You can see in the example that the ledTime object is assigned zero when the LED pin was toggled. This might not be surprising at first glance, but it has an effect if more time-consuming things are happening:

Consider a situation where the comparison between ledTime and PERIOD is done after 750 milliseconds. Then setting ledTime to zero means that all following toggle operations will be 250 ms "late". If, in contrast, PERIOD was subtracted from ledTime, the LED would see one short period and then continue blinking as if nothing happened.

Non-blocking blinky with millis()

This is very close to an example from the arduino docs:

// set constants for blinking the built-in LED at 1 Hz
#define OUTPIN LED_BUILTIN
#define PERIOD 500  // this is in milliseconds

int ledState = LOW;

// millis() returns an unsigned long so we'll use that to keep track of time
unsigned long lastTime = 0;

void setup() {
  // set the digital pin as output:
  pinMode(OUTPIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= PERIOD) // this will be true every PERIOD milliseconds
  {
    lastTime = now;
    if (ledState == LOW)
    {
      ledState = HIGH;
    }
    else
    {
      ledState = LOW;
    }
    digitalWrite(OUTPIN, ledState);
  }

  // now there's lots of time to do other stuff here
}

Using millis() in this way - to time operations in a non-blocking way - is something that is needed quite frequently, so consider using the elapsedMillis library for this.

Measure how long something took, using elapsedMillis and elapsedMicros

#include <elapsedMillis.h>

void setup() {
  Serial.begin(115200);
  elapsedMillis msTimer;
  elapsedMicros usTimer;

  long int dt = 500;
  delay(dt);

  long int us = usTimer;
  long int ms = msTimer;

  Serial.print("delay(");Serial.print(dt);Serial.println(") took");
  Serial.print(us);Serial.println(" us, or");
  Serial.print(ms);Serial.println(" ms");
}

void loop() {
}

In this example, an elapsedMillis object and an elapsedMicros object are used to measure how long something took, by creating them just before the expression we want to time is executed, and getting their values afterwards. They will show slightly different results, but the millisecond result won't be off by more than one millisecond.

More than 1 task without delay()

If you have more than 1 task to execute repeatedly in different intervals, use this example as a starting point:

unsigned long intervals[] = {250,2000}; //this defines the interval for each task in milliseconds
unsigned long last[] = {0,0};           //this records the last executed time for each task

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); //set the built-it led pin as output
  Serial.begin(115200);         //initialize serial
}

void loop() {
  unsigned long now = millis();
  if(now-last[0]>=intervals[0]){ last[0]=now; firstTask(); }
  if(now-last[1]>=intervals[1]){ last[1]=now; secondTask(); }
  
  //do other things here
}

void firstTask(){
  //let's toggle the built-in led
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN)?0:1);
}

void secondTask(){
  //say hello
  Serial.println("hello from secondTask()");
}

To add another task to execute every 15 seconds, extend the variables intervals and last:

unsigned long intervals[] = {250,2000,15000};
unsigned long last[] = {0,0,0};

Then add an if statement to execute the new task. In this example, I named it thirdTask.

if(now-last[2]>=intervals[2]){ last[2]=now; thirdTask(); }

Finally declare the function:

void thirdTask(){
  //your code here
}