通用定时器

简介

通用定时器是 ESP32-S2 定时器组外设的驱动程序。ESP32-S2 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。

通用定时器通常在以下场景中使用:

  • 如同挂钟一般自由运行,随时随地获取高分辨率时间戳;

  • 生成周期性警报,定期触发事件;

  • 生成一次性警报,在目标时间内响应。

功能概述

下文介绍了配置和操作定时器的常规步骤:

  • 资源分配 - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。

  • 设置和获取计数值 - 如何强制定时器从起点开始计数,以及如何随时获取计数值。

  • 设置警报动作 - 启动警报事件应设置的参数。

  • 注册事件回调函数 - 如何将用户的特定代码挂载到警报事件回调函数。

  • 使能和禁用定时器 - 如何使能和禁用定时器。

  • 启动和停止定时器 - 通过不同报警行为启动定时器的典型使用场景。

  • 电源管理 - 选择不同的时钟源将会如何影响功耗。

  • IRAM 安全 - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。

  • 线程安全 - 驱动程序保证哪些 API 线程安全。

  • Kconfig 选项 - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。

资源分配

不同的 ESP 芯片可能有不同数量的独立定时器组,每组内也可能有若干个独立定时器。1

通用定时器实例由 gptimer_handle_t 表示。后台驱动会在资源池中管理所有可用的硬件资源,这样您便无需考虑硬件所属的定时器以及定时器组。

要安装一个定时器实例,需要提前提供配置结构体 gptimer_config_t

完成上述结构配置之后,可以将结构传递给 gptimer_new_timer(),用以实例化定时器实例并返回定时器句柄。

该函数可能由于内存不足、参数无效等错误而失败。具体来说,当没有更多的空闲定时器(即所有硬件资源已用完)时,将返回 ESP_ERR_NOT_FOUND。可用定时器总数由 SOC_TIMER_GROUP_TOTAL_TIMERS 表示,不同的 ESP 芯片该数值不同。

如已不再需要之前创建的通用定时器实例,应通过调用 gptimer_del_timer() 回收定时器,以便底层硬件定时器用于其他目的。在删除通用定时器句柄之前,请通过 gptimer_disable() 禁用定时器,或者通过 gptimer_enable() 确认定时器尚未使能。

创建分辨率为 1 MHz 的通用定时器句柄

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

设置和获取计数值

创建通用定时器时,内部计数器将默认重置为零。计数值可以通过 gptimer_set_raw_count() 异步更新。最大计数值取决于硬件定时器的位宽,这也会在 SOC 宏 SOC_TIMER_GROUP_COUNTER_BIT_WIDTH 中有所反映。当更新活动定时器的原始计数值时,定时器将立即从新值开始计数。

计数值可以随时通过 gptimer_get_raw_count() 获取。

设置警报动作

对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 gptimer_alarm_config_t 的不同参数:

要使警报配置生效,需要调用 gptimer_set_alarm_action()。特别是当 gptimer_alarm_config_t 设置为 NULL 时,报警功能将被禁用。

备注

如果警报值已设置且定时器超过该值,则会立即触发警报。

注册事件回调函数

定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 gptimer_register_event_callbacks() 将函数挂载到中断服务例程 (ISR)。gptimer_event_callbacks_t 中列出了所有支持的事件回调函数:

  • gptimer_event_callbacks_t::on_alarm 设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有 ISR 后缀的 FreeRTOS API)。函数原型在 gptimer_alarm_cb_t 中有所声明。

您也可以通过参数 user_data 将自己的上下文保存到 gptimer_register_event_callbacks() 中。用户数据将直接传递给回调函数。

此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在 gptimer_enable() 之前调用这一函数,否则将返回 ESP_ERR_INVALID_STATE 错误。了解详细信息,请查看章节 使能和禁用定时器

使能和禁用定时器

在对定时器进行 IO 控制之前,需要先调用 gptimer_enable() 使能定时器。此函数功能如下:

  • 此函数将把定时器驱动程序的状态从 init 切换为 enable

  • 如果 gptimer_register_event_callbacks() 已经延迟安装中断服务,此函数将使能中断服务。

  • 如果选择了特定的时钟源(例如 APB 时钟),此函数将获取适当的电源管理锁。了解更多信息,请查看章节 电源管理

调用 gptimer_disable() 会进行相反的操作,即将定时器驱动程序恢复到 init 状态,禁用中断服务并释放电源管理锁。

启动和停止定时器

启动和停止是定时器的基本 IO 操作。调用 gptimer_start() 可以使内部计数器开始工作,而 gptimer_stop() 可以使计数器停止工作。下文说明了如何在存在或不存在警报事件的情况下启动定时器。 调用 gptimer_start() 将使驱动程序状态从 enable 转换为 run, 反之亦然。您需要确保 start 和 stop 函数成对使用,否则,函数可能返回 ESP_ERR_INVALID_STATE

将定时器作为挂钟启动

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

触发周期性事件

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 the 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_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));

触发一次性事件

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 the 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_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));

警报值动态更新

通过更改 gptimer_alarm_event_data_t::alarm_value,可以在 ISR 程序回调中动态更新警报值。警报值将在回调函数返回后更新。

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 the 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_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));

电源管理

有些电源管理的策略会在某些时刻关闭时钟源,或者改变时钟源的频率,以求降低功耗。比如在启用 DFS 后, APB 时钟源会降低频率。如果浅睡眠(light sleep) 模式也被开启, PLL 和 XTAL 时钟都会被默认关闭,从而导致 GPTimer 的计时不准确。

驱动程序会根据具体的时钟源选择,通过创建不同的电源锁来避免上述情况的发生。驱动会在 gptimer_enable() 函数中增加电源锁的引用计数,并在 gptimer_disable() 函数中减少电源锁的引用计数,从而保证了在 gptimer_enable()gptimer_disable() 之间, GPTimer 的时钟源始处于稳定工作的状态。

IRAM 安全

默认情况下,当 cache 因写入或擦除 flash 等原因而被禁用时,通用定时器的中断服务将会延迟,造成警报中断无法及时执行。在实时应用程序中通常需要避免这一情况发生。

调用 Kconfig 选项 CONFIG_GPTIMER_ISR_IRAM_SAFE 可实现如下功能:

  • 即使禁用 cache 也可使能正在运行的中断

  • 将 ISR 使用的所有函数放入 IRAM 2

  • 将驱动程序对象放入 DRAM(以防意外映射到 PSRAM)

这将允许中断在 cache 禁用时运行,但会增加 IRAM 使用量。

调用另一 Kconfig 选项 CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM 也可将常用的 IO 控制功能放入 IRAM,以便这些函数在 cache 禁用时也能执行。常用的 IO 控制功能如下:

线程安全

驱动提供的所有 API 都是线程安全的,这意味着您可以从不同的 RTOS 任务中调用这些函数,而无需额外的互斥锁去保护。以下这些函数还被允许在中断上下文中运行。

Kconfig 选项

应用示例

API 参考

Header File

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.

备注

The newly created timer is put in the “init” state.

参数
  • config[in] GPTimer configuration

  • ret_timer[out] Returned timer handle

返回

  • 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

esp_err_t gptimer_del_timer(gptimer_handle_t timer)

Delete the GPTimer handle.

备注

A timer must be in the “init” state before it can be deleted.

参数

timer[in] Timer handle created by gptimer_new_timer()

返回

  • 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 is not in init state

  • ESP_FAIL: Delete GPTimer failed because of other error

esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value)

Set GPTimer raw count value.

备注

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

备注

This function is allowed to run within ISR context

备注

If CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is enabled, this function will be placed in the IRAM by linker, makes it possible to execute even when the Flash Cache is disabled.

参数
  • timer[in] Timer handle created by gptimer_new_timer()

  • value[in] Count value to be set

返回

  • 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

esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value)

Get GPTimer raw count value.

备注

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

备注

This function is allowed to run within ISR context

备注

If CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is enabled, this function will be placed in the IRAM by linker, makes it possible to execute even when the Flash Cache is disabled.

参数
  • timer[in] Timer handle created by gptimer_new_timer()

  • value[out] Returned GPTimer count value

返回

  • 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

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

Set callbacks for GPTimer.

备注

User registered callbacks are expected to be runnable within ISR context

备注

The first call to this function needs to be before the call to gptimer_enable

备注

User can deregister a previously registered callback by calling this function and setting the callback member in the cbs structure to NULL.

参数
  • timer[in] Timer handle created by gptimer_new_timer()

  • cbs[in] Group of callback functions

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

返回

  • ESP_OK: Set event callbacks successfully

  • ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Set event callbacks failed because the timer is not in init state

  • ESP_FAIL: Set event callbacks failed because of other error

esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)

Set alarm event actions for GPTimer.

备注

This function is allowed to run within ISR context, so that user can set new alarm action immediately in the ISR callback.

备注

If CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is enabled, this function will be placed in the IRAM by linker, makes it possible to execute even when the Flash Cache is disabled.

参数
  • timer[in] Timer handle created by gptimer_new_timer()

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

返回

  • 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

esp_err_t gptimer_enable(gptimer_handle_t timer)

Enable GPTimer.

备注

This function will transit the timer state from “init” to “enable”.

备注

This function will enable the interrupt service, if it’s lazy installed in gptimer_register_event_callbacks.

备注

This function will acquire a PM lock, if a specific source clock (e.g. APB) is selected in the gptimer_config_t, while CONFIG_PM_ENABLE is enabled.

备注

Enable a timer doesn’t mean to start it. See also gptimer_start() for how to make the timer start counting.

参数

timer[in] Timer handle created by gptimer_new_timer()

返回

  • ESP_OK: Enable GPTimer successfully

  • ESP_ERR_INVALID_ARG: Enable GPTimer failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Enable GPTimer failed because the timer is already enabled

  • ESP_FAIL: Enable GPTimer failed because of other error

esp_err_t gptimer_disable(gptimer_handle_t timer)

Disable GPTimer.

备注

This function will transit the timer state from “enable” to “init”.

备注

This function will disable the interrupt service if it’s installed.

备注

This function will release the PM lock if it’s acquired in the gptimer_enable.

备注

Disable a timer doesn’t mean to stop it. See also gptimer_stop for how to make the timer stop counting.

参数

timer[in] Timer handle created by gptimer_new_timer()

返回

  • ESP_OK: Disable GPTimer successfully

  • ESP_ERR_INVALID_ARG: Disable GPTimer failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Disable GPTimer failed because the timer is not enabled yet

  • ESP_FAIL: Disable GPTimer failed because of other error

esp_err_t gptimer_start(gptimer_handle_t timer)

Start GPTimer (internal counter starts counting)

备注

This function will transit the timer state from “enable” to “run”.

备注

This function is allowed to run within ISR context

备注

If CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is enabled, this function will be placed in the IRAM by linker, makes it possible to execute even when the Flash Cache is disabled.

参数

timer[in] Timer handle created by gptimer_new_timer()

返回

  • 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 enabled or is already in running

  • ESP_FAIL: Start GPTimer failed because of other error

esp_err_t gptimer_stop(gptimer_handle_t timer)

Stop GPTimer (internal counter stops counting)

备注

This function will transit the timer state from “run” to “enable”.

备注

This function is allowed to run within ISR context

备注

If CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is enabled, this function will be placed in the IRAM by linker, makes it possible to execute even when the Flash Cache is disabled.

参数

timer[in] Timer handle created by gptimer_new_timer()

返回

  • 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 running.

  • ESP_FAIL: Stop GPTimer failed because of other error

Structures

struct gptimer_alarm_event_data_t

GPTimer alarm 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.

备注

The callbacks are all running under ISR environment

备注

When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.

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

int intr_priority

GPTimer interrupt priority, if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3)

uint32_t intr_shared

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

struct gptimer_config_t::[anonymous] flags

GPTimer config flags

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

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

struct gptimer_alarm_config_t::[anonymous] flags

Alarm config flags

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.

Param timer

[in] Timer handle created by gptimer_new_timer()

Param edata

[in] Alarm event data, fed by driver

Param user_ctx

[in] User data, passed from gptimer_register_event_callbacks()

Return

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

Header File

Type Definitions

typedef soc_periph_gptimer_clk_src_t gptimer_clock_source_t

GPTimer clock source.

备注

User should select the clock source based on the power and resolution requirement

Enumerations

enum gptimer_count_direction_t

GPTimer count direction.

Values:

enumerator GPTIMER_COUNT_DOWN

Decrease count value

enumerator GPTIMER_COUNT_UP

Increase count value

1

不同 ESP 芯片系列的通用定时器实例数量可能不同。了解详细信息,请参考《ESP32-S2 技术参考手册》 > 章节定时器组 (TIMG) [PDF]。驱动程序不会禁止您申请更多的定时器,但是当所有可用的硬件资源用完时将会返回错误。在分配资源时,请务必检查返回值(例如 gptimer_new_timer())。

2

gptimer_event_callbacks_t::on_alarm 回调函数和这一函数调用的函数也需放在 IRAM 中,请自行处理。