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.
Contents
- Library Setup
- Code Example
- Library Usage
- Initialization
- Get time
- Pause timer
- Resume timer
- Reset timer
- Add time
- Subtract time
- Attach interrupt function
- Spreadsheet calculator
- Download
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
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