Timer
About
The ESP32 SoCs contains from 2 to 4 hardware timers. They are all 64-bit (54-bit for ESP32-C3) generic timers based on 16-bit pre-scalers and 64-bit (54-bit for ESP32-C3) up / down counters which are capable of being auto-reloaded.
ESP32 SoC |
Number of timers |
|---|---|
ESP32 |
4 |
ESP32-S2 |
4 |
ESP32-S3 |
4 |
ESP32-C3 |
2 |
ESP32-C6 |
2 |
ESP32-H2 |
2 |
Arduino-ESP32 Timer API
timerBegin
This function is used to configure the timer. After successful setup the timer will automatically start.
hw_timer_t * timerBegin(uint32_t frequency);
frequencyselect timer frequency in Hz. Sets how quickly the timer counter is “ticking”.
This function will return timer structure if configuration is successful.
If NULL is returned, error occurs and the timer was not configured.
timerEnd
This function is used to end timer.
void timerEnd(hw_timer_t * timer);
timertimer struct.
timerStart
This function is used to start counter of the timer.
void timerStart(hw_timer_t * timer);
timertimer struct.
timerStop
This function is used to stop counter of the timer.
void timerStop(hw_timer_t * timer);
timertimer struct.
timerRestart
This function is used to restart counter of the timer.
void timerRestart(hw_timer_t * timer);
timertimer struct.
timerWrite
This function is used to set counter value of the timer.
void timerWrite(hw_timer_t * timer, uint64_t val);
timertimer struct.valcounter value to be set.
timerRead
This function is used to read counter value of the timer.
uint64_t timerRead(hw_timer_t * timer);
timertimer struct.
This function will return counter value of the timer.
timerReadMicros
This function is used to read counter value in microseconds of the timer.
uint64_t timerReadMicros(hw_timer_t * timer);
timertimer struct.
This function will return counter value of the timer in microseconds.
timerReadMillis
This function is used to read counter value in milliseconds of the timer.
uint64_t timerReadMillis(hw_timer_t * timer);
timertimer struct.
This function will return counter value of the timer in milliseconds.
timerReadSeconds
This function is used to read counter value in seconds of the timer.
double timerReadSeconds(hw_timer_t * timer);
timertimer struct.
This function will return counter value of the timer in seconds.
timerGetFrequency
This function is used to get resolution in Hz of the timer.
uint16_t timerGetFrequency(hw_timer_t * timer);
timertimer struct.
This function will return frequency in Hz of the timer.
timerAttachInterrupt
This function is used to attach interrupt to timer.
void timerAttachInterrupt(hw_timer_t * timer, void (*userFunc)(void));
timertimer struct.userFuncfunction to be called when interrupt is triggered.
The C++ overload accepts std::function<void(void)>, so capturing lambdas and bound member functions can be used as the callback. Include FunctionalInterrupt.h to use this overload. It is available only when the SoC has a general-purpose timer (SOC_GPTIMER_SUPPORTED).
#include <FunctionalInterrupt.h>
void timerAttachInterrupt(hw_timer_t * timer, std::function<void(void)> userFunc);
A function pointer matches the C overload. A capturing lambda matches the C++ overload. A non-capturing lambda can convert to both; capture a variable or wrap the lambda in std::function<void(void)> to select the C++ overload.
Timer callbacks run in ISR context. Keep the handler short. Use volatile for data shared with loop().
Do not declare a std::function object with IRAM_ATTR. That places a writable C++ object in instruction RAM. On RISC-V targets (ESP32-C6 and similar) the constructor then store-faults during do_global_ctors before setup() runs. For an IRAM-safe callback, use a C function pointer marked ARDUINO_ISR_ATTR. std::function::operator() and most lambda bodies stay in flash.
Re-attaching a callback replaces the previous one. timerDetachInterrupt and timerEnd release any heap-allocated std::function state.
#include <Arduino.h>
#include <FunctionalInterrupt.h>
hw_timer_t *timer = NULL;
volatile uint32_t counter = 0;
void setup() {
Serial.begin(115200);
timer = timerBegin(1000000);
timerAttachInterrupt(timer, [&]() {
counter = counter + 1;
});
timerAlarm(timer, 1000000, true, 0);
}
void loop() {
Serial.printf("Counter: %" PRIu32 "\n", counter);
delay(1000);
}
timerAttachInterruptArg
This function is used to attach interrupt to timer using arguments.
void timerAttachInterruptArg(hw_timer_t * timer, void (*userFunc)(void*), void * arg);
timertimer struct.userFuncfunction to be called when interrupt is triggered.argpointer to the interrupt arguments.
timerDetachInterrupt
This function is used to detach interrupt from timer.
void timerDetachInterrupt(hw_timer_t * timer);
timertimer struct.
timerAlarm
This function is used to configure alarm value and autoreload of the timer. Alarm is automatically enabled.
void timerAlarm(hw_timer_t * timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
timertimer struct.alarm_valuealarm value to generate event.autoreloadenabled/disabled autorealod.reload_countnumber of autoreloads (0 = unlimited). Has no effect if autorealod is disabled.
Example Applications
There are 3 example uses of Timer:
Repeat timer example:
/*
Repeat timer example
This example shows how to use hardware timer in ESP32. The timer calls onTimer
function every second. The timer can be stopped with button attached to PIN 0
(IO0).
This example code is in the public domain.
*/
#include <Arduino.h>
// Stop button is attached to PIN 0 (IO0)
#define BTN_STOP_ALARM 0
hw_timer_t *timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;
void ARDUINO_ISR_ATTR onTimer() {
// Increment the counter and set the time of ISR
portENTER_CRITICAL_ISR(&timerMux);
isrCounter = isrCounter + 1;
lastIsrAt = millis();
portEXIT_CRITICAL_ISR(&timerMux);
// Give a semaphore that we can check in the loop
xSemaphoreGiveFromISR(timerSemaphore, NULL);
// It is safe to use digitalRead/Write here if you want to toggle an output
}
void setup() {
Serial.begin(115200);
// Set BTN_STOP_ALARM to input mode
pinMode(BTN_STOP_ALARM, INPUT_PULLUP);
// Create semaphore to inform us when the timer has fired
timerSemaphore = xSemaphoreCreateBinary();
// Set timer frequency to 1Mhz
timer = timerBegin(1000000);
// Attach onTimer function to our timer.
timerAttachInterrupt(timer, &onTimer);
// Set alarm to call onTimer function every second (value in microseconds).
// Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
timerAlarm(timer, 1000000, true, 0);
}
void loop() {
// If Timer has fired
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
uint32_t isrCount = 0, isrTime = 0;
// Read the interrupt count and time
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
isrTime = lastIsrAt;
portEXIT_CRITICAL(&timerMux);
// Print it
Serial.print("onTimer no. ");
Serial.print(isrCount);
Serial.print(" at ");
Serial.print(isrTime);
Serial.println(" ms");
}
// If button is pressed
if (digitalRead(BTN_STOP_ALARM) == LOW) {
// If timer is still running
if (timer) {
// Stop and free timer
timerEnd(timer);
timer = NULL;
}
}
}
Watchdog timer example:
#include <Arduino.h>
#include "esp_system.h"
#include "rom/ets_sys.h"
const int button = 0; //gpio to use to trigger delay
const int wdtTimeout = 3000; //time in ms to trigger the watchdog
hw_timer_t *timer = NULL;
void ARDUINO_ISR_ATTR resetModule() {
ets_printf("reboot\n");
esp_restart();
}
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("running setup");
pinMode(button, INPUT_PULLUP); //init control pin
timer = timerBegin(1000000); //timer 1Mhz resolution
timerAttachInterrupt(timer, &resetModule); //attach callback
timerAlarm(timer, wdtTimeout * 1000, false, 0); //set time in us
}
void loop() {
Serial.println("running main loop");
timerWrite(timer, 0); //reset timer (feed watchdog)
long loopTime = millis();
//while button is pressed, delay up to 3 seconds to trigger the timer
while (!digitalRead(button)) {
Serial.println("button pressed");
delay(500);
}
delay(1000); //simulate work
loopTime = millis() - loopTime;
Serial.print("loop time is = ");
Serial.println(loopTime); //should be under 3000
}
Functional interrupt (std::function / lambda) example:
/*
SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0
Hardware Timer FunctionalInterrupt Example
==========================================
Shows the different ways to declare a hardware timer callback:
1. C function pointer - timerAttachInterrupt(timer, &fn)
2. Capturing lambda - needs FunctionalInterrupt.h
3. std::function object - stored in DRAM, not IRAM
4. std::bind to a member - class instance as the callback
5. timerAttachInterruptArg - C callback with a context pointer
The sketch re-attaches a different style every 5 alarms. Press BOOT to
stop and free the timer.
*/
#include <Arduino.h>
#include <FunctionalInterrupt.h>
hw_timer_t *timer = NULL;
volatile uint32_t isrCounter = 0;
enum DemoMode : uint8_t {
MODE_C_PTR = 0,
MODE_LAMBDA,
MODE_STD_FUNCTION,
MODE_BIND,
MODE_ARG,
MODE_COUNT
};
static const char *modeName[MODE_COUNT] = {
"C function pointer", "capturing lambda", "std::function object", "std::bind member", "timerAttachInterruptArg",
};
static uint8_t currentMode = MODE_C_PTR;
static void ARDUINO_ISR_ATTR bumpCounter() {
isrCounter = isrCounter + 1;
}
static void ARDUINO_ISR_ATTR bumpCounterArg(void *arg) {
volatile uint32_t *count = (volatile uint32_t *)arg;
*count = *count + 1;
}
class TickSource {
public:
void ARDUINO_ISR_ATTR isr() {
isrCounter = isrCounter + 1;
}
};
static TickSource tickSource;
// std::function is a writable C++ object. Do not mark it IRAM_ATTR: on RISC-V
// (ESP32-C6 and similar) that places it in instruction RAM and the constructor
// store-faults at boot.
std::function<void()> onTimerFn = []() {
isrCounter = isrCounter + 1;
};
static void attachMode(uint8_t mode) {
switch (mode) {
case MODE_C_PTR:
// Exact match for the C overload. No FunctionalInterrupt.h required.
timerAttachInterrupt(timer, &bumpCounter);
break;
case MODE_LAMBDA:
// Non-capturing lambdas can be ambiguous with the C overload; capture.
timerAttachInterrupt(timer, [&]() {
isrCounter = isrCounter + 1;
});
break;
case MODE_STD_FUNCTION: timerAttachInterrupt(timer, onTimerFn); break;
case MODE_BIND: timerAttachInterrupt(timer, std::bind(&TickSource::isr, &tickSource)); break;
case MODE_ARG:
// C API with a user pointer. Same idea as GPIO attachInterruptArg().
timerAttachInterruptArg(timer, bumpCounterArg, (void *)&isrCounter);
break;
default: break;
}
}
void setup() {
Serial.begin(115200);
delay(1000);
pinMode(BOOT_PIN, INPUT_PULLUP);
Serial.println("Hardware Timer FunctionalInterrupt Example");
Serial.println("Re-attaches a different callback style every 5 ticks.");
Serial.println("Press BOOT to stop the timer.");
timer = timerBegin(1000000);
if (timer == NULL) {
Serial.println("Failed to start timer");
return;
}
attachMode(currentMode);
Serial.printf("Using %s\n", modeName[currentMode]);
// Alarm every second, auto-reload, unlimited count
timerAlarm(timer, 1000000, true, 0);
}
void loop() {
static uint32_t lastCount = 0;
uint32_t count = isrCounter;
if (count != lastCount) {
lastCount = count;
Serial.printf("[%s] fired %" PRIu32 " times\n", modeName[currentMode], count);
if ((count % 5) == 0) {
currentMode = (currentMode + 1) % MODE_COUNT;
attachMode(currentMode);
Serial.printf("Switched to %s\n", modeName[currentMode]);
}
}
if (digitalRead(BOOT_PIN) == LOW) {
if (timer) {
timerEnd(timer);
timer = NULL;
Serial.println("Timer stopped");
}
}
delay(10);
}