(1)实验平台:
普中ESP32S3开发板https://item.taobao.com/item.htm?id=803002165011(2)资料下载:普中科技-各型号产品资料下载链接
前面章节我们介绍了 ESP32 的外部中断使用,本章继续介绍ESP32 的定时器功能。本章分为如下几部分内容:
12.1 实验介绍
12.1.1 实验简介
定时器,顾名思义就是用来计时的,我们常常会设定计时或闹钟,然后时间到了就告诉我们要做什么。ESP32 也是这样,通过定时器可以完成各种预设好的任务。ESP32 定时器到达指定时间后也会产生中断,然后在回调函数内执行所需功能,这个和外部中断类似。在 ESP32 中有系统定时器和通用定时器。
12.1.1.1 系统定时器
ESP32-S3 芯片内置一组 52 位系统定时器。该定时器可用于生成操作系统所需的滴答定时中断,也可用作普通定时器生成周期或单次延时中断。下面先来简单了解一下系统定时器结构图,通过学习系统定时器结构图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。
图中可以看到系统定时器内置两个计数器 UNIT0 和UNIT1 以及三个比较器COMP0、COMP1、COMP2。比较器用于监控计数器的计数值是否达到报警值。
(1)计数器
UNIT0、UNIT1 均为 ESP32-S3 系统定时器内置的52 位计数器。计数器使用 XTAL_CLK 作为时钟源(40MHz)。XTAL_CLK 经分频后,在一个计数周期生成频率为 fXTAL_CLK/3 的时钟信号,然后在另一个计数周期生成频率为fXTAL_CLK/2 的时钟信号。因此,计数器使用的时钟 CNT_CLK,其实际平均频率为 fXTAL_CLK/2.5,即 16MHz,如下图所示:
每个 CNT_CLK 时钟周期,计数递增 1/16µs,即 16 个周期递增1µs。上图展示了系统定时器生成报警的过程。在上述过程中用到一个计数器(Timer Countern)和一个比较器(Timer Comparatorx),比较器将根据比较结果,生成报警中断。
用户可以通过配置寄存器 SYSTIMER_CONF_REG 中下面三个位来控制计数器UNITn,这三个位分别是:
①:SYSTIMER_TIMER_UNITn_WORK_EN
②:SYSTIMER_TIMER_UNITn_CORE0_STALL_EN
③:SYSTIMER_TIMER_UNITn_CORE1_STALL_EN
关于这三位的配置请参考《esp32-s3_technical_reference_manual_cn》。
(2)比较器
COMP0、COMP1、COMP2 均为 ESP32-S3 系统定时器内置的52 位比较器。比较器同样使用 XTAL_CLK 作为时钟源(40MHz)。
12.1.1.2 通用定时器
通用定时器可用于准确设定时间间隔、在一定间隔后触发(周期或非周期的)中断或充当硬件时钟。如下图所示:
ESP32-S3 包含两个定时器组,即定时器组 0 和定时器组1。每个定时器组有两个通用定时器(下文用 Tx 表示,x 为 0 或 1)和一个主系统看门狗定时器。所有通用定时器均基于 16 位预分频器和 54 位可自动重新加载向上/向下计数器。
下面先来学习通用定时器架构,通过学习通用定时器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。
(1)时钟选择器
每个定时器可通过配置寄存器 TIMG_TxCONFIG_REG 的TIMG_Tx_USE_XTAL字段,选择 APB 时钟(APB_CLK)或外部时钟(XTAL_CLK)作为时钟源。
(2)16 位预分频器
时钟源经过 16 位预分频器分频,产生时基计数器使用的时基计数器时钟(TB_CLK)。16 位预分频器的分频系数可通过 TIMG_Tx_DIVIDER 字段配置,选取从 2 到 65536 之间的任意值。注意,将 TIMG_Tx_DIVIDER 置0 后,分频系数会变为 65536。TIMG_Tx_DIVIDER 置 1 时,实际分频系数为2,计数器的值为实际时间的一半。
需要注意的是:定时器必须关闭(即 TIMG_Tx_EN 必须清零),才能更改16位预分频器。在定时器使能时更改 16 位预分频器会造成不可预知的结果。
(3)54 位时基计数器
54 位时基计数器基于 TB_CLK,可通过 TIMG_Tx_INCREASE 字段配置为递增或递减。时基计数器可通过置位或清零 TIMG_Tx_EN 字段使能或关闭。使能时,时基计数器的值会在每个 TB_CLK 周期递增或递减。关闭时,时基计数器暂停计数。注意,TIMG_Tx_EN 置位后,TIMG_Tx_INCREASE 字段还可以更改,时基计数器可立即改变计数方向。
时基计数器 54 位定时器的当前值必须被锁入两个寄存器,才能被CPU读取(因为 CPU 为 32 位)。向 TIMG_TxUPDATE_REG 写入任意值时,54 位定时器的值开始被锁入寄存器 TIMG_TxLO_REG 和 TIMG_TxHI_REG,两个寄存器分别锁存低 32 位和高 22 位。当 TIMG_TxUPDATE_REG 被硬件清零,表明锁存操作已经完成,可以从这两个寄存器中读取当前计数值。在TIMG_TxUPDATE_REG被写入新值之前,保持寄存器 TIMG_TxLO_REG 和 TIMG_TxHI_REG 的值不变,以供32 位的 CPU 读值。
(4)比较器
定时器可配置为在当前值与报警值相同时触发报警。报警会产生中断,(可选择)让定时器的当前值自动重新加载。54 位报警值可在TIMG_TxALARMLO_REG和 TIMG_TxALARMHI_REG 配置,两者分别代表报警值的低32 位和高22 位。但是,只有置位 TIMG_Tx_ALARM_EN 字段使能报警功能后,配置的报警值才会生效。为解决报警使能“过晚”(即报警使能时,定时器的值已过报警值),可逆计数器向上计数时,若定时器的当前值高于报警值(在一定范围内),或可逆计数器向下计数时,定时器的当前值低于报警值(在一定范围内),硬件都会立即触发报警。
12.1.2 实验目的
通过定时器让 LED 周期性每秒闪烁 1 次。
12.1.3 系统定时器函数使用
具体的步骤如下:
①:设置 ESP 定时器的回调函数
②:创建一个事件
③:每周期内触发一次
12.1.3.1 设置 ESP 定时器的回调函数
这里有些特殊,用于配置 ESP 定时器的回调函数,其结构体如下所示:
esp_timer_create_args_t tim_periodic_arg;
该函数的形参描述,如下表所示:
无返回值。
该结构体使用 esp_timer_create_args_t 类型的结构体变量传入定时器的配置参数,该结构体的定义如下所示:
/**
* @brief 计时器配置已传递给 esp_Timer_create
*/
typedef struct
{
esp_timer_cb_t callback; /* 计时器到期时调用的函数 */
void* arg; /* 要传递给回调的参数 */
esp_timer_dispatch_t dispatch_method; /* 从任务或 ISR 调用回调*/
const char* name; /* 计时器名称,用于 esp_Timer_dump 函数 */
bool skip_unhandled_events; /* 跳过周期计时器的未处理事件 */
} esp_timer_create_args_t;
使用方法如下:
/* 定义一个定时器结构体 */
esp_timer_create_args_t tim_periodic_arg = {
.callback = &esptim_callback, /* 设置回调函数*/
.arg = NULL, /* 不携带参数*/
};
12.1.3.2 创建一个事件
该函数用于创建指定定时器事件,其函数原型如下所示:
esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,esp_timer_handle_t* out_handle);
使用方法如下:
esp_timer_create(&tim_periodic_arg, &esp_tim_handle); /* 创建一个事件*/
12.1.3.3 每周期内触发一次
该函数用于使能定时器并启动,其函数原型如下所示:
esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer,uint64_tperiod_us);
使用方法如下:
esp_timer_start_periodic(esp_tim_handle, 1000000); /* 每周期内触发一次*/
12.1.4 通用定时器函数使用
12.1.4.1 配置通用定时器
该函数用于配置通用定时器,其函数原型如下所示:
esp_err_t gptimer_new_timer(const gptimer_config_t *config,gptimer_handle_t *ret_timer);
该函数使用 gptimer_config_t 类型的结构体变量传入gptimer 外设的配置参数,该结构体的定义如下所示:
/**
* @brief 通用定时器配置
*/
typedef struct
{
gptimer_clock_source_t clk_src; /* 通用定时器时钟源 */
gptimer_count_direction_t direction; /* 计数方向 */
uint32_t resolution_hz; /* 计数器分辨率(工作频率) */
struct
{
uint32_t intr_shared: 1;/* 若置为 1,则计时器中断编号可以与其他外围设备共享*/
} flags; /* 通用定时器配置标志 */
} gptimer_config_t;
使用方法如下:
gptimer_config_t g_tim_handle;
gptimer_handle_t g_tim = NULL;
g_tim_handle.clk_src = GPTIMER_CLK_SRC_DEFAULT; /* 选择定时器时钟源*/
g_tim_handle.direction = GPTIMER_COUNT_UP; /* 递增计数模式 */
g_tim_handle.resolution_hz = resolution; /* 计数器分辨率 */
gptimer_new_timer(&g_tim_handle, &g_tim); /* 创建新的通用定时器 */
12.1.4.2 配置通用定时器的计数值以及定时器周期
该函数用于配置通用定时器,其函数原型如下所示:
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer,unsigned long long value);
使用方法如下:
gptimer_set_raw_count(g_tim, 1000);
12.1.4.3 注册用户回调函数
该结构体用于注册用户回调函数,其结构体原型如下所示:
g_tim_callbacks.on_alarm = gptimer_callback;
该结构体用于注册用户回调函数,并没有涉及到形参与返回值,其调用的gptimer_callback(),在下面会讲解到。
该结构体使用 gptimer_event_callbacks_t 类型的结构体对gptimer 报警进行参数配置,该结构体的定义如下所示:
/**
* @brief 支持的 GPTimer 回调组
* @note 回调都在 ISR 环境下运行
* @note 当 CONFIG_GPTIMER_ISR_IRAM_SAFE 被启用时,
回调本身及其调用的函数应该被放置在 IRAM 中。
*/
typedef struct
{
gptimer_alarm_cb_t on_alarm; /* 定时器报警回调 */
} gptimer_event_callbacks_t;
使用方法如下:
gptimer_event_callbacks_t g_tim_callbacks;
g_tim_callbacks.on_alarm = gptimer_callback; /* 注册用户回调函数*/
12.1.4.4 创建一个消息队列,并引入一个事件
该函数用于创建一个新的队列实例,并返回一个句柄,通过该句柄可以引用新队列,其函数原型如下所示:
xQueueCreate( uxQueueLength, uxItemSize );
消息队列的调用方式用到了条件编译的方式,当满足条件编译所需要的条件时才会使用到该消息队列,同时该消息队列通过宏定义的方式进行调用,该宏定义如下所示:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize )
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
在内部,在 FreeRTOS 实现中,队列使用两个内存块。第一个块用于保存队列的数据结构。第二个块用于保存放入队列中的项目。如果使用xQueueCreate()创建队列,那么两个内存块都会在 xQueueCreate()函数内自动动态分配。(请参照 https://www.freertos.org/a00111.html)。如果使用xQueueCreateStatic()创建队列,则应用程序编写器必须提供队列将使用的内存。因此,xQueueCreateStatic()允许在不使用任何动态内存分配的情况下创建队列。该函数的使用方法,如下所示:
#include "driver/gpio.h"
struct Example
{
char ExampleID;
char Data[20];
};
void example_fun(void *pvParameters)
{
QueueHandle_t xQueue1, xQueue2;
/* 创建一个能够包含 10 个 uint32_t 值的队列 */
xQueue1 = xQueueCreate( 10, sizeof(uint32_t));
if(xQueue1 == 0)
{
/* 队列未创建,不得使用 */
}
/* 创建一个能够包含 10 个指向消息结构的指针的队列 */
/* 这些应该通过指针传递,因为它们包含大量数据 */
xQueue2 = xQueueCreate( 10, sizeof(struct Example *));
if(xQueue2 == 0)
{
/* 队列未创建,不得使用 */
}
}
12.1.4.5 设置定时器报警动作
该函数用于配置通用定时器报事件警,其函数原型如下所示:
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer,const gptimer_alarm_config_t *config);
该函数使用 gptimer_alarm_config_t 类型的结构体变量传入gptimer外设的报警配置参数,该结构体的定义如下所示:
/**
* @brief 通用定时器报警配置
*/
typedef struct
{
uint64_t alarm_count; /* 报警目标计数值 */
uint64_t reload_count; /* 报警重新加载计数值 */
struct
{
uint32_t auto_reload_on_alarm: 1;/* 报警事件发生后立即通过硬件重新加载计数值 */
} flags; /* 报警配置标志 */
} gptimer_alarm_config_t;
使用方法如下:
gptimer_alarm_config_t alarm_config;
alarm_config.alarm_count = 1000000; /* 报警目标计数值 */
gptimer_set_alarm_action(g_tim, &alarm_config);
12.2 硬件设计
本实验使用到硬件资源如下:
(1)LED 模块
(2)ESP32 GPIO
LED 模块电路前面已介绍,此处不再重复。
12.3 软件设计
12.3.1 系统定时器实验
下面我们打开“\4--实验程序\4--ESP-IDF 实验\1--基础实验\08-time\1-esp_time”程序,在 components/bsp 文件夹内新建了一个esp_time文件夹,该文件夹内存放的是系统定时器驱动。
在 bsp 文件夹内还有一个 CMakeLists.txt 文件,里面用来设置对应的文件路径,每当新建文件夹,都需要将该文件夹名称添加到CMakeLists.txt 内,这个在前面已经介绍过,类似与 KEIL5 MDK 开发 STM32 中路径的添加。如下所示:
12.3.1.1 系统定时器驱动
12.3.1.1.1 esp_time.c 文件
代码如下:
/**
****************************************************************************************************
* @file esp_time.c
* @author 普中科技
* @version V1.0
* @date 2024-03-01
* @brief ESP32系统定时器驱动代码
* @license Copyright (c) 2024-2034, 深圳市普中科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:普中科技 ESP32-S3 开发板
* 在线视频:https://space.bilibili.com/2146492485
* 技术论坛:www.prechin.net
* 公司网址:www.prechin.cn
* 购买地址:
*
****************************************************************************************************
*/
#include "esp_time.h"
#include "esp_timer.h"
#include "led.h"
/**
* @brief 定时器回调函数
* @param arg: 不携带参数
* @retval 无
*/
void esptim_callback(void *arg)
{
LED_TOGGLE();
}
/**
* @brief 初始化高精度定时器(ESP_TIMER)
* @param arr: 自动重装值
* @param tps: 定时器周期,以微妙为单位(μs),以一秒为定时器周期来执行一次定时器中断,
* 那此处tps = 1s = 1000000μs
* @retval 无
*/
void esptim_int_init(uint16_t arr, uint64_t tps)
{
esp_timer_handle_t esp_tim_handle; /* 定时器回调函数句柄 */
/* 定义一个定时器结构体 */
esp_timer_create_args_t tim_periodic_arg = {
.callback = &esptim_callback, /* 设置回调函数 */
.arg = NULL, /* 不携带参数 */
};
esp_timer_create(&tim_periodic_arg, &esp_tim_handle); /* 创建一个事件 */
esp_timer_start_periodic(esp_tim_handle, tps); /* 每周期内触发一次 */
}
esptim_int_init 函数调用结构体 esp_timer_create_args_t 配置ESP定时器时,传入的参数 tim_periodic_arg 调用了定时器服务函数,esp_timer_create 配置了自动重装载值,这是因为对于ESP 定时器而言,只有这两个成员变量是有效的,因此仅需配置这两个成员变量。并且开启了ESP定时器的更新中断,因此每当 ESP 定时器计数溢出后都会产生一次更新中断。
esptim_callback 函数为回调函数。在 ESP 定时器每次计数溢出后都会翻转一次 LED1 的状态。
12.3.1.1.2 main 函数
代码如下:
/**
****************************************************************************************************
* @file main.c
* @author 普中科技
* @version V1.0
* @date 2024-03-01
* @brief
* @license Copyright (c) 2024-2034, 深圳市普中科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:普中科技 ESP32-S3 开发板
* 在线视频:https://space.bilibili.com/2146492485
* 技术论坛:www.prechin.net
* 公司网址:www.prechin.cn
* 购买地址:
*
****************************************************************************************************
* 实验名称:ESP32系统定时器实验
*
* 接线说明:
*
* LED模块-->ESP32 IO
* (D1)-->(3)
*
* 实验现象:程序下载成功后,D1指示灯亮灭;
*
* 注意事项:
*
*
****************************************************************************************************
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "led.h"
#include "esp_time.h"
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
esptim_int_init(5000, 1000000); /* 初始化高分辨率定时器,此处设置定时器周期为1秒,
但该函数事宜微妙为单位进行计算,
故而1秒钟换算为1000000微秒 */
while(1)
{
vTaskDelay(10); /* 延时10ms */
}
}
从上面的代码中可以看到,ESP 定时器的自动重装载配置为5000,ESP定时器的周期值配置为 1000000,因为 ESP32-S3 高分辨率定时器的计数周期是以微秒作为基础单位进行运算,所以当我们设定计数周期为1 秒时需要将单位换算为微秒。因此 LED 的闪烁周期为 1 秒。
12.3.2 通用定时器实验
下面我们打开“\4--实验程序\4--ESP-IDF 实验\1--基础实验\08-time\2-gp_time”程序,在 components/bsp 文件夹内新建了一个gp_time文件夹,该文件夹内存放的是系统定时器驱动。
在 bsp 文件夹内还有一个 CMakeLists.txt 文件,里面用来设置对应的文件路径,每当新建文件夹,都需要将该文件夹名称添加到CMakeLists.txt 内,这个在前面已经介绍过,类似与 KEIL5 MDK 开发 STM32 中路径的添加。如下所示:
12.3.2.1 系统定时器驱动
12.3.2.1.1 gp_time.c 文件
代码如下:
/**
****************************************************************************************************
* @file gp_time.c
* @author 普中科技
* @version V1.0
* @date 2024-03-01
* @brief 通用定时器驱动代码
* @license Copyright (c) 2024-2034, 深圳市普中科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:普中科技 ESP32-S3 开发板
* 在线视频:https://space.bilibili.com/2146492485
* 技术论坛:www.prechin.net
* 公司网址:www.prechin.cn
* 购买地址:
*
****************************************************************************************************
*/
#include "gp_time.h"
#include "esp_log.h"
QueueHandle_t queue;
/**
* @brief 初始化通用定时器
* @param counts: 计数值
* @param resolution: 定时器周期,resolution = 1s = 1000000μs(此处,定时器以微秒作为计算单位,)
* @retval 无
*/
void gptim_int_init(uint16_t counts, uint32_t resolution)
{
gptimer_config_t g_tim_handle;
gptimer_alarm_config_t alarm_config;
gptimer_event_callbacks_t g_tim_callbacks;
uint64_t count;
/* 配置通用定时器 */
ESP_LOGI("GPTIMER_ALARM", "配置通用定时器");
gptimer_handle_t g_tim = NULL; /* 创建通用定时器句柄 */
g_tim_handle.clk_src = GPTIMER_CLK_SRC_DEFAULT; /* 选择定时器时钟源 */
g_tim_handle.direction = GPTIMER_COUNT_UP; /* 递增计数模式 */
g_tim_handle.resolution_hz = resolution; /* 计数器分辨率 */
g_tim_callbacks.on_alarm = gptimer_callback; /* 注册用户回调函数 */
alarm_config.alarm_count = 1000000; /* 报警目标计数值 */
ESP_ERROR_CHECK(gptimer_new_timer(&g_tim_handle, &g_tim)); /* 创建新的通用定时器,并返回句柄 */
queue = xQueueCreate(10, sizeof(gptimer_event_t)); /* 创建一个队列,并引入一个事件 */
if (!queue)
{
ESP_LOGE("GPTIMER_ALARM", "创建队列失败"); /* 创建队列失败 */
return;
}
/* 设置和获取计数值 */
ESP_LOGI("GPTIMER_ALARM", "设置计数值");
ESP_ERROR_CHECK(gptimer_set_raw_count(g_tim, counts)); /* 设置计数值 */
ESP_LOGI("GPTIMER_ALARM", "获取计数值");
ESP_ERROR_CHECK(gptimer_get_raw_count(g_tim, &count)); /* 获取计数值 */
ESP_LOGI("GPTIMER_ALARM", "定时器计数值: %llu", count);
/* 注册事件回调函数 */
ESP_ERROR_CHECK(gptimer_register_event_callbacks(g_tim, &g_tim_callbacks, queue)); /* 配置通用定时器回调函数 */
/* 设置报警动作 */
ESP_LOGI("GPTIMER_ALARM", "使能通用定时器");
ESP_ERROR_CHECK(gptimer_enable(g_tim)); /* 使能通用定时器 */
ESP_ERROR_CHECK(gptimer_set_alarm_action(g_tim, &alarm_config)); /* 配置通用定时器报警事件 */
ESP_ERROR_CHECK(gptimer_start(g_tim)); /* 启动通用定时器 */
}
/**
* @brief 定时器回调函数
* @param 无
* @retval 无
*/
bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
BaseType_t high_task_awoken = pdFALSE;
queue = (QueueHandle_t)user_data;
/* 从事件数据中检索计数值 */
gptimer_event_t ele = {
.event_count = edata->count_value
};
/* 可选:通过操作系统队列将事件数据发送到其他任务 */
xQueueSendFromISR(queue, &ele, &high_task_awoken);
/* 重新配置报警值 */
gptimer_alarm_config_t alarm_config = {
.alarm_count = edata->alarm_value + 1000000, /* 在接下来的1秒内报警 */
};
gptimer_set_alarm_action(timer, &alarm_config);
/* 返回是否需要在ISR结束时让步 */
return high_task_awoken == pdTRUE;
}
对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 gptimer_alarm_config_t 的不同参数:
①:gptimer_alarm_config_t::alarm_count 设置触发警报事件的目标计数值。设置警报值时还需考虑计数方向。尤其是当 gptimer_alarm_config_t::auto_reload_on_alarm 为true 时,gptimer_alarm_config_t::alarm_count 和 gptimer_alarm_config_t::reload_count 不能设置为相同的值,因为警报值和重载值相同时没有意义。
②:gptimer_alarm_config_t::reload_count 代表警报事件发生时要重载的计数值。此配置仅在 gptimer_alarm_config_t::auto_reload_on_alarm 设置为 true 时生效。
③:gptimer_alarm_config_t::auto_reload_on_alarm 标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将gptimer_alarm_config_t::reload_count 的值重载到计数器中。
要使警报配置生效,需要调用 gptimer_set_alarm_action()。特别是当gptimer_alarm_config_t 设置为 NULL 时,报警功能将被禁用。
定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 gptimer_register_event_callbacks() 将函数挂载到中断服务例程(ISR)。gptimer_event_callbacks_t 中列出了所有支持的事件回调函数:gptimer_event_callbacks_t 设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有 ISR 后缀的 FreeRTOSAPI)。函数原型在 gptimer_alarm_cb_t 中有所声明。也可以通过参数 user_data,将自己的上下文保存到gptimer_register_event_callbacks()中。用户数据将直接传递给回调函数。
此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在gptimer_enable()之前调用这一函数,否则将返回 ESP_ERR_INVALID_STATE错误。
12.3.2.1.2 main 函数
代码如下:
/**
****************************************************************************************************
* @file main.c
* @author 普中科技
* @version V1.0
* @date 2024-03-01
* @brief
* @license Copyright (c) 2024-2034, 深圳市普中科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:普中科技 ESP32-S3 开发板
* 在线视频:https://space.bilibili.com/2146492485
* 技术论坛:www.prechin.net
* 公司网址:www.prechin.cn
* 购买地址:
*
****************************************************************************************************
* 实验名称:通用定时器实验
*
* 接线说明:
*
* LED模块-->ESP32 IO
* (D1)-->(3)
*
* 实验现象:程序下载成功后,D1指示灯指定时间闪烁;
*
* 注意事项:
*
*
****************************************************************************************************
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "led.h"
#include "gp_time.h"
#include "esp_log.h"
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
esp_err_t ret;
gptimer_event_t g_tim_evente;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
gptim_int_init(100, 1000000); /* 初始化通用定时器 */
while (1)
{
if (xQueueReceive(queue, &g_tim_evente, 2000))
{
ESP_LOGI("GPTIMER_ALARM", "定时器报警, 计数值: %llu", g_tim_evente.event_count); /* 打印通用定时器发生一次计数事件后获取到的值 */
LED_TOGGLE();
}
else
{
ESP_LOGW("GPTIMER_ALARM", "错过一次计数事件");
}
}
vQueueDelete(queue);
}
从上面的代码中可以看到,通用定时器的计数值为100,定时器周期设置为1000000 微秒并通过创建消息队列的方式引入一个定时器事件。
12.4 实验现象
12.4.1 系统定时器实验
下载程序前,按照如下接线:
将程序下载到开发板内(可参考“2.2.4 程序下载运行”章节),可以看到D1 指示灯间隔 1S 闪烁。
12.4.2 通用定时器实验
下载程序前,按照如下接线:
将程序下载到开发板内(可参考“2.2.4 程序下载运行”章节),可以看到D1 指示灯间隔 1S 闪烁。在一定周期内串口打印输出定时器报警事件,报警值以及计数值等信息。