图15.3.1.1 GPTIMER实验程序流程图
15.3.2 GPTIMER函数解析
ESP-IDF提供了一套API来配置通用定时器。要使用此功能,需要导入必要的头文件:
#include "driver/gptimer.h"
接下来,作者将介绍一些常用的GPTIMER函数,这些函数的描述及其作用如下:
1,配置通用定时器
该函数用于配置通用定时器,其函数原型如下所示:
esp_err_t gptimer_new_timer(const gptimer_config_t *config,
gptimer_handle_t *ret_timer);
该函数的形参描述,如下表所示:
| |
| |
| 通用定时器句柄类型。如果没有特定需求一般设置为NULL |
表15.3.2.1 函数gptimer_new_timer()形参描述
返回值:ESP_OK表示配置成功。其他表示配置失败。
下表是gptimer_config_t类型的结构体变量描述。
| 成员变量 | |
| clk_src: 选择定时器的时钟源。 gptimer_clock_source_t 中列出多种可用时钟,仅可选择其中种 | GPTIMER_CLK_SRC_APB: 选择APB作为时钟源 |
GPTIMER_CLK_SRC_XTAL: 选择XTAL作为时钟源 |
GPTIMER_CLK_SRC_DEFAULT: 选择APB作为默认选项 |
direction: 设置定时器的计数方向,gptimer_count_direction_t 中列出多个支持的方向,仅可选择其中一种 | GPTIMER_COUNT_DOWN: 向下计数,即从65535到0 |
GPTIMER_COUNT_UP: 向上计数,即从0到65535 |
resolution_hz: 设置内部计数器的分辨率。计数器每滴答一次相当于 1 / resolution_hz 秒 | |
intr_priority: 设置中断的优先级。若设置为 0,则会分配一个默认优先级的中断,否则会使用指定的优先级。 | |
intr_shared: 设置是否将定时器中断源标记为共享源,默认为1。如果该参数设置为true(1),那么多个定时器将共享同一个中断源。这意味着当其中一个定时器触发中断时,相应的中断处理程序将被调用,并且不同的定时器中断可以在同一个中断服务函数中进行处理。而如果intr_shared参数设置为false(0),那么每个定时器都会有独立的中断源,并且将有一个相应的中断服务函数用于处理每个定时器的中断。 | |
表15.3.2.2 gptimer_config_t结构体参数值描述
完成上述结构体参数配置之后,可以将结构传递给 gptimer_new_timer() 函数,用以实例化定时器实例并返回定时器句柄。
2,计数值以及定时器周期
该函数用于配置通用定时器的计数值以及定时器周期,其函数原型如下所示:
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer,
unsigned long long value);
该函数的形参描述,如下表所示:
| |
| 计时器句柄由gptimer_new_timer创建 |
| |
表15.3.2.3 函数gptimer_set_raw_count()形参描述
返回值:ESP_OK表示配置成功,其他表示配置失败。
3,注册用户回调函数
该结构体用于注册用户回调函数,其结构体原型如下所示:
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer,
const gptimer_event_callbacks_t *cbs, void *user_data);
该函数的形参描述,如下表所示:
| |
| 需要设置的定时器,计时器句柄由gptimer_new_timer创建 |
| |
| |
表15.3.2.4 函数gptimer_register_event_callbacks ()形参描述
返回值:ESP_OK表示配置成功。其他表示配置失败。
当定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,便可通过该函数将函数挂载至中断服务例程(ISR)。
4,定时器报警,设置报警动作
该函数用于配置通用定时器报事件警,其函数原型如下所示:
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer,
const gptimer_alarm_config_t *config);
该函数的形参描述,如下表所示:
| |
| |
| 指向通用定时器报警配置的指针类型。报警配置,尤其是将配置设置为NULL意味着禁用报警功能 |
表15.3.2.5 函数gptimer_set_alarm_action()形参描述
返回值:ESP_OK表示配置成功,其他表示失败。
该函数使用gptimer_alarm_config_t类型的结构体变量传入gptimer外设的报警配置参数,该结构体的定义如下所示:
/**
*/
typedef struct {
uint64_t alarm_count; /* 报警目标计数值 */
uint64_t reload_count; /* 报警重新加载计数值 */
struct {
uint32_t auto_reload_on_alarm: 1;/* 报警事件发生后立即通过硬件重新加载计数值 */
} flags; /* 报警配置标志 */
} gptimer_alarm_config_t;
5,使能定时器
该函数用于配置通用定时器报事件警,其函数原型如下所示:
esp_err_t gptimer_enable(gptimer_handle_t timer);
该函数的形参描述,如下表所示:
表15.3.2.6 函数gptimer_enable()形参描述
返回值:ESP_OK表示配置成功,其他表示失败。
此函数将把定时器驱动程序的状态从初始化切换为使能状态,如果 gptimer_register_event_callbacks() 已经延迟安装回调服务函数,此函数将使能回调服务函数。
6,启动定时器
该函数用于配置通用定时器报事件警,其函数原型如下所示:
esp_err_t gptimer_start(gptimer_handle_t timer);
该函数的形参描述,如下表所示:
表15.3.2.7 函数gptimer_enable()形参描述
返回值:ESP_OK表示配置成功,其他表示失败。
我们使能了定时器之后,并没有代表定时器已经开始运行,还需要通过调用 gptimer_start() 函数使内部计数器开始工作。
15.3.3 GPTIMER驱动解析
在IDF版的06_gp_timer例程中,作者在06_gp_timer \components\BSP路径下新增了一个GPTIM文件夹,用于存放gptim.c和gptim.h这两个文件。其中,gptim.h文件负责声明GPTIM相关的函数和变量,而gptim.c文件则实现了GPTIM的驱动代码。下面,我们将详细解析这两个文件的实现内容。
1,gptim.h文件
/* 参数引用 */
typedef struct {
uint64_t event_count;
} gptimer_event_t;
extern QueueHandle_t queue;
/* 函数声明 */
void gptim_int_init(uint16_t counts, uint16_t resolution); /* 初始化通用定时器 */
bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *edata,
void *user_data); /* 定时器回调函数 */
2,gptim.c文件
/**
* @brief 初始化通用定时器
* @param resolution: 定时器周期,resolution = 1s = 1000000μs
(此处,定时器以微秒作为计算单位,)
* @retval 无
*/
void gptim_int_init(uint16_t counts, uint16_t resolution)
{
gptimer_alarm_config_t alarm_config;
uint64_t count;
/* 配置通用定时器 */
ESP_LOGI("GPTIMER_ALARM", "配置通用定时器"); /* 创建通用定时器句柄 */
gptimer_handle_t g_tim = NULL;
gptimer_config_t g_tim_handle = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, /* 选择定时器时钟源 */
.direction = GPTIMER_COUNT_UP, /* 递增计数模式 */
.resolution_hz = resolution, /* 计数器分辨率 */
};
gptimer_event_callbacks_t 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错误。
15.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
set(src_dirs
GPTIM
LED)
set(include_dirs
GPTIM
LED)
set(requires
driver
esp_timer)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的红色GPTIM驱动需要由开发者自行添加,以确保GPTIM驱动能够顺利集成到构建系统中。需要注意的是我们的依赖库(requires)需要添加上ESP32-S3定时器定时器的库,这一步骤是必不可少的,它确保了GPTIM驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
15.3.5 实验应用代码
打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
uint8_t record;
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)
{
record = 1;
/* 打印通用定时器发生一次计数事件后获取到的值 */
if (xQueueReceive(queue, &g_tim_evente, 2000))
{
ESP_LOGI("GPTIMER_ALARM",
"定时器报警, 计数值: %llu",
g_tim_evente.event_count);
record--;
}
else
{
ESP_LOGW("GPTIMER_ALARM", "错过一次计数事件");
}
}
vQueueDelete(queue);
}
从上面的代码中可以看到,通用定时器的计数值为100,定时器周期设置为1000000微秒并通过创建消息队列的方式引入一个定时器事件。
15.4 下载验证
在完成编译和烧录操作后,可以看到板子上的LED在闪烁,在一定周期内串口打印输出定时器报警事件,报警值以及计数值等信息。