高分辨率定时器(ESP 定时器)
概述
尽管 FreeRTOS 提供软件定时器,但 FreeRTOS 软件定时器存在一定局限性:
- 最大分辨率等于 RTOS 滴答周期 
- 定时器回调函数从低优先级的定时器服务(即守护进程)任务中分发。该任务可能会被其他任务抢占,导致精度和准确性下降。 
硬件定时器虽不受上述限制,但使用不便。例如,应用组件可能需要在特定的未来时间触发计时器事件,但硬件定时器通常只有一个“compare(比较)”值用于中断生成。为提升使用的便利性,应在硬件定时器的基础上构建某种机制来管理待处理事件列表,确保在相应的硬件中断发生时调度回调。
esp_timer API 集支持单次定时器和周期定时器、微秒级的时间分辨率、以及 52 位范围。
esp_timer 内部使用 52 位硬件定时器,具体硬件实现取决于芯片型号,如 ESP32-C6 使用的是 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 调度定时器处理完毕后进行。
ETM 事件
esp_timer 的构建基于 systimer 硬件定时器,能够产生报警事件并与 ETM 模块交互。调用函数 esp_timer_new_etm_alarm_event() 以获取相应的 ETM 事件句柄。
如需了解如何将 ETM 事件连接到相应 ETM 通道,请参阅 ETM。
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_usmicroseconds. If the given timer is a periodic timer, the timer is restarted immediately with a new period of- timeout_usmicroseconds.- 参数
- 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