Arduino millis() vs micros()

When using the Arduino library, you have two simple ways of getting the current time since the Arduino board started: millis() and micros().

Those are very useful functions that you need in almost all your programs. For example, they will allow you to write multitasks programs very easily, and thus avoid using the delay() function.

Here’s an example of a typical program using millis()/micros():

unsigned long previousTime = micros(); // or millis()
long timeInterval = 800;

void setup() { }

void loop() {
  unsigned long currentTime = micros(); // or millis()

  // Enter the If block only if at least 800 micros (or millis) has passed since last time
  if (currentTime - previousTime > timeInterval) {
    // do action
    previousTime = currentTime;
  }
}

You might wonder what are the common points and differences between millis() and micros(). Apart from the obvious time difference, is there anything you should know? The answer is yes (and that’s why this tutorial exists).

millis() vs micros(): let’s begin!

Time resolution

You get a 1 millisecond resolution for the millis() function. Sounds obvious, right?

So, for the micros() function, you get a resolution of… one microsecond ? Wrong!

The resolution for micros() is 4 microseconds on all 16MHz Arduino boards: Uno, Mega, Nano, etc. For example, if you read the time with micros() and get 10000, then the next value you get is 10004, and after that 10008, and so on. You won’t be able to go down to multiples of 1, 2 or 3 microseconds.

But that’s not really a problem. 4 microseconds is already a good resolution, especially when considering that you’re using an Arduino, not a microcontroller designed for sending rockets in space.

So, in one second, you’ll get 1,000 values with millis(), and you’ll get 250,000 values with micros(), which corresponds to 1,000,000/4.

Note that the precision of the current time for millis() and micros() is the same. You should always get the same time given by the 2 functions, just with a different resolution.

millis() vs micros() overflow

What’s that?

The millis() and micros() both store the current time since the Arduino started, in an unsigned long variable.

On 16 bits Arduino (Uno, Mega, etc), an unsigned long is stored on 4 bytes, or 32 bits. Values for the variable ranges from 0 to 4,294,967,295 (2^32 – 1).

It’s important to know that. Why? Because when you keep incrementing a variable, it will eventually reach the maximum possible value. After this value, the variable comes back to 0. That’s what we call overflowing.

Practically speaking:

With millis(), it will take about 49.7 days to overflow. How to compute that:

(Max value for unsigned long) divided by (number of milliseconds in one second) divided by (number of seconds in one hour) divided by (number of hours in one day).

2^32 / 1000 / 3600 / 24 = 49.7 days.

For micros(), we’ll use:

(Max value for unsigned long) divided by (number of microseconds in one second) divided by (number of seconds in one minute).

2^32 / 1000000 / 60 = 71.6 minutes.

What does that mean? After 71.6 minutes, the value you get from micros() will overflow (reset to zero), and keep incrementing until it reaches the max value again, and again and again.

That’s just a little bit more than one hour. A micros() overflow is thus very likely to happen when you run your programs, and you should be aware of that.

For millis(), it will take more than 49 days, but you still can’t ignore that. Your Arduino board can potentially run forever (let’s say forever = 15 years before the hardware is being damaged). If you plan to use an Arduino for a connected home application, your board will be powered on for a long time, and the millis() is also likely to overflow.

How to solve the overflow issue

So, it seems your program could go wrong in a 100 different ways when the variable for millis() or micros() overflows.

But… There is a simple solution to that.

First, let’s assume that you’ll use millis() and micros() to compare durations. You’ll compute the difference between the current time and the last time you did an action, which gives you a duration. If the duration is higher than a given threshold, you execute the action again. Using millis() and micros() will be mostly only for this use case. You can’t use them to get the real current time anyway (remember, they monitor the time since the Arduino started, not since the epoch time).

When using millis() and micros() for a comparison like in the program below, things will work even if the time value overflows.

// previousTime was declared before in the program (as an unsigned long)
unsigned long currentTime = millis(); // or micros()
if (currentTime - previousTime > timeInterval) {
  // execute action
  previousTime = currentTime;
}

It’s pretty simple: you compare the current time with the last time you stored the value. If it’s higher than a certain threshold, you execute an action and write the current time into the last time variable.

Now imagine that the millis()/micros() value overflows. The next time you compare the current time with the previous time, you’ll get a huge positive difference, which will be higher than the threshold. You’ll then update the last time with the current time (which will be just above zero), and that’s it. Problem solved. In fact, there wasn’t even a problem anyway!

millis() and micros() inside interrupts

If you’re using interrupts in your program, then you need to be aware of this.

Let’s say you have attached an interrupt on a digital input pin. This interrupt is triggered whenever someone is pushing a button connected to the pin.

Inside an interrupt, all time related functionalities don’t work. You can still use millis() and micros() to get the time, but if you want to wait a certain duration by comparing 2 different times, it won’t work. Plus, if you stay too long inside an interrupt, you might miss important micros interrupts and your micros() will not be on time anymore!

Anyway, keep that in mind: when using interrupts, keep the code very short and quick. Only modify some variables that you’ll process later in the main Arduino thread. Never use delay(), and try to never use millis() and micros() as well.

I won’t get too technical in this post about the timers related to the millis() and micros() functions. But if you want, you can read the source code for those functions directly on GitHub, as the Arduino project is completely open source.

When to use Arduino millis() vs micros()

First of all, the functionality is the same: both millis() and micros() are keeping the time since the Arduino program started.

If your program requires executing actions with a resolution higher than one millisecond, then use micros(). If not, just use millis().

The drawback you get when using micros() is that the time variable overflows much quicker than the millis() variable (about 71 minutes instead of 49 days). It might be a problem, unless you only use the time functionalities to compare previous and current time, like we did above in this post.

All in all, the 2 things to keep in mind are the time resolution, and the duration before an overflow. Knowing that, you should have no more problem when writing an Arduino program using time functionalities!

Leave a Comment