高分辨率定时器(ESP 定时器)
概述
尽管 FreeRTOS 提供软件定时器,但 FreeRTOS 软件定时器存在一定局限性:
最大分辨率等于 RTOS 滴答周期
定时器回调函数从低优先级的定时器服务(即守护进程)任务中分发。该任务可能会被其他任务抢占,导致精度和准确性下降。
硬件定时器虽不受上述限制,但使用不便。例如,应用组件可能需要在特定的未来时间触发计时器事件,但硬件定时器通常只有一个“compare(比较)”值用于中断生成。为提升使用的便利性,应在硬件定时器的基础上构建某种机制来管理待处理事件列表,确保在相应的硬件中断发生时调度回调。
esp_timer
API 集支持单次定时器和周期定时器、微秒级的时间分辨率、以及 52 位范围。
esp_timer
内部使用 52 位硬件定时器,具体硬件实现取决于芯片型号,如 ESP32-C2 使用的是 SYSTIMER。
定时器回调可通过以下两种方式调度:
ESP_TIMER_TASK
。ESP_TIMER_ISR
。仅当 CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD 被启用时可用(默认为禁用)。
使用 ESP_TIMER_TASK
这一途径时,定时器回调函数是从高优先级的 esp_timer
任务中调度的。由于所有回调函数都是从同一个任务中调度,因此建议在回调函数本身中仅执行最小化的工作,如使用队列向低优先级任务发布事件。
如果有优先级高于 esp_timer
的其他任务正在运行,则回调调度将延迟,直至 esp_timer
能够运行。例如,执行 SPI flash 操作时便会发生此类情况。
使用 ESP_TIMER_ISR
这一途径时,定时器回调由定时器中断处理程序直接调度。对旨在降低延迟的简单回调,建议使用此途径。
创建、启动定时器并调度回调需要一些时间。因此,单次 esp_timer
的超时值存在最小限制。若调用 esp_timer_start_once()
时设置的超时值小于 20 us,回调函数仍会在大约 20 微秒后被调度。
周期 esp_timer
将最小周期限制为 50 us。周期小于 50 us 的软件定时器会占用大部分 CPU 时间,因此它们并不实用。如需使用小周期定时器,请考虑使用专用硬件外设或 DMA 功能。
使用 esp_timer
API
单个定时器由 esp_timer_handle_t
类型表示。每个定时器都有与之关联的回调函数,定时器超时时便会从 esp_timer
任务中调用此函数。
要创建定时器,请调用函数
esp_timer_create()
。要删除定时器,请调用函数
esp_timer_delete()
。
定时器可以以单次模式和周期模式启动。
要以单次模式启动定时器,请调用函数
esp_timer_start_once()
,传递应在多久后调用回调的时间间隔。调用回调时,定时器被视为停止工作。要以周期模式启动定时器,请调用函数
esp_timer_start_periodic()
,传递应调用回调的周期。计时器持续运行,直到调用函数esp_timer_stop()
。
注意,当调用函数 esp_timer_start_once()
或 esp_timer_start_periodic()
时,应确保定时器处于非运行状态。因此,要重启运行中的定时器,需首先调用函数 esp_timer_stop()
停止定时器,然后调用 esp_timer_start_once()
或 esp_timer_start_periodic()
来启动定时器。
回调函数
备注
回调函数应尽可能简略,避免影响所有定时器。
若定时器回调由 ESP_TIMER_ISR
方式处理,则该回调不应调用切换上下文的 portYIELD_FROM_ISR()
,而应调用函数 esp_timer_isr_dispatch_need_yield()
。如果系统有此需求,上下文切换将在所有 ISR 调度定时器处理完毕后进行。
Light-sleep 模式下的 esp_timer
在 Light-sleep 期间, esp_timer
计数器停止工作,并且不调用回调函数,而是由 RTC 计数器负责计算时间。唤醒后,系统得到两个计数器间的差值,并调用函数推进 esp_timer
计数器计数。计数器计数被推进后,系统开始调用 Light-sleep 期间未被调用的回调。回调数量取决于 Light-sleep 模式持续时长和定时器周期,这可能会导致某些队列溢出。以上情况仅适用于周期性定时器,单次定时器只会被调用一次。
通过在 Light-sleep 模式前调用函数 esp_timer_stop()
可以改变上述行为。但在某些情况下这可能并不方便。比起使用停止函数,在 esp_timer_create()
中使用 skip_unhandled_events
选项将更加便利。 当 skip_unhandled_events
为真时,如果一个周期性定时器在 Light-sleep 期间超时一次或多次,那么在唤醒时只有一个回调会被调用。
使用带有自动 Light-sleep 的 skip_unhandled_events
选项(请参阅 电源管理),有助于在系统处于 Light-sleep 状态时降低功耗。 Light-sleep 的持续时间也在一定程度上由下一个事件发生的时间确定。具有 skip_unhandled_events`
选项的定时器不会唤醒系统。
处理回调
设计 esp_timer
是为了使定时器实现高分辨率和低延迟,并具备处理延迟事件的能力。如果定时器延迟,回调将被尽快调用,不会丢失。在最糟的情况下,周期性定时器可能超过一个周期还没有被处理,此时回调将被陆续调用,而不会等待设定的周期。这会给某些应用带来负面影响,为避免此类情况发生,特引入 skip_unhandled_events
选项。设置该选项后,即使一个周期性定时器多次超时且无法调用回调,该定时器在恢复处理能力后,仍将只产生一个回调事件。
获取当前时间
esp_timer
还提供了一个便捷函数 esp_timer_get_time()
以获取自启动以来经过的时间,可精确到微秒。这个函数通常会在 app_main
函数即将被调用前,返回自 esp_timer
启动以来的微秒数。
不同于 gettimeofday
函数,esp_timer_get_time()
返回的值:
芯片从 Deep-sleep 中唤醒后,从零开始
没有应用时区或 DST 调整
应用示例
esp_timer
API 的详细用法可参阅 system/esp_timer。
API 参考
Header File
Functions
-
esp_err_t esp_timer_early_init(void)
Minimal initialization of esp_timer.
This function can be called very early in startup process, after this call only esp_timer_get_time function can be used.
备注
This function is called from startup code. Applications do not need to call this function before using other esp_timer APIs.
- 返回
ESP_OK on success
-
esp_err_t esp_timer_init(void)
Initialize esp_timer library.
This function will be called from startup code on every core if CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY is enabled, It allocates the timer ISR on MULTIPLE cores and creates the timer task which can be run on any core.
备注
This function is called from startup code. Applications do not need to call this function before using other esp_timer APIs. Before calling this function, esp_timer_early_init must be called by the startup code.
- 返回
ESP_OK on success
ESP_ERR_NO_MEM if allocation has failed
ESP_ERR_INVALID_STATE if already initialized
other errors from interrupt allocator
-
esp_err_t esp_timer_deinit(void)
De-initialize esp_timer library.
备注
Normally this function should not be called from applications
- 返回
ESP_OK on success
ESP_ERR_INVALID_STATE if not yet initialized
-
esp_err_t esp_timer_create(const esp_timer_create_args_t *create_args, esp_timer_handle_t *out_handle)
Create an esp_timer instance.
备注
When done using the timer, delete it with esp_timer_delete function.
- 参数
create_args -- Pointer to a structure with timer creation arguments. Not saved by the library, can be allocated on the stack.
out_handle -- [out] Output, pointer to esp_timer_handle_t variable which will hold the created timer handle.
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if some of the create_args are not valid
ESP_ERR_INVALID_STATE if esp_timer library is not initialized yet
ESP_ERR_NO_MEM if memory allocation fails
-
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us)
Start one-shot timer.
Timer should not be running when this function is called.
- 参数
timer -- timer handle created using esp_timer_create
timeout_us -- timer timeout, in microseconds relative to the current moment
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if the handle is invalid
ESP_ERR_INVALID_STATE if the timer is already running
-
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period)
Start a periodic timer.
Timer should not be running when this function is called. This function will start the timer which will trigger every 'period' microseconds.
- 参数
timer -- timer handle created using esp_timer_create
period -- timer period, in microseconds
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if the handle is invalid
ESP_ERR_INVALID_STATE if the timer is already running
-
esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us)
Restart a currently running timer.
If the given timer is a one-shot timer, the timer is restarted immediately and will timeout once in
timeout_us
microseconds. If the given timer is a periodic timer, the timer is restarted immediately with a new period oftimeout_us
microseconds.- 参数
timer -- timer Handle created using esp_timer_create
timeout_us -- Timeout, in microseconds relative to the current time. In case of a periodic timer, also represents the new period.
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if the handle is invalid
ESP_ERR_INVALID_STATE if the timer is not running
-
esp_err_t esp_timer_stop(esp_timer_handle_t timer)
Stop the timer.
This function stops the timer previously started using esp_timer_start_once or esp_timer_start_periodic.
- 参数
timer -- timer handle created using esp_timer_create
- 返回
ESP_OK on success
ESP_ERR_INVALID_STATE if the timer is not running
-
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
Delete an esp_timer instance.
The timer must be stopped before deleting. A one-shot timer which has expired does not need to be stopped.
- 参数
timer -- timer handle allocated using esp_timer_create
- 返回
ESP_OK on success
ESP_ERR_INVALID_STATE if the timer is running
-
int64_t esp_timer_get_time(void)
Get time in microseconds since boot.
- 返回
number of microseconds since underlying timer has been started
-
int64_t esp_timer_get_next_alarm(void)
Get the timestamp when the next timeout is expected to occur.
- 返回
Timestamp of the nearest timer event, in microseconds. The timebase is the same as for the values returned by esp_timer_get_time.
-
int64_t esp_timer_get_next_alarm_for_wake_up(void)
Get the timestamp when the next timeout is expected to occur skipping those which have skip_unhandled_events flag.
- 返回
Timestamp of the nearest timer event, in microseconds. The timebase is the same as for the values returned by esp_timer_get_time.
-
esp_err_t esp_timer_get_period(esp_timer_handle_t timer, uint64_t *period)
Get the period of a timer.
This function fetches the timeout period of a timer.
备注
The timeout period is the time interval with which a timer restarts after expiry. For one-shot timers, the period is 0 as there is no periodicity associated with such timers.
- 参数
timer -- timer handle allocated using esp_timer_create
period -- memory to store the timer period value in microseconds
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if the arguments are invalid
-
esp_err_t esp_timer_get_expiry_time(esp_timer_handle_t timer, uint64_t *expiry)
Get the expiry time of a one-shot timer.
This function fetches the expiry time of a one-shot timer.
备注
This API returns a valid expiry time only for a one-shot timer. It returns an error if the timer handle passed to the function is for a periodic timer.
- 参数
timer -- timer handle allocated using esp_timer_create
expiry -- memory to store the timeout value in microseconds
- 返回
ESP_OK on success
ESP_ERR_INVALID_ARG if the arguments are invalid
ESP_ERR_NOT_SUPPORTED if the timer type is periodic
-
esp_err_t esp_timer_dump(FILE *stream)
Dump the list of timers to a stream.
If CONFIG_ESP_TIMER_PROFILING option is enabled, this prints the list of all the existing timers. Otherwise, only the list active timers is printed.
The format is:
name period alarm times_armed times_triggered total_callback_run_time
where:
name — timer name (if CONFIG_ESP_TIMER_PROFILING is defined), or timer pointer period — period of timer, in microseconds, or 0 for one-shot timer alarm - time of the next alarm, in microseconds since boot, or 0 if the timer is not started
The following fields are printed if CONFIG_ESP_TIMER_PROFILING is defined:
times_armed — number of times the timer was armed via esp_timer_start_X times_triggered - number of times the callback was called total_callback_run_time - total time taken by callback to execute, across all calls
- 参数
stream -- stream (such as stdout) to dump the information to
- 返回
ESP_OK on success
ESP_ERR_NO_MEM if can not allocate temporary buffer for the output
-
void esp_timer_isr_dispatch_need_yield(void)
Requests a context switch from a timer callback function.
This only works for a timer that has an ISR dispatch method. The context switch will be called after all ISR dispatch timers have been processed.
-
bool esp_timer_is_active(esp_timer_handle_t timer)
Returns status of a timer, active or not.
This function is used to identify if the timer is still active or not.
- 参数
timer -- timer handle created using esp_timer_create
- 返回
1 if timer is still active
0 if timer is not active.
-
esp_err_t esp_timer_new_etm_alarm_event(esp_etm_event_handle_t *out_event)
Get the ETM event handle of esp_timer underlying alarm event.
备注
The created ETM event object can be deleted later by calling
esp_etm_del_event
备注
The ETM event is generated by the underlying hardware — systimer, therefore, if the esp_timer is not clocked by systimer, then no ETM event will be generated.
- 参数
out_event -- [out] Returned ETM event handle
- 返回
ESP_OK Success
ESP_ERR_INVALID_ARG Parameter error
Structures
-
struct esp_timer_create_args_t
Timer configuration passed to esp_timer_create.
Public Members
-
esp_timer_cb_t callback
Function to call when timer expires.
-
void *arg
Argument to pass to the callback.
-
esp_timer_dispatch_t dispatch_method
Call the callback from task or from ISR.
-
const char *name
Timer name, used in esp_timer_dump function.
-
bool skip_unhandled_events
Skip unhandled events for periodic timers.
-
esp_timer_cb_t callback
Type Definitions
-
typedef struct esp_timer *esp_timer_handle_t
Opaque type representing a single esp_timer.
-
typedef void (*esp_timer_cb_t)(void *arg)
Timer callback function type.
- Param arg
pointer to opaque user-specific data