ESP32入门开发·通用硬件定时器 (GPTimer)
目录
1. 概述
2. 架构介绍
2.1 16 位预分频器与时钟选择器
2.2 54位时基计数器
2.3 警报器
3. 周期性触发配置
3.1 LED初始化配置
3.2 创建和启动定时器
3.3 定时器警报配置
3.4 警报事件配置
3.5 完整代码
1. 概述
通用定时器可用于准确设定时间间隔、在一定间隔后触发(周期或非周期的)中断或充当硬件时钟,ESP32-S3 包含两个定时器组,即定时器组0和定时器组1,每个定时器组有两个通用定时器和一个主系统看门狗定时器。所有通用定时器均基于16位预分频器和54位可自动重新加载向上/向下计数器:
定时器具有如下功能:
• 16 位时钟预分频器,分频系数为2到65536
• 54位时基计数器可配置成递增或递减
• 可读取时基计数器的实时值
• 暂停和恢复时基计数器
• 可配置的报警产生机制
• 计数器值重新加载(报警时自动重新加载或软件控制的即时重新加载)
• 电平触发中断
2. 架构介绍
定时器组(下文为了方便描述用Tx 表示,x为0或1,代表定时器组0或者组1),Tx包含时钟选择器、一个16位整数预分频器、一个时基计数器和一个用于产生警报的比较器:
大概整理一下,如有错误请指正:
2.1 16 位预分频器与时钟选择器
首先对于框住的部分,每个定时器 可通过配置寄存器 TIMG_TxCONFIG_REG 的 TIMG_Tx_USE_XTAL 字段,选择APB时钟 (APB_CLK) 或外部时钟 (XTAL_CLK) 作为时钟源。其中0:使用 APB_CLK 作为定时器组的源时钟;1:使用 XTAL_CLK 作为定时器组的源时钟,如下图:
并且从上图我们可以看到 TIMG_Tx_DIVIDER 所占位数是13~28位,也就是16位预分频器。时钟源经16位预分频器分频,产生时基计数器使用的时基计数器时钟 (TB_CLK)。
这里需要注意:
16 位预分频器的分频系数可通过TIMG_Tx_DIVIDER字段配置,选取从2到65536之间的任意值。注意,将TIMG_Tx_DIVIDER 置 0后,分频系数会变为65536。TIMG_Tx_DIVIDER置1时,实际分频系数为2,计数器的值为实际时间的一半。
2.2 54位时基计数器
54 位时基计数器基于TB_CLK,可通过TIMG_Tx_INCREASE字段配置为递增或递减。时基计数器可通过置位或清零TIMG_Tx_EN 字段使能或关闭。使能时,时基计数器的值会在每个TB_CLK周期递增或递减。关闭时,时基计数器暂停计数。注意,TIMG_Tx_EN置位后,TIMG_Tx_INCREASE字段还可以更改,时基计数器可立即改变计数方向。
2.3 警报器
54 位报警值可在TIMG_TxALARMLO_REG和TIMG_TxALARMHI_REG 配置,两者分别代表报警值的低32位和高22位。但是,只有置位TIMG_Tx_ALARM_EN字段使能报警功能后,配置的报警值才会生效。为解决报警使 能“过晚”(即报警使能时,定时器的值已过报警值),可逆计数器向上计数时,若定时器的当前值高于报警值(在一定范围内),或可逆计数器向下计数时,定时器的当前值低于报警值(在一定范围内),硬件都会立即触发报警。
3. 周期性触发配置
3.1 LED初始化配置
创建项目,初始化GPIO口,这里不在详细的描述,想要详细了解可以查看:
ESP32入门开发·VScode空白项目搭建·点亮一颗LED灯-CSDN博客
就是初始化配置一些GPIO:
#define LED_GPIO_PIN GPIO_NUM_1 // 定义LED连接的GPIO引脚号void LED_Init(void)
{gpio_config_t io_conf; // 定义GPIO配置结构体// 设置GPIO配置参数io_conf.pin_bit_mask = (1ULL << LED_GPIO_PIN); // 设置引脚位掩码(64位无符号长整型)io_conf.mode = GPIO_MODE_INPUT_OUTPUT; // 设置为输入输出模式io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // 禁用下拉电阻io_conf.pull_up_en = GPIO_PULLUP_DISABLE; // 禁用上拉电阻io_conf.intr_type = GPIO_INTR_DISABLE; // 禁用中断功能// 应用GPIO配置gpio_config(&io_conf);
}
3.2 创建和启动定时器
注意先包含头文件:
#include <driver/gptimer.h>
首先初始化一个定时器句柄 gptimer_handle_t ,方便后续调用管理,初始化值为0,然后对gptimer_config_t 值进行配置,这里我选择默认的时钟源,计数方式选择向上计数,频率选择为1MHz:
gptimer_handle_t gptimer = NULL;gptimer_config_t timer_config =
{.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒
};
对于 gptimer_config_t 其函数原型为:
/*** @brief General Purpose Timer configuration*/
typedef struct {gptimer_clock_source_t clk_src; /*!< GPTimer clock source */gptimer_count_direction_t direction; /*!< Count direction */uint32_t resolution_hz; /*!< Counter resolution (working frequency) in Hz,hence, the step size of each count tick equals to (1 / resolution_hz) seconds */int intr_priority; /*!< GPTimer interrupt priority,if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */struct {uint32_t intr_shared: 1; /*!< Set true, the timer interrupt number can be shared with other peripherals */} flags; /*!< GPTimer config flags*/
} gptimer_config_t;
这里我们只用了一些基础配置。
将数据写入:
gptimer_new_timer(&timer_config, &gptimer);// 创建定时器实例
对于使能和启动定时器比较简单:
// 使能定时器gptimer_enable(gptimer);// 启动定时器gptimer_start(gptimer);
3.3 定时器警报配置
调用 gptimer_alarm_config_t 函数当,定时器报警事件发生,定时器自动重装载到0,定时器报警周期因为我们刚刚设定频率为1MHz,也就是时钟计次数位1us,那么我们现在想要一秒重装载一次,就需要1000000us,也就是计1000000次数:
gptimer_alarm_config_t alarm_config = {.reload_count = 0, // 当警报事件发生时,定时器会自动重载到 0.alarm_count = 1000000, // 设置实际的警报周期,因为分辨率是 1us,所以 1000000 代表 1s.flags.auto_reload_on_alarm = true, // 使能自动重载功能};
通过调用 gptimer_set_alarm_action 函数将数据配置到定时器当中:
gptimer_set_alarm_action(gptimer, &alarm_config);// 设置定时器的警报动作
gptimer_alarm_config_t 函数原型:
/*** @brief General Purpose Timer alarm configuration*/
typedef struct {uint64_t alarm_count; /*!< Alarm target count value */uint64_t reload_count; /*!< Alarm reload count value, effect only when `auto_reload_on_alarm` is set to true */struct {uint32_t auto_reload_on_alarm: 1; /*!< Reload the count value by hardware, immediately at the alarm event */} flags; /*!< Alarm config flags*/
} gptimer_alarm_config_t;
3.4 警报事件配置
既然已经发生报警了,那么发生报警好需要完成警示,也就是报警事件,我们想创建一个警报器触发后需要做什么,我们这里将电平翻转一下:
gpio_level = gpio_level?0:1;gpio_set_level(LED_GPIO_PIN,gpio_level);
ESP32-S3在回调函数声明前,需要调用 gptimer_event_callbacks_t 其是一个结构体,去表示需要掉用的结构体是哪个:
/*** @brief Group of supported GPTimer callbacks* @note The callbacks are all running under ISR environment* @note When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.*/
typedef struct {gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */
} gptimer_event_callbacks_t;
/*** @brief Timer alarm callback prototype** @param[in] timer Timer handle created by `gptimer_new_timer`* @param[in] edata Alarm event data, fed by driver* @param[in] user_ctx User data, passed from `gptimer_register_event_callbacks`* @return Whether a high priority task has been waken up by this function*/
typedef bool (*gptimer_alarm_cb_t) (gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
这里我们根据结构体定义创建一个回调函数,用来使电平翻转:
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{gpio_level = gpio_level?0:1;gpio_set_level(LED_GPIO_PIN,gpio_level);return true;
}
这样结构体即为:
gptimer_event_callbacks_t cbs = {.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数};
当定时器触发特定事件(如警报事件)时,将调用用户定义的回调函数:
// 注册定时器事件回调函数,允许携带用户上下文gptimer_register_event_callbacks(gptimer, &cbs, NULL);
3.5 完整代码
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"#include <driver/gptimer.h>#define LED_GPIO_PIN GPIO_NUM_1
bool gpio_level = false;static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{gpio_level = gpio_level?0:1;gpio_set_level(LED_GPIO_PIN,gpio_level);return true;
}void LED_Init(void)
{gpio_config_t io_conf;io_conf.pin_bit_mask = (1ULL << LED_GPIO_PIN);io_conf.mode = GPIO_MODE_INPUT_OUTPUT;io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;io_conf.pull_up_en = GPIO_PULLUP_DISABLE;io_conf.intr_type = GPIO_INTR_DISABLE;gpio_config(&io_conf);
}void Timer_Init(void)
{gptimer_handle_t gptimer = NULL;gptimer_config_t timer_config = {.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒};gptimer_new_timer(&timer_config, &gptimer);// 创建定时器实例gptimer_alarm_config_t alarm_config = {.reload_count = 0, // 当警报事件发生时,定时器会自动重载到 0.alarm_count = 1000000, // 设置实际的警报周期,因为分辨率是 1us,所以 1000000 代表 1s.flags.auto_reload_on_alarm = true, // 使能自动重载功能};gptimer_set_alarm_action(gptimer, &alarm_config);// 设置定时器的警报动作gptimer_event_callbacks_t cbs = {.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数};// 注册定时器事件回调函数,允许携带用户上下文gptimer_register_event_callbacks(gptimer, &cbs, NULL);// 使能定时器gptimer_enable(gptimer);// 启动定时器gptimer_start(gptimer);
}void app_main(void)
{LED_Init();Timer_Init();while(1) {vTaskDelay(pdMS_TO_TICKS(10));//注意这里必须要有东西,否则看门狗会一直被调用}
}
下载调试会发现,LED灯按照1s闪烁一次。
ESP32学习笔记_时光の尘的博客-CSDN博客