ESP32入门开发·通用硬件定时器 (GPTimer)

目录

[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口,这里不在详细的描述,想要详细了解可以查看:

ESP32入门开发·VScode空白项目搭建·点亮一颗LED灯-CSDN博客

就是初始化配置一些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闪烁一次。

ESP32学习笔记_时光の尘的博客-CSDN博客