Chapter 172: System Timer (SYSTIMER) Usage
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the architecture and purpose of the SYSTIMER peripheral.
- Utilize the
esp_timer
API for high-precision timekeeping. - Obtain the current system time with microsecond resolution using
esp_timer_get_time()
. - Implement high-precision interval measurements.
- Create and manage one-shot and periodic software timers using the
esp_timer
API, which often leverages SYSTIMER. - Recognize which ESP32 variants include the SYSTIMER peripheral and how
esp_timer
behaves on others. - Troubleshoot common issues related to high-resolution timers.
Introduction
In the realm of embedded systems, while general-purpose timers (like those discussed in Chapter 171) offer flexibility for a wide array of tasks, there’s often a need for a dedicated, high-resolution, continuously running system clock. This clock serves as a reliable time base for the operating system, precise event timestamping, and fine-grained scheduling. On newer ESP32 variants, this role is fulfilled by the System Timer (SYSTIMER) peripheral.
The SYSTIMER is designed to provide a monotonic, 64-bit counter that typically runs at a fixed, high frequency (e.g., 16 MHz or 20 MHz, derived from the crystal oscillator). Unlike general-purpose timers that might be stopped, started, or reconfigured by different parts of an application, the SYSTIMER is generally intended to run continuously from boot, providing a stable time reference.
In ESP-IDF, the primary way to interact with this high-resolution timing capability is through the esp_timer
API. This API provides an abstraction layer, using SYSTIMER as its backend on supported chips, and falling back to other timer sources (like the LACT/FRC2 timer or RTC timer) on older variants. This chapter will delve into the SYSTIMER itself and, more practically, how to use the esp_timer
API for precision timing applications.
Understanding SYSTIMER and the esp_timer
API is crucial for tasks requiring microsecond-level accuracy, such as profiling code, synchronizing events, or implementing custom low-level schedulers.
Theory
SYSTIMER Peripheral Architecture
The SYSTIMER peripheral, where available, is a hardware module designed for system-level timekeeping. Its key characteristics typically include:
- Core Counter: A 64-bit main up-counter. This large width ensures an extremely long overflow period (many thousands of years), making it practically non-overflowing for typical application lifetimes.
- Clock Source: It’s driven by a fixed, high-frequency clock, usually derived directly from the main crystal oscillator (XTAL_CLK). A common configuration is
XTAL_CLK / 2
. For a 40 MHz XTAL, this results in a 20 MHz SYSTIMER clock (50 ns resolution). For a 32 MHz XTAL, it would be 16 MHz (62.5 ns resolution). This fixed frequency provides consistent timing. - Always-On (Typically): Once started (which is usually done by the system early in the boot process), the SYSTIMER runs continuously.
- Comparators (Alarms): SYSTIMER modules usually contain a few hardware comparators (often 3). When the main counter’s value matches a comparator’s target value, an alarm event (interrupt) can be triggered.
- One comparator is typically reserved for the operating system’s tick (e.g., FreeRTOS tick interrupt).
- The remaining comparators can be used for other system-level purposes or by the
esp_timer
API for its software timers.
- Low Power Operation: SYSTIMER is often designed to continue running even when the CPU cores are in light sleep mode, allowing for accurate timekeeping across sleep cycles.
The esp_timer
API: The Recommended Interface
While the SYSTIMER is a hardware peripheral, direct register manipulation is generally discouraged for application developers in ESP-IDF. Instead, Espressif provides the esp_timer
API (driver/include/esp_timer.h
) as a high-level abstraction for high-resolution wall-clock time and software timers.
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#DBEAFE', 'primaryTextColor': '#1E40AF', 'primaryBorderColor': '#2563EB'}}}%% graph TD subgraph "Your Application Code" A("<b>esp_timer_get_time()</b><br><b>esp_timer_create()</b>"); end subgraph "ESP-IDF Abstraction Layer" B("esp_timer API"); end subgraph "Hardware Backend (Chip Dependent)" C{"If (ESP32-S3, C3, C6, ...)"}; D["<b>SYSTIMER</b><br>(Hardware 64-bit counter)"]; E["<b>LACT / FRC2 or RTC Timer</b><br>(Software 64-bit extension)"]; end A --> B; B --> C; C -- "True" --> D; C -- "False (ESP32, S2, ...)" --> E; %% Styling classDef app fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef api fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef hardware fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; class A app; class B api; class C decision; class D,E hardware;
Key features of esp_timer
:
- Microsecond Resolution:
esp_timer_get_time()
returns the time sinceesp_timer
initialization (typically close to boot time) as a 64-bit integer in microseconds. - Backend Agnostic (to an extent):
- On ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2, and other newer variants with SYSTIMER,
esp_timer
uses SYSTIMER as its default backend (CONFIG_ESP_TIMER_IMPL_SYSTIMER
). - On older variants like the original ESP32 and ESP32-S2 (which lack SYSTIMER),
esp_timer
uses the LACT (Low Accuracy Timer, also known as FRC2) or RTC timer as a backend (CONFIG_ESP_TIMER_IMPL_LACT
orCONFIG_ESP_TIMER_IMPL_RTC
). The precision and behavior (especially during sleep) can differ.
- On ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2, and other newer variants with SYSTIMER,
- Software Timers: The
esp_timer
API allows creation of multiple software timers (one-shot or periodic) that are multiplexed onto the underlying hardware comparators (if SYSTIMER is the backend) or managed in software. These are distinct from FreeRTOS timers (Chapter 15) and offer higher precision. Callbacks fromesp_timer
run from a high-priorityesp_timer
task.
Key esp_timer
Functions
esp_err_t esp_timer_init()
:- Initializes the
esp_timer
system. This is usually called automatically by the startup code (esp_system_init()
). Applications typically don’t need to call this directly unless they have very specific custom startup sequences.
- Initializes the
int64_t esp_timer_get_time()
:- This is the most commonly used function. It returns the current time as a
uint64_t
value representing the number of microseconds sinceesp_timer
was initialized. - It’s designed to be a low-overhead call, suitable for frequent polling or timestamping.
- This is the most commonly used function. It returns the current time as a
esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle)
:- Creates a software timer.
esp_timer_create_args_t
includes:callback
: The function to call when the timer expires.arg
: An argument to pass to the callback.dispatch_method
: Specifies how the callback is dispatched.ESP_TIMER_TASK
(default) dispatches via theesp_timer
task.ESP_TIMER_ISR
attempts to dispatch from ISR context (use with extreme care, only for very short, critical callbacks, and subject to backend capabilities).name
: A descriptive name for the timer (useful for debugging).skip_unhandled_events
: If true, skip events if the timer task is busy.
out_handle
: Returns a handle to the created timer.
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#DBEAFE', 'primaryTextColor': '#1E40AF', 'primaryBorderColor': '#2563EB'}}}%% graph TD A(Timer Interrupt Fires) --> B{Dispatch Method?}; B -- "ESP_TIMER_TASK (Default)" --> C[Event sent to<br><b>esp_timer</b> FreeRTOS task]; C --> D["Callback runs in a<br>high-priority task context."]; D --> E["<b>Pros:</b> Safer, can use<br>some FreeRTOS APIs.<br><b>Cons:</b> Higher latency."]; B -- "ESP_TIMER_ISR" --> F[Callback executed<br>directly from ISR]; F --> G["Callback runs in<br><b>Interrupt Context</b>."]; G --> H["<b>Pros:</b> Very low latency.<br><b>Cons:</b> Highly restricted.<br>Cannot block or use<br>most FreeRTOS APIs."]; %% Styling classDef startNode fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef goodNode fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46; classDef badNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; class A startNode; class B decisionNode; class C,D,F,G processNode; class E goodNode; class H badNode;
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us)
:- Starts a previously created timer to fire once after
timeout_us
microseconds.
- Starts a previously created timer to fire once after
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us)
:- Starts a previously created timer to fire periodically, with an interval of
period_us
microseconds.
- Starts a previously created timer to fire periodically, with an interval of
esp_err_t esp_timer_stop(esp_timer_handle_t timer)
:- Stops a running timer. The timer can be restarted later.
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
:- Stops and deletes a timer, freeing its resources.
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#DBEAFE', 'primaryTextColor': '#1E40AF', 'primaryBorderColor': '#2563EB'}}}%% graph TD subgraph "Setup Phase" A(Start) --> B{Create Timer}; B -- esp_timer_create() --> C[Get Timer Handle]; end subgraph "Operational Phase" C --> D{Choose Mode}; D -- "One-Shot" --> E["esp_timer_start_once()"]; D -- "Periodic" --> F["esp_timer_start_periodic()"]; E --> G{Wait for Timeout}; F --> H{Wait for Period}; H --> I[Callback Fires]; I --> H; G --> J[Callback Fires]; end subgraph "Teardown Phase" K(Stop?) --> L{"esp_timer_stop()"}; L --> M{"esp_timer_delete()"}; M --> N(End); end J --> K; I --> K; %% Styling classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; class A,N startNode; class B,C,E,F,G,H,I,J,L,M processNode; class D,K decisionNode;
SYSTIMER vs. GPTimers (Chapter 171)
Feature | SYSTIMER (via esp_timer) | GPTimers (General Purpose Timers) |
---|---|---|
Primary Use | System-wide high-resolution timekeeping, precise timestamps, esp_timer software timers. | Application-specific timing, PWM, event counting, custom periodic interrupts. |
Counter | 64-bit, typically always running from boot. | 64-bit (typically), with configurable start, stop, and direction control. |
Resolution | Fixed and high (e.g., 50 ns raw). esp_timer_get_time() provides microsecond resolution. |
Configurable via a prescaler; can range from very high to lower resolutions. |
Clock Source | Fixed, high-frequency source (e.g., XTAL/2). | Selectable clock sources (e.g., APB, XTAL, RTC). |
Configuration | Minimal direct user configuration; managed by the esp_timer API. |
Highly configurable: prescaler, alarm, auto-reload, count direction, etc. |
Always On | Yes, on supported chips after initialization. | No, it is controlled by the application (start/stop). |
API Focus | esp_timer_get_time() , esp_timer_create() , etc. |
gptimer_new_timer() , gptimer_set_alarm_action() , etc. |
Availability | Newer ESP32 variants (S3, C3, C6, H2). | Available on most or all ESP32 variants. |
Sleep Behavior | Designed to continue running during light sleep. | Behavior varies; timers clocked by APB may stop during sleep. |
In essence, use esp_timer_get_time()
for getting the current “wall-clock” time with microsecond precision. Use esp_timer_create()
for general-purpose software timers when you need callbacks at specific intervals with higher precision than FreeRTOS timers might offer. Use GPTimers when you need direct hardware control over counting, specific alarm values tied to raw counts, complex reload behaviors, or interaction with other peripherals that might use GPTimer events.
Practical Examples
The following examples assume you are working with an ESP32 variant that supports SYSTIMER as the backend for esp_timer
(e.g., ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2). If you are on an older variant, the esp_timer
API will still work, but its underlying source will be different.
Example 1: Getting Current Time and Basic Timestamping
This example demonstrates how to get the current system time using esp_timer_get_time()
and use it for basic timestamping.
1. Project Setup:
Create a new ESP-IDF project or use an existing one.
2. Code (main/main.c
):
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_rom_sys.h" // For esp_rom_delay_us
static const char *TAG = "systimer_example_1";
void app_main(void)
{
// esp_timer_init() is called by system startup code.
int64_t time_now_us = esp_timer_get_time();
ESP_LOGI(TAG, "Time since boot: %lld microseconds", time_now_us);
ESP_LOGI(TAG, "Performing a short delay...");
int64_t start_time_us = esp_timer_get_time();
// Simulate some work or delay
esp_rom_delay_us(500 * 1000); // Delay for 500 milliseconds (500,000 microseconds)
int64_t end_time_us = esp_timer_get_time();
ESP_LOGI(TAG, "Delay finished.");
int64_t duration_us = end_time_us - start_time_us;
ESP_LOGI(TAG, "Start time: %lld us", start_time_us);
ESP_LOGI(TAG, "End time: %lld us", end_time_us);
ESP_LOGI(TAG, "Measured duration: %lld microseconds (expected ~500000 us)", duration_us);
// Loop to show time progression
for (int i = 0; i < 5; i++) {
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay 1 second using FreeRTOS ticks
time_now_us = esp_timer_get_time();
ESP_LOGI(TAG, "Current time: %lld us", time_now_us);
}
}
3. CMakeLists.txt (main/CMakeLists.txt):
Ensure your CMakeLists.txt includes esp_timer (usually pulled in by esp_system or freertos). Explicitly:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_timer esp_log freertos)
4. Build Instructions:
- Open VS Code.
- Ensure your ESP-IDF project is selected and configured for your target ESP32 variant.
- Build the project (Espressif IDF: Build your project).
5. Run/Flash/Observe Steps:
- Flash the built firmware to your ESP32 (Espressif IDF: Flash your project (UART)).
- Open the ESP-IDF Monitor (Espressif IDF: Monitor your device).
- Observe the logged timestamps and the measured duration, which should be close to 500,000 µs. You’ll also see the current time printed every second.
Example 2: One-Shot esp_timer
This example demonstrates creating and using a one-shot timer with esp_timer
.
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#DBEAFE', 'primaryTextColor': '#1E40AF', 'primaryBorderColor': '#2563EB'}}}%% stateDiagram-v2 direction TB [*] --> Idle: esp_timer_create() Idle --> Running: esp_timer_start_once() Running --> Fired: Timeout expires Fired --> Idle: Callback completes Running --> Idle: esp_timer_stop() Idle --> [*]: esp_timer_delete() Running --> [*]: esp_timer_delete() Fired --> [*]: esp_timer_delete() note right of Fired : The timer's callback function\nis executed at this point.
1. Code (main/main.c
):
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
static const char *TAG = "systimer_example_2";
static void oneshot_timer_callback(void* arg)
{
int64_t time_now_us = esp_timer_get_time();
char* timer_name = (char*) arg;
ESP_LOGI(TAG, "One-shot timer '%s' fired at %lld us", timer_name, time_now_us);
}
void app_main(void)
{
ESP_LOGI(TAG, "Creating and starting a one-shot esp_timer...");
esp_timer_handle_t oneshot_timer_handle;
esp_timer_create_args_t oneshot_timer_args = {
.callback = &oneshot_timer_callback,
.arg = "MyOneShotTimer", // Argument for the callback
.name = "one-shot" // Descriptive name for debugging
// .dispatch_method = ESP_TIMER_TASK, // Default
// .skip_unhandled_events = false, // Default
};
ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer_handle));
// Start the timer to fire once after 2.5 seconds (2,500,000 microseconds)
uint64_t timeout_us = 2500 * 1000;
ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer_handle, timeout_us));
ESP_LOGI(TAG, "One-shot timer started. Will fire in %.2f seconds.", (double)timeout_us / 1000000.0);
// Keep main task alive to allow timer to fire
// In a real app, other tasks would be running.
vTaskDelay(pdMS_TO_TICKS(5000)); // Wait for 5 seconds
ESP_LOGI(TAG, "Deleting the one-shot timer.");
ESP_ERROR_CHECK(esp_timer_stop(oneshot_timer_handle)); // Good practice to stop before delete, though delete might imply stop.
ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer_handle));
ESP_LOGI(TAG, "Example finished.");
}
2. Build, Flash, and Observe:
Follow standard build/flash steps. You should see the log indicating the timer has started, and then approximately 2.5 seconds later, the “One-shot timer ‘MyOneShotTimer’ fired” message.
Example 3: Periodic esp_timer
This example demonstrates creating and using a periodic timer with esp_timer
.
1. Code (main/main.c
):
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
static const char *TAG = "systimer_example_3";
static int periodic_fire_count = 0;
static void periodic_timer_callback(void* arg)
{
periodic_fire_count++;
int64_t time_now_us = esp_timer_get_time();
ESP_LOGI(TAG, "Periodic timer fired! Count: %d, Time: %lld us", periodic_fire_count, time_now_us);
esp_timer_handle_t* timer_handle_ptr = (esp_timer_handle_t*) arg;
if (periodic_fire_count >= 5) {
ESP_LOGI(TAG, "Periodic timer fired 5 times. Stopping and deleting it.");
ESP_ERROR_CHECK(esp_timer_stop(*timer_handle_ptr));
ESP_ERROR_CHECK(esp_timer_delete(*timer_handle_ptr));
ESP_LOGI(TAG, "Periodic timer deleted.");
}
}
void app_main(void)
{
ESP_LOGI(TAG, "Creating and starting a periodic esp_timer...");
// Note: We pass the address of the handle to the callback so it can delete itself.
// This is one way; another is to use a global or signal another task to delete it.
static esp_timer_handle_t periodic_timer_handle; // Static or global if passed to callback for deletion
esp_timer_create_args_t periodic_timer_args = {
.callback = &periodic_timer_callback,
.arg = &periodic_timer_handle, // Pass timer handle address as argument
.name = "periodic"
};
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer_handle));
// Start the timer to fire every 750 milliseconds (750,000 microseconds)
uint64_t period_us = 750 * 1000;
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, period_us));
ESP_LOGI(TAG, "Periodic timer started. Will fire every %.3f seconds.", (double)period_us / 1000000.0);
// Keep main task alive. The timer callback will eventually stop and delete the timer.
// If the timer ran indefinitely, this task would need to run indefinitely too.
while(periodic_fire_count < 5) {
vTaskDelay(pdMS_TO_TICKS(100));
}
ESP_LOGI(TAG, "Main task observed 5 firings. Example finished.");
}
2. Build, Flash, and Observe:
Build and flash. You will see the “Periodic timer fired!” message approximately every 750 milliseconds. After 5 firings, the timer callback will stop and delete the timer, and the program will conclude.
Variant Notes
The availability and underlying implementation of high-resolution timing via esp_timer
vary significantly across ESP32 variants. This is critical to understand for portability and performance expectations.
ESP32 Variant Family | SYSTIMER Peripheral | Default esp_timer Backend | Key Characteristics |
---|---|---|---|
ESP32-S3, C3, C6, H2 | Yes | SYSTIMER | Provides true hardware 64-bit microsecond timing. Runs from XTAL clock (e.g., 20 MHz from 40 MHz XTAL). Resilient across light sleep modes. |
ESP32, ESP32-S2 | No | LACT or RTC | Lacks the dedicated SYSTIMER hardware. esp_timer relies on other timers like LACT (FRC2) or RTC, which have different precision and sleep behavior. |
- ESP32, ESP32-S2:
- Do NOT have the SYSTIMER peripheral.
- On these chips,
esp_timer
typically uses the LACT (Low Accuracy Timer / FRC2) or RTC timer as its backend. - The LACT is a 32-bit timer, and
esp_timer
extends it to 64-bit in software. Its resolution is typically based on the APB clock (e.g., 80 MHz), so it can still offer microsecond-level timing, but its characteristics (e.g., behavior in deep sleep) are different from SYSTIMER. - RTC timer has lower resolution but can run during deep sleep.
- The exact precision and behavior during sleep modes might differ compared to SYSTIMER-backed implementations.
- ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2:
- HAVE the SYSTIMER peripheral.
esp_timer
uses SYSTIMER as its default high-resolution backend.- SYSTIMER typically runs from
XTAL_CLK / 2
. For example:- If XTAL is 40 MHz, SYSTIMER clock is 20 MHz (50 ns per tick).
- If XTAL is 32 MHz (e.g., some ESP32-C3 modules), SYSTIMER clock is 16 MHz (62.5 ns per tick).
- This provides true hardware-based 64-bit microsecond timing and is designed to work reliably across light sleep modes.
Key Implications:
- Code Portability: The
esp_timer
API itself is portable. Code written usingesp_timer_get_time()
,esp_timer_create()
, etc., will compile and run on all variants. - Performance & Precision: The actual precision and overhead of
esp_timer_get_time()
might differ slightly. SYSTIMER is generally optimized for this. - Sleep Mode Behavior: Timers based on SYSTIMER are often more resilient across light sleep states. If using
esp_timer
on older chips, check documentation for behavior if deep sleep or extensive light sleep is involved. - Kconfig: The
CONFIG_ESP_TIMER_IMPL
option insdkconfig
shows which backend is active. For newer chips, it defaults toSYSTIMER
.
Tip: Always verify the SYSTIMER clock frequency for your specific variant and XTAL frequency by checking its Technical Reference Manual (TRM). The
esp_timer
API abstracts this into microseconds, but understanding the underlying hardware tick rate can be useful for advanced debugging or performance analysis.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Blocking in a callback | Other timers are delayed or don’t fire. System seems unresponsive. Watchdog timer might be triggered. | Solution: Keep callbacks extremely short. Offload heavy work to a dedicated FreeRTOS task using a queue or semaphore to signal it. |
Forgetting to delete timers | Memory leak over time. Application may eventually crash from resource exhaustion. | Solution: Ensure every call to esp_timer_create() has a matching esp_timer_delete() when the timer is no longer needed. |
Using vTaskDelay in a callback |
The entire esp_timer task is blocked, delaying all other software timers. This is a severe form of blocking. |
Solution: Never use FreeRTOS blocking delays inside a timer callback. Use one-shot timers to schedule future actions if needed. |
Confusing µs and ms | Timer fires 1000x too fast or 1000x too slow. A 1-second timer set to 1000 will fire in 1ms. |
Solution: Remember all esp_timer API calls use microseconds. Use 1000 * 1000 for one second. |
Modifying a timer from another task without a handle | Unable to stop or delete a timer created in a different scope or task. | Solution: Store the esp_timer_handle_t in a location accessible to the tasks that need to control the timer (e.g., a global variable or passed in a struct). |
Assuming timer portability without checking backend | Code relies on SYSTIMER’s light sleep resilience but is run on an ESP32 where the timer stops in sleep. | Solution: Abstract behavior with the esp_timer API, but if sleep is critical, check the docs for the specific backend (SYSTIMER vs LACT) on your target chip. |
Exercises
- High-Frequency Event Timestamping:
- Write an application that simulates generating 10 “events” in rapid succession within a loop (e.g., by calling
esp_rom_delay_us()
for a very short, slightly varying period like 50-100µs between them). - For each event, get a timestamp using
esp_timer_get_time()
. - After all events, print each timestamp and the delta time (duration in µs) between consecutive events. This will highlight the microsecond precision.
- Write an application that simulates generating 10 “events” in rapid succession within a loop (e.g., by calling
esp_timer
vs. GPTimer for Short Delays (on compatible chip):- Requires a chip with both SYSTIMER (for
esp_timer
) and GPTimers (e.g., ESP32-S3). - Objective: Compare the practical precision/overhead of creating a very short delay using
esp_timer_start_once()
versus configuring a GPTimer for a one-shot alarm. - Part A: Implement a 200 µs delay using
esp_timer_start_once()
. The callback should log the actual time elapsed usingesp_timer_get_time()
. - Part B: Implement a 200 µs delay using a GPTimer configured for a one-shot alarm. The GPTimer ISR should log the actual time elapsed (perhaps by reading another free-running GPTimer or
esp_timer_get_time()
if careful about ISR context). - Compare the setup complexity and observed accuracy. (Note: This is an advanced exercise).
- Requires a chip with both SYSTIMER (for
- Microsecond Accurate LED Blink Scheduler:
- Create two
esp_timer
instances. - Timer 1: Periodic, fires every 150.250 milliseconds (150250 µs). Its callback toggles LED1.
- Timer 2: Periodic, fires every 300.500 milliseconds (300500 µs). Its callback toggles LED2.
- Observe the LEDs. They should blink at precise, slightly offset rates, demonstrating the microsecond-level scheduling capability of
esp_timer
. This is more precise than whatvTaskDelay
(which is tick-based) can typically achieve for such fractional millisecond timings.
- Create two
Summary
- The SYSTIMER peripheral (on newer ESP32 variants like S3, C3, C6, H2) provides a high-resolution, 64-bit, always-on counter, typically clocked from
XTAL_CLK / 2
. - The
esp_timer
API is the recommended way to access high-resolution time and create software timers in ESP-IDF. It uses SYSTIMER as a backend on supported chips. esp_timer_get_time()
returns the time in microseconds since initialization as auint64_t
, offering a precise and non-overflowing timestamp.esp_timer_create()
,esp_timer_start_once()
,esp_timer_start_periodic()
,esp_timer_stop()
, andesp_timer_delete()
are used to manage software timers with microsecond-level precision.- Older ESP32 variants (original ESP32, ESP32-S2) lack SYSTIMER;
esp_timer
on these chips uses LACT or RTC timers as backends, which may have different characteristics. esp_timer
callbacks should be kept short and non-blocking; defer lengthy operations to dedicated tasks.- SYSTIMER/
esp_timer
is ideal for precise timestamping, profiling, and scheduling tasks with finer granularity than standard FreeRTOS ticks.
Further Reading
- ESP-IDF API Reference –
esp_timer
:- https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/system/esp_timer.html (Replace
esp32
with your target variant likeesp32s3
,esp32c3
if you want to see if there are variant-specific notes, though the API is general).
- https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/system/esp_timer.html (Replace
- ESP32-S3 Technical Reference Manual (Example for a chip with SYSTIMER):
- Search for “ESP32-S3 Technical Reference Manual” on the Espressif website and navigate to the “SYSTIMER” chapter.
- Example (ESP32-S3 TRM): https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf
- Kconfig options related to
esp_timer
:- Check
components/esp_system/Kconfig.projbuild
andcomponents/esp_timer/Kconfig
in your ESP-IDF installation to see options likeCONFIG_ESP_TIMER_IMPL
.
- Check