08. 通用定时器

news/2025/3/14 21:06:58/文章来源:https://www.cnblogs.com/FlurryHeart/p/18772875

一、什么是通用定时器

  ESP32 S3 芯片配备了两个通用定时器组,每组均包含两个通用定时器和一个主系统看门狗定时器。每个通用定时器都具备多个通道。通过明确指定定时器号和通道号,用户可以精准地选定所需的定时器和通道。每个定时器均支持独立编程,并且具备微秒级的精确时间中断生成能力。基本的定时器参数设置包括定时器号、通道号、预分频器配置、自动重新加载值的设定,以及定时器中断使能功能的开启。

定时器组

二、通用定时器架构

通用定时器架构

【1】、时钟选择器

  每个定时器可通过配置寄存器 TIMG_TxCONFIG_REGTIMG_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,计数器的值为实际时间的一半。

【3】、54 位时基计数器

  54 位时基计数器基于 TB_CLK,可通过 TIMG_Tx_INCREASE 字段配置为递增或递减。时基计数器可通过置位或清零 TIMG_Tx_EN 字段使能或关闭。使能时,时基计数器的值会在每个 TB_CLK 周期递增或递减。关闭时,时基计数器暂停计数。

  时基计数器 54 位定时器的当前值必须被锁入两个寄存器,才能被 CPU 读取。向 TIMG_TxUPDATE_REG 写入任意值时,54 位定时器的值开始被锁入寄存器 TIMG_TxLO_REGTIMG_TxHI_REG,两个寄存器分别锁存低 32 位和高 22 位。当 TIMG_TxUPDATE_REG 被硬件清零,表明锁存操作已经完成,可以从这两个寄存器中读取当前计数值。在 TIMG_TxUPDATE_REG 被写入新值之前,保持寄存器 TIMG_TxLO_REGTIMG_TxHI_REG 的值不变,以供 32 位的 CPU 读值。

TIMG_Tx_EN 置位后,TIMG_Tx_INCREASE 字段还可以更改,时基计数器可立即改变计数方向。

【4】、比较器

  定时器可配置为在当前值与报警值相同时触发报警。报警会产生中断,(可选择)让定时器的当前值自动重新加载。54 位报警值可在 TIMG_TxALARMLO_REGTIMG_TxALARMHI_REG 配置,两者分别代表报警值的低 32 位和高 22 位。但是,只有置位 TIMG_Tx_ALARM_EN 字段使能报警功能后,配置的报警值才会生效。为解决报警使能 “过晚”(即报警使能时,定时器的值已过报警值),可逆计数器向上计数时,若定时器的当前值高于报警值(在一定范围内),或可逆计数器向下计数时,定时器的当前值低于报警值(在一定范围内),硬件都会立即触发报警。

三、通用定时器常用函数

  ESP-IDF 提供了一套 API 来配置通用定时器。要使用此功能,需要导入必要的头文件:

#include "driver/gptimer.h"

3.1、配置通用定时器

  我们可以使用 gptimer_new_timer() 函数 配置通用定时器,其函数原型如下所示:

/*** @brief 配置通用定时器* * @param config 配置通用定时器指针* @param ret_timer 通用定时器句柄类型* @return esp_err_t ESP_OK表示配置成功,其它表示配置失败*/
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer);

  形参 config 是指向 gptimer_config_t 类型结构体的指针,它的结构体变量定义如下:

typedef struct 
{gptimer_clock_source_t clk_src;         // 选择定时器的时钟源gptimer_count_direction_t direction;    // 设置定时器的计数方向uint32_t resolution_hz;                 // 设置内部计数器的分辨率,每个计数器的步长等于(1/resolution_hz)秒int intr_priority;                      // 设置中断的优先级struct{uint32_t intr_shared: 1;            // 设置是否将定时器中断源标记为共享源} flags;
} gptimer_config_t;

  成员 clk_src 用来 选择定时器的时钟源,它的可选值如下:

typedef enum 
{GPTIMER_CLK_SRC_APB = SOC_MOD_CLK_APB,     /*!< Select APB as the source clock */GPTIMER_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL,   /*!< Select XTAL as the source clock */GPTIMER_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB, /*!< Select APB as the default choice */
} soc_periph_gptimer_clk_src_t;
typedef soc_periph_gptimer_clk_src_t gptimer_clock_source_t;

  成员 direction 用来 设置定时器的计数方向,它的可选值如下:

typedef enum 
{GPTIMER_COUNT_DOWN, /*!< Decrease count value */GPTIMER_COUNT_UP,   /*!< Increase count value */
} gptimer_count_direction_t;

  成员 resolution_hz 用来 设置内部计数器的分辨率,计数器每滴答一次相当于 \(\frac{1}{resolution\_hz}\) 秒。

  成员 intr_priority 用来 设置中断的优先级。若设置为 0,则会分配一个默认优先级的中断,否则会使用指定的优先级。

  成员 intr_shared 用来 设置是否将定时器中断源标记为共享源。默认为 1。如果该参数设置为 true。那么多个定时器将共享同一个中断源。这意味着当其中一个定时器触发中断时,相应的中断处理程序将被调用,并且不同的定时器中断可以在同一个中断服务函数中进行处理。而如果 intr_shared参数设置为 false,那么每个定时器都会有独立的中断源,并且将有一个相应的中断服务函数用于处理每个定时器的中断。

3.2、设置原始计数值

  我们可以使用 gptimer_set_raw_count() 函数 配置通用定时器的原始计数值,其函数原型如下所示:

/*** @brief 设置定时器的计数值* * @param timer 定时器句柄* @param value 要设置的计数值* @return esp_err_t ESP_OK表示配置成功,其它表示配置失败*/
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value);

3.3、注册回调函数

  我们可以使用 gptimer_register_event_callbacks() 函数 注册用户回调函数,其函数原型如下所示:

/*** @brief 注册回调函数* * @param timer 定时器句柄* @param cbs 需要绑定的回调函数* @param user_data 用户的数据,将直接传递给回调函数* @return esp_err_t ESP_OK表示配置成功,其它表示配置失败*/
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data);

3.4、设置定时器报警动作

  我们可以使用 gptimer_set_alarm_action() 函数用于 配置通用定时器报警事件,其函数原型如下所示:

/*** @brief 设置定时器报警动作* * @param timer 定时器句柄* @param config 报警配置* @return esp_err_t ESP_OK表示配置成功,其它表示配置失败*/
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config);

  形参 config 是 gptimer_alarm_config_t 类型的结构体变量的指针,该结构体的定义如下所示:

typedef struct
{uint64_t alarm_count;                   // 报警目标计数值uint64_t reload_count;                  // 报警重新加载计数值struct {uint32_t auto_reload_on_alarm: 1;   // 报警事件发生后立即通过硬件重新加载计数值} flags;                                // 报警配置标志
} gptimer_alarm_config_t;

3.5、使能定时器

  我们可以使用 gptimer_enable() 函数来 使能定时器,它的函数原型如下:

/*** @brief 使能定时器* * @param timer 定时器句柄* @return esp_err_t ESP_OK表示使能成功,其它表示使能失败*/
esp_err_t gptimer_enable(gptimer_handle_t timer);

该函数将把定时器驱动程序的状态从初始化切换为使能状态,如果 gptimer_register_event_callbacks() 已经延迟安装回调服务函数,此函数将使能回调服务函数。

3.6、启动定时器

  我们可以使用 gptimer_start() 函数来 启动定时器,它的函数原型如下:

/*** @brief 开启定时器* * @param timer 定时器句柄* @return esp_err_t ESP_OK表示开启成功,其它表示开启失败*/
esp_err_t gptimer_start(gptimer_handle_t timer);

四、实验例程

  我们修改【components】文件夹下的【peripheral】文件夹下的【inc】文件夹下的 bsp_timer.h 文件和【components】文件夹下的【peripheral】文件夹下的【src】文件夹下 bsp_timer.c 文件。

#ifndef __BSP_TIMER_H__
#define __BSP_TIMER_H__#include "driver/gpio.h"
#include "driver/gptimer.h"extern esp_timer_handle_t g_esp_timer_handle;
extern gptimer_handle_t g_gptimer_handle;void bsp_esp_timer_init(esp_timer_handle_t *handle);
void bsp_gptimer_init(gptimer_handle_t *handle, uint32_t resolution, uint64_t alarm_count, gptimer_alarm_cb_t gptimer_alarm_cbbs);
bool gptimer_alarm_callback(gptimer_handle_t handle, const gptimer_alarm_event_data_t *edata, void *user_data);#endif // !__BSP_TIMER_H__
gptimer_handle_t g_gptimer_handle;static void esp_timer_callback(void *arg);/*** @brief 通用定时器初始化* * @param handle 通用定时器句柄* @param resolution 定时器分辨率* @param alarm_count 报警目标计数值* @param gptimer_alarm_cbbs 报警回调函数* * @note 计数器每滴答一次相当于 1 / resolution 秒,IDF文档要求该值 2 < clk / resolution < 65536*/
void bsp_gptimer_init(gptimer_handle_t *handle, uint32_t resolution, uint64_t alarm_count, gptimer_alarm_cb_t gptimer_alarm_cbbs)
{gptimer_config_t gptimer_config = {0};gptimer_alarm_config_t gptimer_alarm_config = {0};gptimer_event_callbacks_t gptimer_event_callback = {0};// 配置通用定时器gptimer_config.clk_src = GPTIMER_CLK_SRC_DEFAULT;                           // 选择定时器时钟源gptimer_config.direction = GPTIMER_COUNT_UP;                                // 递增计数模式gptimer_config.resolution_hz = resolution;                                  // 定时器分辨率gptimer_new_timer(&gptimer_config, handle);                                 // 创建新的通用定时器,并返回句柄// 设置原始计数值gptimer_set_raw_count(*handle, alarm_count);gptimer_alarm_config.alarm_count = alarm_count;                             // 报警目标计数值gptimer_alarm_config.flags.auto_reload_on_alarm = true;                     // 当报警发生时,是否自动重载gptimer_set_alarm_action(*handle, &gptimer_alarm_config);                   // 设置报警动作// 设置报警回调函数gptimer_event_callback.on_alarm = gptimer_alarm_cbbs;gptimer_register_event_callbacks(*handle, &gptimer_event_callback, NULL);   // 注册事件回调函数gptimer_enable(g_gptimer_handle);                                           // 使能定时器
}/*** @brief 定时器报警回调函数* * @param handle 定时器句柄* @param edata 报警事件数据* @param user_data 用户传递给报警回调函数的参数* @return true 有高优先级的任务被唤醒* @return false 没有高优先级的任务被唤醒*/
bool gptimer_alarm_callback(gptimer_handle_t handle, const gptimer_alarm_event_data_t *edata, void *user_data)
{gpio_set_level(GPIO_NUM_0, !gpio_get_level(GPIO_NUM_0));return false;
}

  修改【main】文件夹下的 main.c 文件。

#include "freertos/FreeRTOS.h"#include "bsp_timer.h"#include "led.h"// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{led_init(GPIO_NUM_0);bsp_gptimer_init(&g_gptimer_handle, 1000000, 1000000, gptimer_alarm_callback);gptimer_start(g_gptimer_handle);while (1){// 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数vTaskDelay(pdMS_TO_TICKS(10));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/898871.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全链路赋能游戏鸿蒙化适配,鸿蒙游戏开发者服务焕新升级

3月14日,华为游戏中心在成都开展了鸿蒙游戏开发者服务日线下活动。本次活动吸引了百余位游戏厂商代表以及开发者参与。华为一线技术专家团队与众多游戏开发者进行了面对面的深入交流,聚焦游戏鸿蒙化全流程技术实践,通过专家授课、案例解析与现场互动,为开发者提供从技术适配…

多线程程序设计(三)——Guarded Suspension

本文摘要了《Java多线程设计模式》一书中提及的 Guarded Suspension 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。 ◆ 适用场景 当线程访问的共享数据没有准备好时,让该线程进入等待状态,直到数据被准备好…

西部数据企业级硬盘HC310开盘数据恢复,300G左右数据量耗时半年

这块西数4T企业级硬盘HC310是杭州某研究所送过来的,突发损坏不识别,通电后咯吱咯吱敲盘异响,磁头坏了。这款企业级硬盘目前开盘成功率一般,因为磁头适配很困难,需要反复更换磁头,备件成本很高。这种硬盘的开盘难度跟服务器SCSI或SAS硬盘有的一拼,没有经验甚至拆一个废一…

5分钟,构建国产数据库智能体

近期,圈里很多朋友,都尝试利用 DeepSeek 构建自己的智能体。我也利用腾讯元器,将个人公众号内容做了个智能体,可以实现简单的问答。那么延展来看,智能体除了可利用公众号内容,也可使用离线文件等方式来构建。这不禁让我考虑,是否可用这样方式构造一个数据库智能体。说干…

Ubuntu 22.04 LTS 基于 Docker 部署 WordPress

Ubuntu 22.04 LTS 基于 Docker 部署 WordPress 1. 引言 WordPress 是全球最受欢迎的内容管理系统 (CMS),使用 Docker 可以简化其部署过程。本教程将介绍如何在 Ubuntu 22.04 LTS 上使用 Docker 部署 WordPress。2. WordPress 简介 2.1 WordPress 是什么? WordPress 是全球最流…

7.接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例1:输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示…

K近邻算法等

1. KNN算法和KD - tree总结 1.1 KNN算法 模型 K近邻(K - Nearest Neighbors,KNN)算法是一种基本的分类与回归方法。它的模型实际上是对特征空间的划分,给定一个训练数据集,对于新的输入实例,在训练数据集中找到与该实例最邻近的 \(K\) 个实例,然后根据这 \(K\) 个实例的…

正则表达式--java进阶day06

1.正则表达式2.正则表达式的规则、使用3.字符类讲解如图,单独一个a满足正则表达式的规则,所以返回true当删去[]后,正则表达式中的规则就会变为必须是abc,否则不满足条件,即使有一个a该规则是指a-d或者m-p,可以写成[a-dm-p]4.预定义字符类注意事项 正则表达式中存在数量问…

探秘Transformer系列之(13)--- FFN

从零开始解析Transformer,目标是:(1) 解析Transformer如何运作,以及为何如此运作,让新同学可以入门;(2) 力争融入一些比较新的或者有特色的论文或者理念,让老鸟也可以有所收获。探秘Transformer系列之(13)--- FFN 目录探秘Transformer系列之(13)--- FFN0x00 概述0x01…

EXCEL-时间函数

💖简介 在Excel中,时间函数用于处理和操作日期和时间数据; 以下是Excel中常用的时间函数及其常见应用场景的总结.📖函数 ⭐时间函数基础 🌟TIME语法:TIME(hour, minute, second) 功能:将小时、分钟、秒转换为时间序列号(0到0.99999999之间的数值)。 示例:TIME(9,30…

day29linux三剑客----sed

day29linux三剑客----sed单个正则字符还认识组合到一起就晕了,怎么办?本质还是对单个字符没理解.认识*认识.*组合到就一起就蒙了,为什么?还是没想明白.的意义,*的意义正则表达式,从左向右,逐步理解单个字符的意义怎么做? 1.思维脑图写没写? 2.每一个正则表达式的符号,…

3.14 学习记录

基于Android Studio 完成了简单的石家庄地铁购票APP