08-GPtimer

news2024/11/25 3:01:36

通用定时器 (GPTimer)

通用定时器简介

通用定时器可用于准确设定时间间隔、在一定间隔后触发(周期或非周期的)中断或充当硬件时钟。如下图所示,ESP32-S3 包含两个定时器组,即定时器组 0 和定时器组 1。每个定时器组有两个通用定时器(T0,T1)和一个主系统看门狗定时器(WDT)。所有通用定时器均基于 16 位预分频器和 54 位可自动重新加载向上/向下计数器。
在这里插入图片描述

通用定时器架构

在这里插入图片描述

  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_EN必须清零),才能更改16位预分频器。在定时器使能时更改16位预分频器会造成不可预知的结果。

  3. 54位时基计数器:54位时基计数器基于TB_CLK,可通过TIMG_Tx_INCREASE字段配置为递增或递减。时基计数器可通过置位或清零TIMG_Tx_EN字段使能或关闭。使能时,时基计数器的值会在每个TB_CLK周期递增或递减。关闭时,时基计数器暂停计数。注意,TIMG_Tx_EN置位后,TIMG_Tx_INCREASE字段还可以更改,时基计数器可立即改变计数方向。

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

更多内容可以查看一下官方手册
通用定时器手册

GPtimer的API参考

1. 头文件

#include "driver/gptimer.h"

2. 创建通用计时器

esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)

创建一个新的通用计时器,并返回句柄。

注意:

新创建的计时器处于“init”状态。

函数参数:

参数描述
config[输入] GPTimer 配置
ret_timer[输出] 返回的计时器句柄

函数返回值:

返回值描述
ESP_OK成功创建 GPTimer
ESP_ERR_INVALID_ARG由于参数无效,创建 GPTimer 失败
ESP_ERR_NO_MEM创建 GPTimer 失败,因为内存不足
ESP_ERR_NOT_FOUND创建 GPTimer 失败,因为所有硬件计时器都已用完,不再有空闲计时器
ESP_FAIL由于其他错误,创建 GPTimer 失败

该函数使用 gptimer_config_t 类型的结构体变量传入 gptimer 外设的配置参数,该结构体的
定义如下所示:

/**
* @brief 通用定时器配置
*/
typedef struct {
 gptimer_clock_source_t clk_src; /* 通用定时器时钟源 */
 gptimer_count_direction_t direction; /* 计数方向 */
 uint32_t resolution_hz; /* 计数器分辨率(工作频率) */
 struct {
 uint32_t intr_shared: 1;/* 若置为 1,则计时器中断编号可以与其他外围设备共享 */
 } flags; /* 通用定时器配置标志 */
} gptimer_config_t;

该函数的使用实例如下

#include "driver/gpio.h"
void example_fun(void)
{
gptimer_config_t g_tim_handle;
gptimer_handle_t g_tim = NULL;
g_tim_handle.clk_src = GPTIMER_CLK_SRC_DEFAULT; /* 选择定时器时钟源 */
g_tim_handle.direction = GPTIMER_COUNT_UP; /* 递增计数模式 */
g_tim_handle.resolution_hz = resolution; /* 计数器分辨率 */
gptimer_new_timer(&g_tim_handle, &g_tim); /* 创建新的通用定时器 */
}

3. 配置通用定时器

esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value);

配置通用定时器的计数值以及定时器周期。

函数形参描述:

形参描述
timer计时器句柄由 gptimer_new_timer 创建
value要设置的计数值

函数返回值描述:

返回值描述
ESP_OK返回 0,表示配置成功
ESP_ERR_INVALID_ARG由于参数无效,获取 GPTimer 原始计数值失败
ESP_FAIL由于其他错误,设置 GPTimer 原始计数值失败

示例用法:

#include "driver/gpio.h"

void example_fun(void) {
    gptimer_handle_t g_tim = NULL;
    gptimer_set_raw_count(g_tim, 100); 
}

4. 注册用户回调函数

g_tim_callbacks.on_alarm = gptimer_callback;

该结构体用于注册用户回调函数,并没有涉及到参与返回值,其调用的 gptimer_callback()

该结构体使用 gptimer_event_callbacks_t 类型的结构体对 gptimer 报警进行参数配置,该结
构体的定义如下所示:

/**
* @brief 支持的 GPTimer 回调组
* @note 回调都在 ISR 环境下运行
* @note 当 CONFIG_GPTIMER_ISR_IRAM_SAFE 被启用时,
回调本身及其调用的函数应该被放置在 IRAM 中。
*/
typedef struct {
 gptimer_alarm_cb_t on_alarm; /* 定时器报警回调 */
} gptimer_event_callbacks_t;

该结构体的使用示例,如下

#include "driver/gpio.h"
void example_fun(void)
{
gptimer_event_callbacks_t g_tim_callbacks;
g_tim_callbacks.on_alarm = gptimer_callback; /* 注册用户回调函数 */
}

配置通用定时器回调函数

esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data)

5. 创建一个消息队列,并引入一个事件

该函数用于创建一个新的队列实例,并返回一个句柄,通过该句柄可以引用新队列,其函数原型如下所示:

QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize );

函数形参描述:

形参描述
uxQueueLength队列可以包含的最大项目数
uxItemSize队列中每个项目所需的字节数。项目是按副本而非引用排队的,因此这是每个已发布项目将复制的字节数。队列中的每个项目的大小必须相同。

函数返回值描述:

如果成功创建了队列,则会返回新创建的队列的句柄。如果无法创建队列,则返回 0。

宏定义:

消息队列的调用方式用到了条件编译的方式,当满足条件编译所需要的条件时才会使用到该消息队列,同时该消息队列通过宏定义的方式进行调用。

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) \
    xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

注意事项:

在内部,在 FreeRTOS 实现中,队列使用两个内存块。第一个块用于保存队列的数据结构。第二个块用于保存放入队列中的项目。如果使用 xQueueCreate() 创建队列,那么两个内存块都会在函数内自动动态分配。如果使用 xQueueCreateStatic() 创建队列,则应用程序编写器必须提供队列将使用的内存。因此,xQueueCreateStatic() 允许在不使用任何动态内存分配的情况下创建队列。

使用示例:

#include "driver/gpio.h"

struct Example	//示例
{
    char ExampleID;
    char Data[20];
};

void example_fun(void *pvParameters)
{
    QueueHandle_t xQueue1, xQueue2;
 
    /* 创建一个能够包含 10 个 uint32_t 值的队列 */
    xQueue1 = xQueueCreate( 10, sizeof(uint32_t));
    if(xQueue1 == 0)
    {
        /* 队列未创建,不得使用 */
    }
 
    /* 创建一个能够包含 10 个指向消息结构的指针的队列 */
    /* 这些应该通过指针传递,因为它们包含大量数据 */
    xQueue2 = xQueueCreate( 10, sizeof(struct Example *));
    if(xQueue2 == 0)
    {
        /* 队列未创建,不得使用 */
    }
}

6. 定时器报警,设置报警动作

该函数用于配置通用定时器报事件警,其函数原型如下所示:

esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config);

函数形参描述:

形参描述
timer指向配置通用定时器的指针
config指向通用定时器报警配置的指针类型。报警配置,尤其是将配置设置为 NULL 意味着禁用报警功能

函数返回值描述:

返回值描述
ESP_OK返回:0,配置成功
ESP_ERR_INVALID_ARG无效参数
ESP_FAIL由于其他错误,创建 GPTimer 失败

gptimer_alarm_config_t 结构体:
该函数使用 gptimer_alarm_config_t 类型的结构体变量传入 gptimer 外设的报警配置参数

该结构体的定义如下

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

在这个结构体gptimer_alarm_config_t中,alarm_countreload_count的含义如下:

  • alarm_count:这是报警目标计数值。当定时器的计数值达到这个alarm_count设定的值时,会触发一个报警事件。

  • reload_count:这是报警重新加载计数值。当报警事件发生后,如果auto_reload_on_alarm被设置为1,那么定时器的计数值会立即被重置为reload_count设定的值。

如果定时器在达到alarm_count设定的值时触发一次报警,而不需要在报警后自动重置计数值。也就是说,这个定时器可能只需要运行一次,而不是周期性地运行。因此,reload_count就不需要被设置。

使用示例:

#include "driver/gpio.h"

void example_fun(void)
{
    gptimer_alarm_config_t alarm_config;
    alarm_config.alarm_count = 1000000; /* 报警目标计数值 */
    gptimer_set_alarm_action(g_tim, &alarm_config);
}

更多API接口可以去看一下官方文档
通用定时器 (GPTimer)

程序设计

实验将配置通用定时器间隔一定时间返回一次报警值,并通过串口或者 VSCode 终端显示报警值,具体步骤如下:
①:配置通用定时器
②:配置通用定时器的计数值以及定时器周期
③:注册用户回调函数
④:创建一个消息队列,并引入一个事件
⑤:定时器报警,设置报警动作

GPTIM.h

/**
 * @file GPTIM.h
 * @author 宁子希 (1589326497@qq.com)
 * @brief   通用定时器驱动代码
 * @version 0.1
 * @date 2024-04-17
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#ifndef __GPTIM_H_
#define __GPTIM_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "esp_log.h"

//参数引用
typedef struct{
    uint64_t event_count;
}gptimer_event_t;

extern QueueHandle_t queue;

//函数声明


void gptim_init(uint64_t count,uint16_t resolution);
bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data); 

#endif

GPTIM.c

/**
 * @file GPTIM.c
 * @author 宁子希 (1589326497@qq.com)
 * @brief   通用定时器驱动代码
 * @version 0.1
 * @date 2024-04-17
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "GPTIM.h"

QueueHandle_t queue;    //消息队列句柄


/**
 * @brief 初始化通用定时器
 * 
 * @param counts 计数值
 * @param resolution 定时器周期
 */
void gptim_init(uint64_t counts,uint16_t resolution){
    gptimer_config_t g_tim_handle;  //gptimer配置结构体
    gptimer_alarm_config_t alarm_config;    //报警配置结构体
    gptimer_event_callbacks_t g_tim_callbacks;  //回调函数配置结构体

    uint64_t count;

    //配置通用定时器
    ESP_LOGE("GPTIMER_ALARM", "配置通用定时器");                                         

    //创建定时器句柄
    gptimer_handle_t g_tim = NULL;

    //配置gptimer
    g_tim_handle.clk_src = GPTIMER_CLK_SRC_DEFAULT;  // 选择定时器时钟源                                  
    g_tim_handle.direction = GPTIMER_COUNT_UP;       // 递增计数模式         
    g_tim_handle.resolution_hz = resolution;         // 计数器分辨率
    
    //注册用户回调函数
    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_LOGE("GPTIMER_ALARM", "设置计数值");
    ESP_ERROR_CHECK(gptimer_set_raw_count(g_tim, counts));  //设置计数值
    ESP_LOGE("GPTIMER_ALARM", "获取计数值");
    ESP_ERROR_CHECK(gptimer_get_raw_count(g_tim, &count));  //获取计数值
    ESP_LOGE("GPTIMER_ALARM", "定时器计数值: %llu", count);

    // 注册事件回调函数
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(g_tim, &g_tim_callbacks, queue));  //配置通用定时器回调函数

    // 设置报警动作
    ESP_LOGE("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));      //启动通用定时器

}

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;
}

main.c

/**
 * @file main.c
 * @author 宁子希 (1589326497@qq.com)
 * @brief   通用定时器驱动代码
 * @version 0.1
 * @date 2024-04-18
 * 
 * @copyright Copyright (c) 2024
 * 
 */

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "led.h"
#include "gptim.h"

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_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);

}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣练习题(2024/4/14)

1接雨水 给定 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…

vue3 -- 项目使用自定义字体font-family

在Vue 3项目中使用自定义字体(font-family)的方法与在普通的HTML/CSS项目中类似。可以按照以下步骤进行操作: 引入字体文件: 首先,确保你的字体文件(通常是.woff、.woff2、.ttf等格式)位于项目中的某个目录下,比如src/assets/font/。 在全局样式中定义字体: 在你的全局…

mysql常见语法操作笔记

1. 数据库的基本操作 1.1. MYSQL登录与退出 D:\phpstudy_pro\Extensions\MySQL5.7.26\bin 输入 mysql -uroot -proot -h127.0.0.1 退出的三种方法 mysql > exit; mysql > quit; mysql > \q; 1.2. MYSQL数据库的一些解释 注意:数据库就相当于文件夹 …

IDEA 控制台中文乱码 4 种解决方案

前言 IntelliJ IDEA 如果不进行相关设置,可能会导致控制台中文乱码、配置文件中文乱码等问题,非常影响编码过程中进行问题追踪。本文总结了 IDEA 中常见的中文乱码解决方法,希望能够帮助到大家。 IDEA 中文乱码 解决方案 一、设置字体为支…

挣钱新玩法,一文带你掌握流量卡推广秘诀

手机流量卡推广项目是什么?听名字我相信大家就已经猜出来了,就是三大运营商为了开发新用户,发起的有奖推广活动,也是为了长期黏贴用户。在这个活动中,用户通过我们的渠道,就能免费办理低套餐流量卡&#xf…

Obsidian 插件安装

方法一: Obsidian 最简单的插件安装当然是通过第三方插件库进行搜索,但是由于魔法上网的问题,经常连不上github,或者下载不了,导致插件无法安装。 方法二: obsidian 社区插件汇总:Airtable -…

【第三十一篇】Autorize插件安装使用教程(结合Burp实现越权实战案例)

Burp Suite是一款功能强大的渗透测试工具,被广泛应用于Web应用程序的安全测试和漏洞挖掘中。 本专栏将结合实操及具体案例,带领读者入门、掌握这款漏洞挖掘利器 读者可订阅专栏:【Burp由入门到精通 |CSDN秋说】 文章目录 前言安装教程使用教程垂直越权垂直越权实战注意前言 …

群晖 NAS rsync 远程文件同步

客户机是外网的 Windows 11,服务器是群晖。 客户机上安装 WSL Alpine Linux 来运行 rsync 进行文件下载。Alpine 相对比 Ubuntu、Debian,要小巧轻量,占用存储空间少,启动速度也很快。 一、安装 WSL Alpine Linux 在 Windows 中&…

scala---基础核心知识(变量定义,数据类型,流程控制,方法定义,函数定义)

一、什么是scala Scala 是一种多范式的编程语言,其设计初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。 二、为什么要学习scala 1、优雅 2、速度快 3、能融合到hado…

ADOP-400G光模块问题发布会

前沿光学(ADOP)400G光模块为客户提供各种超高密度的400G以太网连接方案,广泛应用于数据中心、企业网和服务提供商。 📣📣以下一些问题是我们新一代400G光模块常能遇见问题,所以我们决定在这里开一场小小的…

ubuntu22安装宝塔面板

方法一:运行安装宝塔命令 wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec 安装成功后,需到服务器管理后台的安全组中配置新规则,放行宝塔面板的端口(以阿…

基于SSM和vue的机票订购管理系统

👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM和vue的机票订购管理系统2拥有两种角色 管理员:用户管理、机票管理、订票管理、公告管理、广告管理、系统管理、添加机票等 用户:登录注册、订票、查看公…

论文复现《SplaTAM: Splat, Track Map 3D Gaussians for Dense RGB-D SLAM》

前言 SplaTAM算法是首个开源的基于RGB-D数据,生成高质量密集3D重建的SLAM技术。 通过结合3DGS技术和SLAM框架,在保持高效性的同时,提供精确的相机定位和场景重建。 代码仓库:spla-tam/SplaTAM: SplaTAM: Splat, Track & Map 3…

MySQL表级锁——技术深度+1

引言 本文是对MySQL表级锁的学习,MySQL一直停留在会用的阶段,需要弄清楚锁和事务的原理并DEBUG查看。 PS:本文涉及到的表结构均可从https://github.com/WeiXiao-Hyy/blog中获取,欢迎Star! MySQL表级锁 MySQL中表级锁主要有表锁…

【简单介绍下PostCSS】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…

如何实现在 Windows 上运行 Linux 程序?

在Windows 上运行Linux程序是可以通过以下几种方法实现: 1.使用 Windows Subsystem for Linux (WSL): WSL是微软提供的功能,可以在Windows 10上运行一个完整的Linux系统。用户可以在Microsoft Store中安装所需的 在开始前我有一些资料,是我根据网友给的…

SQL --索引

索引 INDEX 伪列 伪装起来的列,不容易被看见,要特意查询才能看见 ROWNUM: 是对查询结果自动生成的一组连续的自然数序号。 SELECT emp.*,ROWNUM FROM emp例题:查询emp表中,前三个员工 SELECT * FROM * from emp w…

Midjourney 实现角色一致性的新方法

AI 绘画的奇妙之处,实乃令人叹为观止!就像大千世界中,寻不见两片完全相同的树叶一般,AI 绘画亦复如是。同一提示之词,竟能催生出千变万化的图像,使得AI所绘之作,宛如自然之物般独特,…

将百度网盘中数据集直接下载到服务器上

步骤: 1:下载安装bypy pip install bypybypy,是一个使用 python 编写的命令行百度网盘客户端 2:初始化 bypy info将这个链接复制到浏览器中打开 复制授权码,粘贴到服务器命令,回车 等待一会,会显示你云盘空间大小信…

【小白学机器学习14】确定零假设h0的技巧:先根据错误的严重程度确定第1类错误α,再确定零假设h0

目录 1 前言: 如何确定H0的逻辑思路 1.1 推导的原理 2 假设检验的2类错误 2.1 什么叫2类错误 2.2 这两类错误的计算公式 2.2.1 计算公式 2.2.2 第1类错误和第2类错误的特点 3 如何设计H0的一些例子和思考 3.1 很多书上的原则1:备择假设通常才是研究者最想证…