SOC-ESP32S3部分:13-定时器
飞书文档https://x509p6c8to.feishu.cn/wiki/Kj5owbUNDiM0fQkGJsecYOBpnbh
IDF中提供了软件和硬件两种定时器,我们可以根据需要选择对应定时器使用。
ESP_Timer
- 高精度与灵活性:是一个基于 FreeRTOS 的通用定时器 API,它提供了高精度的定时器功能,时间分辨率可以达到微秒级别。可以创建单次触发和周期性触发的定时器,用户可以根据需要灵活设置定时器的超时时间。
- 软件定时器:本质上是软件定时器,依赖于系统时钟和 FreeRTOS 的任务调度机制来实现定时功能。它在系统的任务调度器中运行,当定时器超时后,会触发相应的回调函数。
GPTimer
- 硬件定时器由芯片的硬件电路实现定时功能,不依赖于软件任务调度,因此具有更高的实时性和稳定性。
- 多种配置选项:可以对定时器的时钟源、计数方向(向上计数或向下计数)、分辨率等进行详细配置,以满足不同应用场景的需求。
如何选择?
- 如果需要一个简单易用的高精度定时器,且不需要对硬件定时器进行复杂配置,选择 esp_timer。
- 如果需要直接控制硬件定时器,或者需要实现复杂的定时功能(如 PWM、脉冲计数等),选择 gptimer。
ESP_Timer
例程中ESP_Timer部分的例程位于esp-idf/examples/system/esp_timer/
示例开始时创建并启动一个周期性和一个一次性定时器,回调函数打印自启动以来经过的时间,一次性定时器的回调函数会重新定义周期性定时器的周期并重启它,之后芯片进入浅睡眠,唤醒后定时器再执行一些回调函数,最后停止并删除定时器以释放内存。
API文档位于https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4/esp32s3/api-reference/system/esp_timer.html
ESP_Timer定时器创建esp_timer_create
#include "esp_timer.h"esp_err_t esp_timer_create(const esp_timer_create_args_t *create_args, esp_timer_handle_t *out_handle);
参数
create_args::定时器的配置参数(如回调函数、名称等)。
out_handle:输出参数,用于存储创建的定时器句柄。成功创建后,可以通过这个句柄来操作定时器。定时器参数配置:
esp_timer_create_args_t 结构体说明
用于定义和配置一个 ESP 定时器。通过这个结构体,你可以指定定时器的回调函数、传递给回调函数的参数、回调函数的调度方法、定时器的名称以及是否在轻睡眠模式下跳过未处理的事件。esp_timer_cb_t callback:
描述: 指向定时器到期时要执行的回调函数的指针。回调函数的原型必须为 void callback(void* arg)。void* arg:
描述: 传递给回调函数的参数。这个参数可以是任何类型的数据,通常用于传递回调函数需要的上下文信息。esp_timer_dispatch_t dispatch_method:
描述: 指定回调函数的调度方法。可以是以下值之一:
ESP_TIMER_TASK: 回调函数在 esp_timer 任务中执行(默认)。
ESP_TIMER_ISR: 回调函数在中断服务程序(ISR)中执行。使用此选项时,必须在 Kconfig 中启用 CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD 选项。const char* name:
描述: 定时器的名称,用于调试和日志记录。bool skip_unhandled_events:
描述: 对于周期性定时器,指定是否在轻睡眠模式下跳过未处理的事件。如果设置为 true,在轻睡眠模式下,未处理的定时器事件将被跳过,以减少唤醒次数。如果设置为 false,定时器事件将按计划执行。
- esp_timer_start_once:启动一个单次触发的定时器,在指定的时间后触发一次并停止。
- esp_timer_start_periodic:启动一个周期性触发的定时器,按照指定的时间间隔重复触发。
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
参数
timer:定时器句柄,由 esp_timer_create 创建并返回。
timeout_us:定时器触发前的等待时间,单位为微秒(us)。定时器会在指定的时间后触发一次,并自动停止。esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us);
参数
timer:定时器句柄,由 esp_timer_create 创建并返回。
period_us:定时器每次触发的时间间隔,单位为微秒(us)。定时器会以指定的时间间隔周期性地触发。
参考代码
例如我们下方实现了一个1s周期性的定时器
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"static const char *TAG = "esp_timer_example";// 定时器回调函数
static void timer_callback(void* arg) {ESP_LOGI(TAG, "Timer update!");
}void app_main() {// 定时器配置esp_timer_create_args_t timer_args = {.callback = &timer_callback,.name = "example_timer"};esp_timer_handle_t timer;// 创建定时器esp_timer_create(&timer_args, &timer);// 启动单次触发定时器,超时时间为 1000000 微秒(即 1 秒)// esp_timer_start_once(timer, 1000000);esp_timer_start_periodic(timer, 1000000); // 1秒周期// 主循环while (1) {vTaskDelay(pdMS_TO_TICKS(100));}
}
GP_Timer
通用定时器是 ESP32-S3 定时器组外设的驱动程序。ESP32-S3 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。
- esp-idf/examples/peripherals/timer_group/gptimer演示了如何在 ESP 芯片上使用通用定时器 API 生成周期性警报事件,触发不同的警报动作。
- esp-idf/examples/peripherals/timer_group/wiegand_interface使用两个定时器(一个在单次触发模式下,另一个在周期触发模式下),来触发中断并在中断中改变 GPIO 的输出状态。
GP_Timer部分的API文档如下:
https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4/esp32s3/api-reference/peripherals/gptimer.html
GP_Timer定时器的创建分为四步:
- 创建定时器实例,定时器的时钟源、计数方式、分辨率
- 注册回调事件,报警时执行的函数
- 设置报警动作,计数到什么值报警,是否重载
- 启动定时器
定时器的创建分为四步:
- 创建定时器实例,定时器的时钟源、计数方式、分辨率,可以使用gptimer_new_timer函数实现
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer);
功能: gptimer_new_timer 函数用于根据指定的配置创建一个新的通用定时器实例。
参数:
config: 指向 gptimer_config_t 结构体的指针,包含定时器的配置参数。
ret_timer: 指向 gptimer_handle_t 类型的指针,用于存储新创建的定时器句柄。
返回值:
ESP_OK: 定时器创建成功。
ESP_ERR_INVALID_ARG: 参数无效。
ESP_ERR_NO_MEM: 内存分配失败。
ESP_ERR_NOT_FOUND: 没有可用的定时器硬件资源。gptimer_config_t 结构体用于定义和配置一个通用定时器。你可以指定定时器的时钟源、计数方向和分辨率。
typedef struct {gptimer_clock_source_t clk_src; //!< Clock source for the timergptimer_count_direction_t direction; //!< Count direction (up or down)uint32_t resolution_hz; //!< Timer resolution in Hz
} gptimer_config_t;gptimer_clock_source_t clk_src:
描述: 指定定时器使用的时钟源。可以是以下值之一:
GPTIMER_CLK_SRC_DEFAULT: 使用默认的时钟源。
GPTIMER_CLK_SRC_APB: 使用 APB 时钟源。
GPTIMER_CLK_SRC_XTAL: 使用晶振时钟源。gptimer_count_direction_t direction:
描述: 指定定时器的计数方向。可以是以下值之一:
GPTIMER_COUNT_UP: 定时器向上计数。
GPTIMER_COUNT_DOWN: 定时器向下计数。uint32_t resolution_hz:
描述: 定时器的分辨率,以 Hz 为单位。分辨率决定了定时器的计数周期。
例如,1000000 表示每个计数周期为 1 微秒。
使用参考:
// 定义一个通用定时器句柄,用于后续对定时器进行操作gptimer_handle_t gptimer = NULL;// 定义一个定时器配置结构体,用于设置定时器的相关参数gptimer_config_t timer_config = {// 使用默认的时钟源.clk_src = GPTIMER_CLK_SRC_DEFAULT,// 定时器计数方向为向上计数.direction = GPTIMER_COUNT_UP,// 定时器的分辨率为 1MHz,即每个计数周期为 1 微秒.resolution_hz = 1000000,};// 根据配置创建一个新的定时器实例,并将其句柄存储在 gptimer 中gptimer_new_timer(&timer_config, &gptimer);
- 注册回调事件,报警时执行的函数
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data);
功能: gptimer_register_event_callbacks 函数用于为定时器注册事件回调函数,并传递用户数据给回调函数。
参数:
timer: 定时器句柄,由 gptimer_new_timer 函数返回。
cbs: 指向 gptimer_event_callbacks_t 结构体的指针,包含定时器的事件回调函数。
user_data: 传递给回调函数的用户数据。这个参数可以是任何类型的数据,通常用于传递回调函数需要的上下文信息。
返回值:
ESP_OK: 回调函数注册成功。
ESP_ERR_INVALID_ARG: 参数无效。
ESP_ERR_NOT_SUPPORTED: 定时器不支持事件回调。gptimer_event_callbacks_t结构体用于定义和配置通用定时器(GPTimer)的事件回调函数。
typedef struct {gptimer_alarm_cb_t on_alarm;
} gptimer_event_callbacks_t;
gptimer_alarm_cb_t on_alarm:
类型: gptimer_alarm_cb_t
描述: 指向定时器达到报警值时要执行的回调函数的指针。回调函数的原型必须为 bool on_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)。
使能定时器
esp_err_t gptimer_enable(gptimer_handle_t timer);
功能: gptimer_enable 函数用于启用一个已配置的定时器。
参数:
timer: 定时器句柄,由 gptimer_new_timer 函数返回。
返回值:
ESP_OK: 定时器启用成功。
ESP_ERR_INVALID_ARG: 参数无效。
ESP_ERR_INVALID_STATE: 定时器已经启用。
使用参考:
// 为定时器注册事件回调函数,并将之前创建的队列句柄作为用户数据传递给回调函数gptimer_event_callbacks_t cbs = {// 当定时器达到报警值时,调用 example_timer_on_alarm_cb_v1 函数.on_alarm = example_timer_on_alarm_cb_v1,};gptimer_register_event_callbacks(gptimer, &cbs, queue);gptimer_enable(gptimer);
- 设置报警动作,计数到什么值报警,是否重载
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config);
功能: gptimer_set_alarm_action 函数用于设置定时器的报警动作。通过这个函数,你可以配置定时器的报警计数值、重载计数值以及是否启用自动重载功能。
参数:
timer: 定时器句柄,由 gptimer_new_timer 函数返回。
config: 指向 gptimer_alarm_config_t 结构体的指针,包含定时器的报警配置参数。
返回值:
ESP_OK: 报警动作设置成功。
ESP_ERR_INVALID_ARG: 参数无效。
ESP_ERR_INVALID_STATE: 定时器未创建或已启用。gptimer_alarm_config_t 定时器的报警计数值。当定时器的计数值达到这个值时,会触发报警事件。
typedef struct {uint64_t alarm_count; //!< Alarm count valueuint64_t reload_count; //!< Reload count value when alarm occursstruct {bool auto_reload_on_alarm; //!< Enable auto-reload on alarm} flags;
} gptimer_alarm_config_t;
uint64_t alarm_count:uint64_t reload_count:
描述: 定时器在达到报警值时重新加载的计数值。如果启用了自动重载功能,定时器会在每次达到报警值后自动重新加载为这个值。struct { bool auto_reload_on_alarm; } flags:
描述: 包含定时器的标志配置。
auto_reload_on_alarm: 布尔值,指定是否在定时器达到报警值时自动重新加载计数器。如果设置为 true,定时器会在每次达到报警值后自动重新加载为 reload_count 的值。
使用参考:
// 为定时器设置报警动作,定义一个定时器报警配置结构体,用于设置定时器的报警参数gptimer_alarm_config_t alarm_config1 = {// 定时器的报警计数值为 1000000,由于分辨率为 1MHz,所以报警周期为 1 秒.alarm_count = 1000000,// 当定时器达到报警值时,计数器将重新加载为 0.reload_count = 0, // 启用定时器自动重载功能,即每次达到报警值后自动重新开始计数.flags.auto_reload_on_alarm = true,};gptimer_set_alarm_action(gptimer, &alarm_config1);
- 启动定时器
一切配置完成后,记得启动定时器即可。
// 启动定时器,开始计数gptimer_start(gptimer);
如下发代码所示,我们开启了间隔一个1s触发一次的定时器,并在触发时发送一条消息到app_main显示。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "esp_log.h"static const char *TAG = "Timer";// 定义定时器报警事件的回调函数,当定时器达到报警值时会调用此函数
static bool IRAM_ATTR example_timer_on_alarm_cb_v1(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{// 用于标记在中断服务程序中是否有高优先级任务被唤醒BaseType_t high_task_awoken = pdFALSE;// 将传入的用户数据转换为队列句柄类型QueueHandle_t queue = (QueueHandle_t)user_data;static uint64_t count = 0;count ++;// 从中断服务程序中向队列发送数据(即当前的计数 count)xQueueSendFromISR(queue, &count, &high_task_awoken);// 返回是否需要在中断服务程序结束时进行任务切换 如果 high_task_awoken 为 pdTRUE,则需要进行任务切换return (high_task_awoken == pdTRUE);
}void app_main(void)
{ESP_LOGI(TAG, "app_main start\n");QueueHandle_t queue = xQueueCreate(10, sizeof(uint64_t));ESP_LOGI(TAG, "Create timer handle");// 定义一个通用定时器句柄,用于后续对定时器进行操作gptimer_handle_t gptimer = NULL;// 定义一个定时器配置结构体,用于设置定时器的相关参数gptimer_config_t timer_config = {// 使用默认的时钟源.clk_src = GPTIMER_CLK_SRC_DEFAULT,// 定时器计数方向为向上计数.direction = GPTIMER_COUNT_UP,// 定时器的分辨率为 1MHz,即每个计数周期为 1 微秒.resolution_hz = 1000000,};// 根据配置创建一个新的定时器实例,并将其句柄存储在 gptimer 中gptimer_new_timer(&timer_config, &gptimer);// 为定时器注册事件回调函数,并将之前创建的队列句柄作为用户数据传递给回调函数gptimer_event_callbacks_t cbs = {// 当定时器达到报警值时,调用 example_timer_on_alarm_cb_v1 函数.on_alarm = example_timer_on_alarm_cb_v1,};gptimer_register_event_callbacks(gptimer, &cbs, queue);gptimer_enable(gptimer);// 为定时器设置报警动作,定义一个定时器报警配置结构体,用于设置定时器的报警参数gptimer_alarm_config_t alarm_config1 = {// 定时器的报警计数值为 1000000,由于分辨率为 1MHz,所以报警周期为 1 秒.alarm_count = 1000000,// 当定时器达到报警值时,计数器将重新加载为 0.reload_count = 0, // 启用定时器自动重载功能,即每次达到报警值后自动重新开始计数.flags.auto_reload_on_alarm = true,};gptimer_set_alarm_action(gptimer, &alarm_config1);// 启动定时器,开始计数gptimer_start(gptimer);uint64_t count;while (1){if (xQueueReceive(queue, &count, pdMS_TO_TICKS(2000))){// 输出一条信息日志,显示当前接收到的定时器计数ESP_LOGI(TAG, "Timer count=%llu", count);}}
}