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

 

Contents

 

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 https://www.programming-electronics-diy.xyz/2024/01/defining-fcpu.html.

Data type

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

/*************************************************************
	USER SETUP SECTION
**************************************************************/

/*
* 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
----------------------------------------------------------------*/
#define MILLIS_TIMING_MODULE	TIMING_USING_TCA

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_init(16000000);
    millis_t lastTime = 0;

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


Library Usage

Initialization


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.

f_cpu

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

resolution

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 https://arduino.stackexchange.com/questions/12587/how-can-i-handle-the-millis-rollover/12588#12588.

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.

Usage:

millis_interrupt_attach(btnReader);

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.

Download

millis v2.1

millis.h

millis.c
micros v2.0

micros.h

micros.c
Other links

spreadsheet calculator for LibreOffice Error and OCR calculator based on time resolution and CPU frequency
Changelog
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