ESP 定时器(高分辨率定时器)
本文介绍了 ESP-IDF 的 ESP 定时器功能,章节目录如下:
概述
使用 ESP 定时器可以创建软件定时器并在超时时调用其回调函数(分发回调函数)。尤其适用于当软件需要执行延迟操作或周期性操作的场景,例如:延迟启动设备或停止设备、周期性采样传感器数据等。
ESP 定时器可以简化一些操作,例如:管理多个定时器、分发回调函数、处理时钟频率变化(如果启用了动态调频),以及从浅睡眠中唤醒后保持准确的时间。
如果应用场景对实时性能要求较高(例如需要生成波形),或要求定时器分辨率可配置,建议使用 GPTimer。此外,GPTimer 还提供一些其他功能,例如事件捕获。
FreeRTOS 有其单独的软件定时器,但分辨率比 ESP 定时器低得多,优势在于 FreeRTOS 定时器有更好的移植性,因为它不依赖于 ESP-IDF。详情请参阅 FreeRTOS 定时器。
功能与概念
ESP 定时器 API 提供:
一次性和周期性定时器
多种分发回调函数的方法
处理过期回调函数
计数器位宽:64 位
时间分辨率:1 us
一次性和周期性定时器
一次性定时器到期时仅调用一次回调函数便停止运行。一次性定时器适用于单次延迟操作,例如在指定时间间隔后关闭设备或读取传感器。
周期性定时器在到期时会调用回调函数并自动重启,回调函数会在指定时间间隔内被反复调用,直至手动暂停周期性定时器。周期性定时器适用于重复操作,例如采样传感器数据、更新显示信息或生成波形。
分发回调函数的方法
定时器回调函数可以使用以下方法进行分发:
任务分发法(默认):
从单个高优先级 ESP 定时器任务中分发定时器回调函数(esp_timer 任务(由 ISR 通知)> 回调)。
适用于对时序要求不高的定时器回调函数。
中断分发法 (
ESP_TIMER_ISR
):直接从中断处理程序分发定时器回调函数(ISR > 回调)。
适合运行时间仅为几微秒的简单、低延迟定时器回调函数。
能确保事件和执行回调之间的延迟较短。
不受其他活动任务影响。
任务分发细节
在 ESP 定时器任务中,回调函数是串行执行的,若多个超时同时发生,执行前一项回调函数会延迟后续回调函数的执行。因此,建议尽量保持回调函数简短。如果需要回调函数执行更多工作,应使用 FreeRTOS 原语(如队列和信号量)将工作推迟到低优先级任务中执行。
如果有其他更高优先级的 FreeRTOS 任务正在运行(例如 SPI flash 操作),则回调函数的分发将被延迟,直到 ESP 定时器任务有机会再次运行。
为了确保任务的可预测性和及时执行,回调函数不应进行阻塞(等待资源)或让步(放弃控制)操作,否则将中断回调函数的串行执行。
中断分发细节
使用中断分发法的定时器,其回调由中断处理程序执行。由于中断可以抢占所有任务,中断分发法带来的延迟较低。中断分发的定时器回调函数不应尝试阻塞,也不应尝试通过 portYIELD_FROM_ISR()
触发上下文切换,而应使用 esp_timer_isr_dispatch_need_yield()
函数。上下文切换将在处理完所有使用 ISR 分发法的定时器后进行。
使用中断分发的定时器时,应避免使用标准的日志记录或调试方法(例如 printf
)。若想调试应用程序或在控制台中显示某些信息,应使用 ESP-IDF 日志宏,例如 ESP_DRAM_LOGI
和 ESP_EARLY_LOGI
等。这些宏专为在各种上下文(包括中断服务程序)中工作而设计。
获取当前时间
可以使用便捷函数 esp_timer_get_time()
获取自 ESP 定时器初始化以来经过的时间。在调用 app_main
函数之前不久,ESP 定时器会开始初始化。该便捷函数速度极快,没有能引入延迟或冲突的锁机制,因此可用于细粒度定时,在任务和 ISR 例程中的精度为 1 μs。
与 gettimeofday()
函数不同,esp_timer_get_time()
具有以下特点:
从深睡眠状态中唤醒后,初始化定时器将从零开始。
返回值没有时区设置或夏令时调整。
系统集成
本节主要介绍如何优化 ESP 定时器的操作并将其与其他 ESP-IDF 功能进行集成。
超时值限制
分发回调不可能是瞬时的,使用 ESP 定时器创建的一次性和周期性定时器具有超时值限制。由于这些限制取决于多个因素,所以无法进行精确估计。
例如,ESP32 以 240 MHz 的频率运行并使用任务分发法,其最小超时值大约如下:
一次性定时器:~20 μs
如果调用
esp_timer_start_once()
,这是系统能够分发回调函数的最小超时值。
周期性定时器:~50 μs
具有较小超时值的周期性软件定时器将消耗大部分 CPU 时间,因此不实用。
CPU 频率越低,最小超时值就越高。一般来说,如果所需的超时值在几十微秒的范围内,则应用程序需要经过彻底测试才能确保稳定运行。
如果最小超时值稍稍超过要求,可以考虑使用中断分发法。
若需要更小的超时值,例如生成或接收波形、进行位操作时,ESP 定时器的分辨率可能不能满足要求。此时建议使用专用外设,例如 GPTimer 或 RMT,以及使用它们的 DMA 功能(如果可用)。
睡眠模式注意事项
如果启动了定时器,并且在等待时间内没有执行其他任务,则可以将芯片置于睡眠状态以优化功耗。
可以通过以下方式进入不同睡眠模式:
自动睡眠 由 电源管理 API 提供:如果没有正在执行的任务,芯片会自动进入浅睡眠状态,并在适当时间自动唤醒,以便 ESP 定时器分发待处理的回调函数。
手动睡眠 由 睡眠模式 API 提供:无论是否正在执行其他任务,都可以将芯片置于睡眠状态。
若手动设置睡眠状态,则可以选择以下睡眠模式:
Deep-sleep 模式:ESP 定时器停用
从深睡眠状态中唤醒时,应用程序即刻重新启动,因此该模式不适用于连续的 ESP 定时器操作。但如果不需要定时器在唤醒后持续运行,则可进入深睡眠状态。
Light-sleep 模式:ESP 定时器暂停
在浅睡眠状态下,ESP 定时器的计数器和回调函数会被暂停。RTC 定时器可保持系统时间。一旦芯片被唤醒,ESP 定时器的计数器会自动为系统增加睡眠期间的时长,之后时间保持和回调函数执行将恢复。
此时,ESP 定时器将尝试分发所有未处理的回调函数(如果有的话),可能会导致 ESP 定时器回调执行队列的溢出。某些应用中不应出现此类行为,为避免这种情况,可参阅 在 Light-sleep 模式下处理回调函数。
FreeRTOS 定时器
尽管 FreeRTOS 提供了 软件定时器,但它们有以下限制:
FreeRTOS 定时器的分辨率受 tick 频率 的限制,该频率通常在 100 到 1000 Hz 之间。
定时器回调函数由低优先级定时器任务分发,该任务可能会被其他任务抢占,导致定时器精度和准确度下降。
但 FreeRTOS 定时器是可移植的(不依赖于 ESP-IDF),且不会从 ISR 中分发,因此具有确定性。
使用方法
在设置 ESP-IDF 项目时,请确保:
在
CMakeLists.txt
中添加所需的组件依赖项esp_timer
。在
.c
文件中包含所需的头文件esp_timer.h
。(可选)设置 Kconfig 选项。详见 Kconfig 选项 > ESP 定时器(高分辨率定时器)
一般程序
创建、启动、暂停和删除定时器的一般程序如下:
创建定时器
使用类型
esp_timer_handle_t
定义定时器句柄通过定义结构体
esp_timer_create_args_t
(包括回调函数)来设置定时器配置参数。备注
建议尽量使回调函数保持简短,避免延迟其他回调函数的执行。
调用函数
esp_timer_create()
来创建定时器。
根据需求启动一次性或周期性的定时器
调用函数
esp_timer_start_once()
,启动一次性定时器。调用函数
esp_timer_start_periodic()
,启动周期性定时器。在调用函数esp_timer_stop()
显式暂停定时器前,该周期性定时器将持续运行。
备注
执行启动函数前,请确保定时器未在运行。如果定时器正在运行,请先调用
esp_timer_restart()
,或是调用esp_timer_stop()
暂停定时器,然后再使用上述启动函数。暂停定时器
调用函数
esp_timer_stop()
,可暂停运行中的定时器。
删除定时器
使用函数
esp_timer_delete()
,可删除不需要的定时器以释放内存。
使用中断分发法
在可用的 分发回调函数的方法 中,如果选择中断分发法,请按以下步骤操作:
设置 Kconfig 选项
创建定时器
通过定义结构体
esp_timer_create_args_t
来设置定时器配置参数:
const esp_timer_create_args_t timer = { ... , .dispatch_method = ESP_TIMER_ISR, ... };
调用函数
esp_timer_create()
来创建定时器。
更多步骤请参阅 一般程序。
在 Light-sleep 模式下处理回调函数
浅睡眠状态下,既能快速唤醒以执行特定操作,又能节省功耗。要想结合 Light-sleep 模式使用 ESP 定时器,请参阅 睡眠模式 API。
在浅睡眠状态下,为控制未处理的回调函数,并且避免唤醒时 ESP 定时器回调执行队列的溢出,请执行以下任一操作:
首先要防止调用回调函数:在进入浅睡眠状态前,请使用函数
esp_timer_stop()
暂停定时器。若出于某种原因不希望调用停止函数,请使用选项
esp_timer_create_args_t::skip_unhandled_events
。此时,若周期性定时器在浅睡眠状态下到期一次或多次,则唤醒时只执行一次回调函数。
调试定时器
使用函数 esp_timer_dump()
,可转储所有定时器或仅运行中的定时器的相关信息:如定时器的参数、定时器启动次数、触发次数、跳过次数以及执行定时器回调函数所需的时间,这些信息能够帮助调试定时器。
请按照以下步骤调试定时器:
设置 Kconfig 选项以获取更详细的输出:
备注
启用此选项会增加代码大小和堆内存使用量。
调用函数
esp_timer_dump()
,在代码中必要的位置打印信息并用于调试定时器。结束调试后,考虑禁用 CONFIG_ESP_TIMER_PROFILING。
故障排除
回调函数分发时间不稳定
如果多次分发相同的回调函数时响应时间变化较大,请尝试下列方法,使分发时间趋于稳定:
使用 Kconfig 选项 CONFIG_ESP_TIMER_TASK_AFFINITY,将 esp_timer 安装到负载较轻的 CPU 核上运行。
分发回调函数时延迟显著
若分发回调函数需要相当长的时间,问题可能出在回调函数本身。更准确地说,由于所有回调函数都在单个 esp_timer 任务中逐个处理,延迟可能是由队列中较早的其他回调函数引起的。
因此,要确保应用程序中的所有回调函数都能快速独立地执行,并且没有任何阻塞操作。
唤醒后重复分发回调函数
从睡眠模式中唤醒后,若回调函数重复执行,请参阅 在 Light-sleep 模式下处理回调函数。
在分发回调函数时栈溢出
如果在执行回调函数时遇到栈溢出的错误,请考虑减少回调函数内的栈使用量;或者,尝试通过调整 CONFIG_ESP_TIMER_TASK_STACK_SIZE 来增加 ESP 定时器任务栈的大小。
应用示例
system/esp_timer 创建并启动一次性及周期性的软件定时器,展示了如何结合 Light-sleep 模式使用定时器,然后停止并删除定时器。
API 参考
Header File
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 onesp_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 Kconfig option
CONFIG_ESP_TIMER_ISR_AFFINITY
is set toNO_AFFINITY
, 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 timer no longer needed, delete it using esp_timer_delete().
- 参数
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 that holds 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 a one-shot timer.
Timer represented by
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 represented by
timer
should not be running when this function is called. This function starts the timer which will trigger everyperiod
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.
Type of
timer
Action
One-shot timer
Restarted immediately and times out once in
timeout_us
microsecondsPeriodic timer
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 a running 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 created 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 the initialization of ESP Timer
-
int64_t esp_timer_get_next_alarm(void)
Get the timestamp of the next expected timeout.
- 返回
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 of the next expected timeout excluding those timers that should not interrupt light sleep (such timers have esp_timer_create_args_t::skip_unhandled_events enabled)
- 返回
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. For a one-shot timer, the timeout period will be 0.
- 参数
timer -- timer handle created 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.
备注
Passing the timer handle of a periodic timer will result in an error.
- 参数
timer -- timer handle created 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.
By default, this function prints the list of active (running) timers. The output format is:
| Name | Period | Alarm |
Name — 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
To print the list of all created timers, enable Kconfig option
CONFIG_ESP_TIMER_PROFILING
. In this case, the output format is:| Name | Period | Alarm | Times_armed | Times_trigg | Times_skip | Cb_exec_time |
Name — timer name
Period — same as above
Alarm — same as above
Times_armed — number of times the timer was armed via esp_timer_start_X
Times_triggered - number of times the callback was triggered
Times_skipped - number of times the callback was skipped
Callback_exec_time - total time taken by callback to execute, across all calls
- 参数
stream -- stream (such as stdout) to which to dump the information
- 返回
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 (running) or not.
- 参数
timer -- timer handle created using esp_timer_create()
- 返回
1 if timer is still active (running)
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 using 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
Callback function to execute when timer expires.
-
void *arg
Argument to pass to callback.
-
esp_timer_dispatch_t dispatch_method
Dispatch callback from task or ISR; if not specified, esp_timer task.
-
const char *name
Timer name, used in esp_timer_dump() function.
-
bool skip_unhandled_events
Setting to skip unhandled events in light sleep for periodic timers.
-
esp_timer_cb_t callback
Type Definitions
-
typedef struct esp_timer *esp_timer_handle_t
Opaque type representing a single timer handle.
-
typedef void (*esp_timer_cb_t)(void *arg)
Timer callback function type.
- Param arg
pointer to opaque user-specific data