高分辨率定时器(ESP 定时器)

[English]

概述

尽管 FreeRTOS 提供软件定时器,但 FreeRTOS 软件定时器存在一定局限性:

  • 最大分辨率等于 RTOS 滴答周期

  • 定时器回调函数从低优先级的定时器服务(即守护进程)任务中分发。该任务可能会被其他任务抢占,导致精度和准确性下降。

硬件定时器虽不受上述限制,但使用不便。例如,应用组件可能需要在特定的未来时间触发计时器事件,但硬件定时器通常只有一个“compare(比较)”值用于中断生成。为提升使用的便利性,应在硬件定时器的基础上构建某种机制来管理待处理事件列表,确保在相应的硬件中断发生时调度回调。

配置 CONFIG_ESP_TIMER_INTERRUPT_LEVEL 选项,设置硬件定时器中断的优先级(可设置为 1、2 或 3 级),提高定时器中断的优先级可以减少由中断延迟引起的定时器处理延迟。

esp_timer API 集支持单次定时器和周期定时器、微秒级的时间分辨率、以及 64 位范围。

esp_timer 内部使用 64 位硬件定时器,具体硬件实现取决于芯片型号,如 ESP32 使用的是 LAC 定时器。

定时器回调可通过以下两种方式调度:

使用 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_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

  • components/esp_timer/include/esp_timer.h

  • This header file can be included with:

    #include "esp_timer.h"
    
  • This header file is a part of the API provided by the esp_timer component. To declare that your component depends on esp_timer, add the following to your CMakeLists.txt:

    REQUIRES esp_timer
    

    or

    PRIV_REQUIRES esp_timer
    

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 of timeout_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.

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

Enumerations

enum esp_timer_dispatch_t

Method for dispatching timer callback.

Values:

enumerator ESP_TIMER_TASK

Callback is called from timer task.

enumerator ESP_TIMER_MAX

Count of the methods for dispatching timer callback.