
目录
[1. 概述](#1. 概述)
[2. 架构介绍](#2. 架构介绍)
[2.1 16 位预分频器与时钟选择器](#2.1 16 位预分频器与时钟选择器)
[2.2 54位时基计数器](#2.2 54位时基计数器)
[2.3 警报器](#2.3 警报器)
[3. 周期性触发配置](#3. 周期性触发配置)
[3.1 LED初始化配置](#3.1 LED初始化配置)
[3.2 创建和启动定时器](#3.2 创建和启动定时器)
[3.3 定时器警报配置](#3.3 定时器警报配置)
[3.4 警报事件配置](#3.4 警报事件配置)
[3.5 完整代码](#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口,这里不在详细的描述,想要详细了解可以查看:
就是初始化配置一些GPIO:
cpp
#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 创建和启动定时器
注意先包含头文件:
cpp
#include <driver/gptimer.h>
首先初始化一个定时器句柄 gptimer_handle_t ,方便后续调用管理,初始化值为0,然后对gptimer_config_t 值进行配置,这里我选择默认的时钟源,计数方式选择向上计数,频率选择为1MHz:
cpp
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 其函数原型为:
cpp
/**
* @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;
这里我们只用了一些基础配置。
将数据写入:
cpp
gptimer_new_timer(&timer_config, &gptimer);// 创建定时器实例
对于使能和启动定时器比较简单:
cpp
// 使能定时器
gptimer_enable(gptimer);
// 启动定时器
gptimer_start(gptimer);
3.3 定时器警报配置
调用 gptimer_alarm_config_t 函数当,定时器报警事件发生,定时器自动重装载到0,定时器报警周期因为我们刚刚设定频率为1MHz,也就是时钟计次数位1us,那么我们现在想要一秒重装载一次,就需要1000000us,也就是计1000000次数:
cpp
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 函数将数据配置到定时器当中:
cpp
gptimer_set_alarm_action(gptimer, &alarm_config);// 设置定时器的警报动作
gptimer_alarm_config_t 函数原型:
cpp
/**
* @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 警报事件配置
既然已经发生报警了,那么发生报警好需要完成警示,也就是报警事件,我们想创建一个警报器触发后需要做什么,我们这里将电平翻转一下:
cpp
gpio_level = gpio_level?0:1;
gpio_set_level(LED_GPIO_PIN,gpio_level);
ESP32-S3在回调函数声明前,需要调用 gptimer_event_callbacks_t 其是一个结构体,去表示需要掉用的结构体是哪个:
cpp
/**
* @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;
cpp
/**
* @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);
这里我们根据结构体定义创建一个回调函数,用来使电平翻转:
cpp
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;
}
这样结构体即为:
cpp
gptimer_event_callbacks_t cbs =
{
.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数
};
当定时器触发特定事件(如警报事件)时,将调用用户定义的回调函数:
cpp
// 注册定时器事件回调函数,允许携带用户上下文
gptimer_register_event_callbacks(gptimer, &cbs, NULL);
3.5 完整代码
cpp
#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闪烁一次。
