Skip to content

uPesy ships directly only in France !

uPesy products available on Amazon for EU

Contents Menu Expand Light mode Dark mode Auto light/dark mode

How do microcontroller timers work?

(Updated at 02/02/2023)

You have probably heard of a timer, but do you really know what it is used for and how it works? In this article, we will discover the different use cases of a timer on a microcontroller as well as its theoretical operation. This knowledge will allow you to use timers in your Arduino, and MicroPython programs effectively. Let’s start by defining what a timer is. 😊

The Timer, an electronic counter

A timer is an electronic counter capable of counting time very accurately. It is a register inside a microcontroller that is incremented each time it receives a pulse from a clock signal generated internally by the microcontroller. We will use timers instead of the microprocessor to count, which is what the Arduino function delay() does.

The hardware timers are independent blocks of the CPU. They take care of counting continuously while the program can perform other functions.

Note

The Arduino functions millis() and micros() functions use a timer to work.

Timers provide additional functionality, such as triggering alarms when a certain threshold is reached. For example, tasks can be executed periodically with interrupts generated by a timer.

What is the purpose of having a timer on a microcontroller? - Use cases

The timer can be used for its basic function: to measure time accurately. It may be interesting to know how long a program has been running or how much time has elapsed since the program started.

In practice, we often use a unique feature of timers: generating interrupts when the counter reaches a certain threshold. Here is what you can do using this feature:

  • Scheduled tasks: a timer can be configured to trigger an interruption at regular intervals, which can be used to update displays, monitor sensor inputs, etc.

  • Generate PWM signals: a timer can be used to generate PWM signals to control the speed of a motor, the brightness of an LED, etc.

  • Event synchronization: a timer can synchronize events internally or with external devices.

  • Create timeouts: maximum time to wait

Note

Timers are used in Arduino functions without necessarily being aware of it, in basic functions (millis() and micros() ), for PWM and in libraries (Servo )

I will tell you more about the inner workings of a timer. It’s important to understand how it works under the hood to configure it correctly later in your programs. 😊

The internal workings of a timer

Timers cannot count indefinitely. They are limited by their resolution, which is measured in bits. For example, the Arduino has 2 timers of 8 bits and 1 of 16 bits. This means that 2 timers can count between 0 and 255 and the other between 0 and 65 535. On an ESP32 board, there are 4 timers of 64bits, which can count between 0 and 18,446,744,073,709,552,000 (there’s a lot to do 😉.)

If the timer exceeds its maximum value, it will overflow. In this case, the counter is usually reset to zero by the <span style=’color:gray’>``autoreload`` </span><span style=’color:gray’>,</span> and an internal alarm is generated to warn the microprocessor.

Operation of a timer in a microcontroller

The time that elapses between each “tick” of the timer obviously depends on the frequency of the clock. On each rising edge of the clock, the counter is incremented. At frequencies of the order of tens of MHz, the time is very short and the counter overflows very quickly.

Fortunately, it is possible to reduce this frequency thanks to internal components of the microcontroller, called dividers or prescalers in English. A**prescaler** allows the frequency of the clock to be divided and incremented more slowly. For example, with a prescaler of 4, the counter will count 4 times slower than the clock signal.

Chronogram of a timer counting up to 5

Note

A timer is configured with its period via the value of the prescaler and the autoreload .

Calculate the value of the prescaler and the autoreload from the desired timer period

The timer period is the time that elapses between 2 counter overflows. To calculate it, we can use the following formula:

\[T_{timer}=T_{clock}\times prescaler \times (autoreload+1)\]

Note

The counter counts from 0 to autoreload inclusive, i.e \((autoreload+1)\) values.

In general, we know the period of the timer we want: we look for the values of the parameters. There are several pairs of possible solutions for a given period. You can have a low prescaler (high count rate) with a high overflow or vice versa. We cannot find the 2 values separately. You have to choose one to find the other.

\[autoreload = \frac{T_{timer}}{T_{clock}\times prescaler}-1\]

Let’s take an 80 MHz clock, and we want a timer period of 1 second. By choosing a prescaler of 1, the overflow value is :

\[autoreload = \frac{T_{timer}}{T_{clock}\times prescaler}-1 = \frac{1}{\frac{1}{80.10^{6}}}-1 = 80.10^{6}-1\]

The autoreload value must be storable in the timer register, i.e., have a value less than \(2^{résolution\space timers}\) bits. This is ok for the ESP32 but not for the Arduino timers. It would be necessary to use a more significant value for the prescaler.

Note

Sometimes it is not possible to find the exact values of the parameters. It will therefore be necessary to make approximations. We can minimize this effect by using the smallest possible prescaler.

If we want to get a value on 16bits for an Arduino timer, we can use a prescaler of 8000 and an autoreload of 9999:

\[autoreload = \frac{T_{timer}}{T_{clock}\times prescaler}-1 = \frac{1}{\frac{8000}{80.10^{6}}}-1 = 10000-1 = 9999\]

Of course, other prescaler values are possible! The prescaler value is usually chosen arbitrarily, as large as possible, and then the threshold value is derived.

In practice, we use a value that simplifies the calculations: for example, a prescaler of 80 for the ESP32 permanent to configure the period of the timer to the nearest microsecond (the frequency of the clock is 80 MHz).

\[autoreload = \frac{T_{timer}}{T_{clock}\times prescaler}-1 = \frac{T_{timer}}{\frac{80}{80.10^{6}}}-1 = T_{timer}\times10^{6}-1\]

Simple, right? 😊

\[autoreload = T_{timer}\times10^{6}-1\]

Note that the ESP32 hides the subtraction in the function code, so we can simplify by prescaling to 80:

\[autoreload = T_{timer}\times10^{6}\]