Using ESP32 timers in Arduino code
(Updated at 02/02/2023)
In this article, we will explore the operation of a timer on the ESP32 using Arduino code. We’ll look at how to set up and use a timer effectively, using practical examples to help you understand the basic concepts. We will discover the steps to configure a timer on the ESP32 with the critical parameters for optimal operation. Here we go. 😊
How do ESP32 timers work?
This article will not cover the timer’s theoretical workings to keep it manageable. If you are a beginner and need to learn how a timer works, read the theoretical article on how it works . It will allow you to understand better how to choose the parameters’ values to use in your Arduino code.
Configuring and using an ESP32 timer with Arduino code
Here is the minimal skeleton code to use a timer on the ESP32 with Arduino code. It allows you to trigger an interrupt when the timer reaches a threshold
value, commonly called autoreload
.
hw_timer_t * timer = NULL;
void IRAM_ATTR timer_isr() {
// This code will be executed every 1000 ticks, 1ms
}
void setup() {
Serial.begin(115200);
uint8_t timer_id = 0;
uint16_t prescaler = 80; // Between 0 and 65 535
int threshold = 1000000; // 64 bits value (limited to int size of 32bits)
timer = timerBegin(timer_id, prescaler, true);
timerAttachInterrupt(timer, &timer_isr, true);
timerAlarmWrite(timer, threshold, true);
timerAlarmEnable(timer);
}
void loop() {
}
Timer selection and basic configuration
We define an object hw_timer_t
object outside the setup()
function to be able to access it from different functions. The function timerBegin(uint8_t id, uint16_t prescaler, bool countUp)
allows to configure the timer :
The ESP32 has 4 independent timers, selected by an id between 0 and 3. Then we select the prescaler
to apply to the timer clock signal. On the ESP32, this is the APB_CLK
clock, clocked at 80 MHz.
Warning
If you use external libraries in your code, they may use timers. In this case, you must be careful not to choose one they already use; otherwise, your program will undoubtedly have bugs!
In most examples available, a prescaler
of 80
is used to obtain a counting period of 1 µs. From a period of the timer in microseconds, we can directly know the value of the corresponding autoreload without any complicated calculations:
The argument countUp
argument specifies the direction we want to count: true
in ascending order, false
in descending order.
Configuring the alarm and triggering an interrupt routine
We attach an interrupt to the timer, which is triggered every time the threshold value is exceeded with timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge)
.
The central argument must be the name of the interrupt routine to be executed (here, timer_isr()
). You can also choose whether the interrupt is to be triggered on the rising or fall in select: we generally prefer the increasing edge for timers.
The threshold value of the counter is defined by timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload)
. We activate the mode autoreload
mode by setting autoreload
to true
. As soon as the timer has exceeded the value alarm_value
value, it starts counting again from zero, and the interrupt function is triggered.
Note
With a prescaler
of 80, the value of alarm_value
corresponds directly to the global period of the timer in microseconds.
Once the timer is fully configured, you can enable it with its alarm using timerAlarmEnable(timer)
.
If we run this code, the function timer_isr()
will be executed every second. Nothing will happen because the function is empty in this “skeleton” code. I suggest a version that flashes the blue LED of the ESP32 on pin GPIO2
.
Blink code with only one timer
Here is the famous “blink” rewritten using only a hardware timer for flashing the blue LED every second:
#define PIN_LED 2
hw_timer_t * timer = NULL;
volatile uint8_t led_state = 0;
void IRAM_ATTR timer_isr(){
led_state = ! led_state;
digitalWrite(PIN_LED, led_state);
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED, OUTPUT);
uint8_t timer_id = 0;
uint16_t prescaler = 80;
int threashold = 1000000;
timer = timerBegin(timer_id, prescaler, true);
timerAttachInterrupt(timer, &timer_isr, true);
timerAlarmWrite(timer, threashold, true);
timerAlarmEnable(timer);
}
void loop() {
// put your main code here to run repeatedly
}
We use the keyword volatile
for the variable led_state
to force the compiler not to optimize it. In fact, for the compiler, the function timer_isr()
is never called in the code because it is called via an external interrupt (which the compiler does not know about).
Without the volatile
attribute, the compiler could remove the unused variable to free up memory. Using the volatile
attribute keyword, we force the compiler to ignore this variable for optimization.
To make the LED blink, we use a little trick here to invert the state of the LED with the operator``!`` . When used on a variable; it replaces all zeros in its binary representation with 1s (and vice versa).
So the value of led_state will be inverted at each entry in the interrupt routine: 0b0000000
→ 0b11111111
→ 0b0000000
… As the digitalWrite()
function only looks at the value of the first bit, we will have an alternation between 0 and 1.
Note
We add the IRAM_ATTR
attribute to the function declaration to tell the compiler to load all the interrupt routine code into the ESP32’s internal RAM, making it run faster.
With this program, the blu LED starts blinking without the function, which can be used to do other tasks. With this program, the blue LED starts blinking without the loop()
function; different can be used for other tasks.
Increment a variable and generate flags
The interrupt routine must be executed as quickly as possible to avoid disturbing the main program. So we often use flags
that change state (usually a boolean: true
or false
) in the isr
. These changes are then processed in the main loop:
hw_timer_t * timer = NULL;
volatile boolean tick_flags = false;
void IRAM_ATTR timer_isr() {
tick_flags = true;
}
void setup() {
Serial.begin(115200);
uint8_t timer_id = 0;
uint16_t prescaler = 80;
int
This section is available to premium members only. You still have 76% to discover.
Subscribe for only 5$/monthAlready subscribed? Sign in