General Purpose Timer (GPTimer)

Introduction

A general purpose timer is a hardware timer with high resolution and flexible alarm action. The behavior when the internal counter of a timer reaches a specific target value is called timer alarm. When a timer alarms, a user registered per-timer callback would be called.

Typically, a general purpose timer can be used in scenarios like:

  • Free running as a wall clock, fetching a high-resolution time stamp at any time and any places

  • Generate period alarms, trigger events periodically

  • Generate one-shot alarm, respond in target time

Functional Overview

The following sections of this document cover the typical steps to install and operate a timer:

  • Resource Allocation - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working.

  • Set and Get count value - covers how to force the timer counting from a start point and how to get the count value at anytime.

  • Start and Stop timer - covers which parameters should be set up to start the timer with specific alarm event behavior.

  • Power Management - describes how different source clock selections can affect power consumption.

  • IRAM safe - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache.

Resource Allocation

Different ESP chip might have different number of independent timer groups, and within each group, there could also be several independent timers. Refer to the datasheet to find out how many hardware timers exist (usually described in the “General Purpose Timer” chapter).

From driver’s point of view, a GPTimer instance is represented by gptimer_handle_t. The driver behind will manage all available hardware resources in a pool, so that users don’t need to care about which timer and which group it belongs to.

To install a timer instance, there’s a configuration structure that needs to be given in advance: gptimer_config_t:

  • clk_src selects the source clock for the timer. The available clocks are listed in gptimer_clock_source_t, 1 you can only pick one of them. For the effect on power consumption of different clock source, please refer to Power management section.

  • direction sets the counting direction of the timer, supported directions are listed in gptimer_count_direction_t, you can only pick one of them.

  • resolution_hz sets the resolution of the internal counter. Each count step is equivalent to 1 / resolution_hz seconds.

  • Optional intr_shared sets whether or not mark the timer interrupt source as a shared one. For the pros/cons of a shared interrupt, you can refer to Interrupt Handling.

With all the above configurations set in the structure, the structure can be passed to gptimer_new_timer() which will instantiate the timer instance and return a handle of the timer.

The function can fail due to various errors such as insufficient memory, invalid arguments, etc. Specifically, when there are no more free timers (i.e. all hardware resources have been used up), then ESP_ERR_NOT_FOUND will be returned. The total number of available timers is represented by the SOC_TIMER_GROUP_TOTAL_TIMERS and its value will depend on the ESP chip.

If a previously created GPTimer instance is no longer required, you should recycle the timer by calling gptimer_del_timer(). This will allow the underlying HW timer to be used for other purposes. Before deleting a GPTimer handle, you should stop it by gptimer_stop() in advance or make sure it has not started yet by gptimer_start().

Creating a GPTimer Handle with Resolution of 1MHz

gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
    .clk_src = GPTIMER_CLK_SRC_APB,
    .direction = GPTIMER_COUNT_UP,
    .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));

Set and Get Count Value

When the GPTimer is created, the internal counter will be reset to zero by default. The counter value can be updated asynchronously by gptimer_set_raw_count(). The maximum count value is dependent on the hardware timer’s bit-width, which is also reflected by the SOC macro SOC_TIMER_GROUP_COUNTER_BIT_WIDTH. When updating the raw count of an active timer, the timer will immediately start counting from the new value.

Count value can be retrieved by gptimer_get_raw_count(), at anytime.

Set Up Alarm Action

Most of the use cases of GPTimer should set up the alarm action before starting the timer, except for the simple wall-clock scenario, where a free running timer is enough. To set up the alarm action, one should configure several members of gptimer_alarm_config_t based on how he takes use of the alarm event:

  • alarm_count sets the target count value that will trigger the alarm event. You should also take the counting direction into consideration when setting the alarm value. Specially, alarm_count and reload_count can’t be set to the same value when auto_reload_on_alarm is true, as keeping reload with a target alarm count is meaningless.

  • reload_count sets the count value to be reloaded when the alarm event happens. This configuration only takes effect when auto_reload_on_alarm is set to true.

  • auto_reload_on_alarm flag sets whether to enable the auto-reload feature. If enabled, the hardware timer will reload the value of reload_count into counter immediately when alarm event happens.

To make the alarm configurations take effect, one should call gptimer_set_alarm_action(). Especially, if gptimer_alarm_config_t is set to NULL, the alarm function will be disabled.

Note

  • If an alarm value is set and the timer has already crossed this value, the alarm will be triggered immediately.

Register Event Callbacks

After the timer starts up, it can generate specific event (e.g. the “Alarm Event”) dynamically. If you have some function that should be called when event happens, you should hook your function to the interrupt service routine by calling gptimer_register_event_callbacks(). All supported event callbacks are listed in the gptimer_event_callbacks_t:

  • on_alarm sets callback function for alarm event. As this function is called within the ISR context, user must ensure that the function doesn’t attempt to block (e.g., by making sure that only FreeRTOS APIs with ISR suffix are called from within the function). The function prototype is declared in gptimer_alarm_cb_t.

One can save his own context to gptimer_register_event_callbacks() as well, via the parameter user_data. The user data will be directly passed to the callback functions.

Start and Stop Timer

To start a timer means to enable its internal counter, it can only be achieved by calling gptimer_start(). The timer can be stopped at any time (even in the interrupt context) by gptimer_stop(). One thing should be kept in mind, calling of gptimer_start() should have the same times of calling gptimer_stop() before you delete the timer, otherwise the driver might be put in an undetermined state. For example, the timer might keep a Power Management lock, which in return increase the power consumption. Also see Power management section.

Start Timer As a Wall Clock

ESP_ERROR_CHECK(gptimer_start(gptimer));
// Retrieve timestamp at anytime
uint64_t count;
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));

Trigger Period Events

typedef struct {
    uint64_t event_count;
} example_queue_element_t;

static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_ctx;
    // Retrieve count value from event data
    example_queue_element_t ele = {
        .event_count = edata->count_value
    };
    // Optional: send the event data to other task by OS queue
    // Don't introduce complex logics in callbacks.
    // Suggest dealing with event data in the main loop, instead of in this callback.
    xQueueSendFromISR(queue, &ele, &high_task_awoken);
    // return whether we need to yield at the end of ISR
    return high_task_awoken == pdTRUE;
}

gptimer_alarm_config_t alarm_config = {
    .reload_count = 0, // counter will reload with 0 on alarm event
    .alarm_count = 1000000, // period = 1s @resolution 1MHz
    .flags.auto_reload_on_alarm = true, // enable auto-reload
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));

gptimer_event_callbacks_t cbs = {
    .on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));

ESP_ERROR_CHECK(gptimer_start(gptimer));

Trigger One-Shot Event

typedef struct {
    uint64_t event_count;
} example_queue_element_t;

static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_ctx;
    // Stop timer the sooner the better
    gptimer_stop(timer);
    // Retrieve count value from event data
    example_queue_element_t ele = {
        .event_count = edata->count_value
    };
    // Optional: send the event data to other task by OS queue
    xQueueSendFromISR(queue, &ele, &high_task_awoken);
    // return whether we need to yield at the end of ISR
    return high_task_awoken == pdTRUE;
}

gptimer_alarm_config_t alarm_config = {
    .alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));

gptimer_event_callbacks_t cbs = {
    .on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_start(gptimer));

Dynamic Alarm Update

Alarm value can be updated dynamically inside the ISR handler callback, by changing the alarm_value of gptimer_alarm_event_data_t. Then the alarm value will be updated after the callback function returns.

typedef struct {
    uint64_t event_count;
} example_queue_element_t;

static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    // Retrieve count value from event data
    example_queue_element_t ele = {
        .event_count = edata->count_value
    };
    // Optional: send the event data to other task by OS queue
    xQueueSendFromISR(queue, &ele, &high_task_awoken);
    // reconfigure alarm value
    gptimer_alarm_config_t alarm_config = {
        .alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
    };
    gptimer_set_alarm_action(timer, &alarm_config);
    // return whether we need to yield at the end of ISR
    return high_task_awoken == pdTRUE;
}

gptimer_alarm_config_t alarm_config = {
    .alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));

gptimer_event_callbacks_t cbs = {
    .on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));

Power Management

When power management is enabled (i.e. CONFIG_PM_ENABLE is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the period of a GPTimer’s counting step and leading to inaccurate time keeping.

However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type ESP_PM_APB_FREQ_MAX. Whenever the driver creates a GPTimer instance that has selected GPTIMER_CLK_SRC_APB as its clock source, the driver will guarantee that the power management lock is acquired when the timer is started by gptimer_start(). Likewise, the driver releases the lock when gptimer_stop() is called for that timer. This requires that the gptimer_start() and gptimer_stop() should appear in pairs.

IRAM Safe

By default, the GPTimer interrupt will be deferred when the Cache is disabled for reasons like writing/erasing Flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.

There’s a Kconfig option CONFIG_GPTIMER_ISR_IRAM_SAFE that will:

  1. Enable the interrupt being serviced even when cache is disabled

  2. Place all functions that used by the ISR into IRAM 2

  3. Place driver object into DRAM (in case it’s linked to PSRAM by accident)

This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.

There’s another Kconfig option CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM that can put commonly used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are as follows:

Application Examples

Typical use cases of GPTimer are listed in the example: peripherals/timer_group/gptimer.

API Reference

Functions

esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)

Create a new General Purpose Timer, and return the handle.

Note

Once a timer is created, it is placed in the stopped state and will not start until gptimer_start() is called.

Return

  • ESP_OK: Create GPTimer successfully

  • ESP_ERR_INVALID_ARG: Create GPTimer failed because of invalid argument

  • ESP_ERR_NO_MEM: Create GPTimer failed because out of memory

  • ESP_ERR_NOT_FOUND: Create GPTimer failed because all hardware timers are used up and no more free one

  • ESP_FAIL: Create GPTimer failed because of other error

Parameters
  • [in] config: GPTimer configuration

  • [out] ret_timer: Returned timer handle

esp_err_t gptimer_del_timer(gptimer_handle_t timer)

Delete the GPTimer handle.

Note

A timer must be in a stop state before it can be deleted.

Return

  • ESP_OK: Delete GPTimer successfully

  • ESP_ERR_INVALID_ARG: Delete GPTimer failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Delete GPTimer failed because the timer has not stopped

  • ESP_FAIL: Delete GPTimer failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value)

Set GPTimer raw count value.

Note

When updating the raw count of an active timer, the timer will immediately start counting from the new value.

Note

This function is allowed to run within ISR context

Note

This function is allowed to be executed when Cache is disabled, by enabling CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM

Return

  • ESP_OK: Set GPTimer raw count value successfully

  • ESP_ERR_INVALID_ARG: Set GPTimer raw count value failed because of invalid argument

  • ESP_FAIL: Set GPTimer raw count value failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

  • [in] value: Count value to be set

esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value)

Get GPTimer raw count value.

Note

With the raw count value and the resolution set in the gptimer_config_t, you can convert the count value into seconds.

Note

This function is allowed to run within ISR context

Note

This function is allowed to be executed when Cache is disabled, by enabling CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM

Return

  • ESP_OK: Get GPTimer raw count value successfully

  • ESP_ERR_INVALID_ARG: Get GPTimer raw count value failed because of invalid argument

  • ESP_FAIL: Get GPTimer raw count value failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

  • [out] value: Returned GPTimer count value

esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data)

Set callbacks for GPTimer.

Note

The user registered callbacks are expected to be runnable within ISR context

Return

  • ESP_OK: Set event callbacks successfully

  • ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument

  • ESP_FAIL: Set event callbacks failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

  • [in] cbs: Group of callback functions

  • [in] user_data: User data, which will be passed to callback functions directly

esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)

Set alarm event actions for GPTimer.

Note

This function is allowed to run within ISR context

Note

This function is allowed to be executed when Cache is disabled, by enabling CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM

Return

  • ESP_OK: Set alarm action for GPTimer successfully

  • ESP_ERR_INVALID_ARG: Set alarm action for GPTimer failed because of invalid argument

  • ESP_FAIL: Set alarm action for GPTimer failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

  • [in] config: Alarm configuration, especially, set config to NULL means disabling the alarm function

esp_err_t gptimer_start(gptimer_handle_t timer)

Start GPTimer.

Note

This function is allowed to run within ISR context

Note

This function is allowed to be executed when Cache is disabled, by enabling CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM

Return

  • ESP_OK: Start GPTimer successfully

  • ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not in stop state

  • ESP_FAIL: Start GPTimer failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

esp_err_t gptimer_stop(gptimer_handle_t timer)

Stop GPTimer.

Note

This function is allowed to run within ISR context

Note

This function is allowed to be executed when Cache is disabled, by enabling CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM

Return

  • ESP_OK: Stop GPTimer successfully

  • ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not in start state

  • ESP_FAIL: Stop GPTimer failed because of other error

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

Structures

struct gptimer_alarm_event_data_t

GPTimer event data.

Public Members

uint64_t count_value

Current count value

uint64_t alarm_value

Current alarm value

struct gptimer_event_callbacks_t

Group of supported GPTimer callbacks.

Note

The callbacks are all running under ISR environment

Public Members

gptimer_alarm_cb_t on_alarm

Timer alarm callback

struct gptimer_config_t

General Purpose Timer configuration.

Public Members

gptimer_clock_source_t clk_src

GPTimer clock source

gptimer_count_direction_t direction

Count direction

uint32_t resolution_hz

Counter resolution (working frequency) in Hz, hence, the step size of each count tick equals to (1 / resolution_hz) seconds

uint32_t intr_shared : 1

Set true, the timer interrupt number can be shared with other peripherals

struct gptimer_alarm_config_t

General Purpose Timer alarm configuration.

Public Members

uint64_t alarm_count

Alarm target count value

uint64_t reload_count

Alarm reload count value, effect only when auto_reload_on_alarm is set to true

uint32_t auto_reload_on_alarm : 1

Reload the count value by hardware, immediately at the alarm event

Type Definitions

typedef struct gptimer_t *gptimer_handle_t

Type of General Purpose Timer handle.

typedef bool (*gptimer_alarm_cb_t)(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)

Timer alarm callback prototype.

Return

Whether a high priority task has been waken up by this function

Parameters
  • [in] timer: Timer handle created by gptimer_new_timer()

  • [in] edata: Alarm event data, fed by driver

  • [in] user_ctx: User data, passed from gptimer_config_t

Enumerations

enum gptimer_clock_source_t

GPTimer clock source.

Note

The clock source listed here is not supported on all targets

Note

User should select the clock source based on real requirements:

GPTimer clock source

Features

Power Management

GPTIMER_CLK_SRC_APB

High resolution

ESP_PM_APB_FREQ_MAX lock

GPTIMER_CLK_SRC_XTAL

Medium resolution, high accuracy

No PM lock

Values:

GPTIMER_CLK_SRC_APB

Select APB as the source clock

enum gptimer_count_direction_t

GPTimer count direction.

Values:

GPTIMER_COUNT_DOWN

Decrease count value

GPTIMER_COUNT_UP

Increase count value

1

Some ESP chip might only support a sub-set of the clocks, if an unsupported clock source is specified, you will get a runtime error during timer installation.

2

on_alarm callback and the functions invoked by itself should also be placed in IRAM, users need to take care of them by themselves.