Saturday, January 9, 2021

millis and micros library for AVR microcontrollers - milliseconds and microseconds time tracking

Having precise timing in microcontrollers is important in many projects. For this you can use the two libraries presented in this article - millis & micros.

millis library triggers a timer interrupt every 1 millisecond and increments the milliseconds variable. The user can select the size of the milliseconds variable ranging from char (8 bits) to long long (64 bits) with an overflow from 255 milliseconds to 584.9 million years.

micros library is almost the same as millis but for microseconds. The overflow is between 255 microseconds and 584942 years.

For both libraries the user can select which timer to use: Timer0, Timer1, Timer2, Timer3 or Timer4.

In the case of millis library, it is recommended that other interrupts take less than 1 millisecond to complete otherwise the millis timer interrupt will be delayed. For the micros the ISRs must finish in less than 100 microseconds. The faster the CPU clock the better.

For the milliseconds and microseconds variable decide if you really need a 32 or 64 variable (long and long long) because the bigger the variable the longer it takes to increment it. For example on a 1MHz CPU it takes about 77 microseconds to increment a long long variable.

millis & micros library for AVR microcontrollers




Library setup

By default the libraries are set to use Timer 2 and unsigned long variable type for time tracking but these can be changed inside the header files.

Classic AVR vs Modern AVR

Since version 2, the libraries supports modern AVR devices. I will be referring to them as UPDI devices since they can be programmed using UPDI interface.

F_CPU - the CPU clock frequency

When using UPDI devices the F_CPU can be a function argument so there is no need for a #define. However for non-UPDI devices like ATmega328PB the F_CPU needs to be defined in the header file so that the timer prescaler can be calculated depending on what timer you select.

F_CPU is best to be defined inside project properties in Microchip Studio (or your particular IDE) or a Makefile if custom Makefiles are used. See

Data type

Both millis and micros libraries have their own data types. The default works in most cases.


* Milliseconds data type
* Data type			- Max time span			- Memory used
* unsigned char			- 255 milliseconds		- 1 byte
* unsigned int			- 65.54 seconds			- 2 bytes
* unsigned long			- 49.71 days			- 4 bytes
* unsigned long long	        - 584.9 million years	        - 8 bytes
typedef unsigned long millis_t;

Selecting the Timer

When using non-UPDI devices the following timers can be selected:

Non-UPDI devices
    MILLIS_TIMER0 - 8-bits
    MILLIS_TIMER1 - 16-bits
    MILLIS_TIMER2 - 8-bits
    MILLIS_TIMER3 - 16-bits
    MILLIS_TIMER4 - 16-bits
#define MILLIS_TIMER 		MILLIS_TIMER2 // which timer to use

On UPDI devices the timer source can be RTC or TCA. 

RTC is the internal 16-bit Real Time Counter and is set to use the internal low power 32 kHz oscillator.

TCA is a 16-bit timer and is set to use channel 0.

UPDI devices
Select timing module.
    TIMING_USING_RTC - use internal RTC
    TIMING_USING_TCA - use timer TCA channel 0

Timer error compensation

A positive or negative number that will be added/subtracted from the compare register value. By setting a pin high then low inside the timer interrupt, a logic analyzer can be used to measure timing accuracy then this value can be adjusted accordingly.

Error calibration. A negative or positive number
that will be added or subtracted from timer register value.
#define MILLIS_ERR_CAL		0 // usually 1 or -1 for RTC

Code example

Non-blocking delay using millis library:

#include "millis.h"

int main(void){
    millis_t lastTime = 0;

	if((millis_t)(millis_get() - lastTime) > 5000){
	    lastTime = millis_get();
	    // Code runs every 5 seconds

Library Usage


void millis_init(uint32_t f_cpu)
void micros_init(uint32_t f_cpu, uint16_t resolution)

Configures the timer and enables global interrupts. For millis the resolution is set to 1 millisecond.


CPU clock frequency in Hertz. When RTC is used as a timer, the frequency is always 32768.


This sets the micros interrupt frequency in microseconds. For non-UPDI devices, the maximum value is 100 microseconds. For UPDI devices, when RTC is used, the minimum  value is 120us because the RTC clock is 32768 Hz with a resolution of 30.5us but this value is multiplied by 4 RTC clocks.

Get time

millis_t millis_get()
micros_t micros_get()

Returns the number of milliseconds / microseconds since the init function was executed or since the last overflow of the variable.

Dealing with millis or micros overflows

Depending on the millis_t data type (uint8_t, uint16_t, uint32_t or uint64_t) the global variable milliseconds or microseconds that holds the time, will eventually reach it's maximum value and then overflow back to 0.

Most of the time you need to use this functions like this:

uint16_t interval = 1000;
uint16_t lastTime = 0;

if(micros() - lastTime >= interval){
    lastTime = micros();
    blink_led(); // 1000ms interval (1s)

After the overflow the micros() will return smaller values than lastTime and the subtraction result will be negative and you don't want that. To solve this issue, simply cast the result to an unsigned integer like this:

uint16_t interval = 1000;
uint16_t lastTime = 0;

if((micros_t) micros() - lastTime >= interval){
    lastTime = micros();
    blink_led(); // 1000ms interval (1s)

For more details consider this link

Pause timer

void millis_pause()
void micros_pause()

Pause and turn off timer to save power.

Resume timer

void millis_resume()
void micros_resume()

Turn the timer back on.

Reset timer

void millis_reset()
void micros_reset()

Reset milliseconds / microseconds count to 0.

Add time

void millis_add(millis_t ms)
void micros_add(micros_t ms)

Add microseconds / milliseconds.

Subtract time

void millis_subtract(millis_t ms)
void micros_subtract(micros_t ms)

Subtract microseconds / milliseconds.

Attach interrupt function

void millis_interrupt_attach(void (*funcPtr)(void))

Attach a custom function to timer interrupt.



The function btnReader will be executed on each interrupt.

Error, OCRA and prescaler tables based on CPU frequency for 1 millisecond timer interrupt - Timer0, Timer1 and Timer2

Error, OCRA and prescaler tables based on CPU frequency and time resolution in microseconds timer interrupt - Timer0, Timer1 and Timer2

micros spreadsheet error and OCR calculator based on time resolutionmicros spreadsheet error and OCR calculator based on time resolution

Notice the error is 0% for most CPU frequencies however the time accuracy will also depend on how accurate the RC oscillator or crystal will be.


millis v2.1


micros v2.0


Other links

spreadsheet calculator for LibreOffice Error and OCR calculator based on time resolution and CPU frequency
v2.1 7, March, 2024:
- millis: a callback function was included in the timer interrupt that can be attached to a custom user function.
v2.0 - added support for modern AVR (UPDI devices). Tested on tinyAVR402.

No comments:

Post a Comment