搜索
bottom↓
回复: 0

《DNESP32S3使用指南-IDF版_V1.6》第十五章 GPTIMER实验

[复制链接]

出0入234汤圆

发表于 4 天前 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2024-7-18 10:03 编辑

2.jpg
1)实验平台:正点原子ESP32S3开发板
2)购买链接:https://detail.tmall.com/item.htm?id=768499342659
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-347618-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学ESP32S3快速入门视频教程:https://www.bilibili.com/video/BV1sH4y1W7Tc
6)正点原子FPGA交流群:132780729
1.png
3.png

第十五章 GPTIMER实验

       通用定时器可用于准确设定时间间隔、在一定间隔后触发(周期或非周期的)中断或充当硬件时钟。通过本章的学习,开发者将学习到通用定时器的使用。
       本章分为如下几个小节:
       15.1 定时器简介
       15.2 硬件设计
       15.3 程序设计
       15.4 下载验证

       15.1 定时器简介
       在上一章节中,我们介绍了定时器的一些基础知识,在这里便不再详细赘述了,请读者们自行阅读上一章节的内容,这一章节我们介绍一下ESP32的通用定时器。

       1,ESP32-S3的定时器基本参数
       ESP32-S3芯片配备了两个通用定时器组,每组均包含两个通用定时器(例如Timer0、Timer1等),且每个定时器都具备多个通道。通过明确指定定时器号和通道号,用户可以精准地选定所需的定时器和通道。每个定时器均支持独立编程,并且具备微秒级的精确时间中断生成能力。基本的定时器参数设置包括定时器号、通道号、预分频器配置、自动重新加载值的设定,以及定时器中断使能功能的开启。这些参数为用户提供了丰富的配置选项,以满足不同应用场景的需求。

第十五章 GPTIMER实验463.png
图15.1.1 定时器组

       2,ESP32-S3的通用定时器架构
       我们先来学习通用定时器架构,通过学习通用定时器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。

第十五章 GPTIMER实验554.png
图15.1.2 通用定时器架构


       (1)时钟选择器
       每个定时器可通过配置寄存器TIMG_TxCONFIG_REG的TIMG_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,计数器的值为实际时间的一半。
       需要注意的是:定时器必须关闭(即TIMG_Tx_EN必须清零),才能更改16位预分频器。在定时器使能时更改16位预分频器会造成不可预知的结果。

       (3)54位时基计数器
       54位时基计数器基于TB_CLK,可通过TIMG_Tx_INCREASE字段配置为递增或递减。时基计数器可通过置位或清零TIMG_Tx_EN字段使能或关闭。使能时,时基计数器的值会在每个TB_CLK周期递增或递减。关闭时,时基计数器暂停计数。注意,TIMG_Tx_EN置位后,TIMG_Tx_INCREASE字段还可以更改,时基计数器可立即改变计数方向。
       时基计数器54位定时器的当前值必须被锁入两个寄存器,才能被CPU读取(因为CPU为32位)。向TIMG_TxUPDATE_REG写入任意值时,54位定时器的值开始被锁入寄存器TIMG_TxLO_REG和TIMG_TxHI_REG,两个寄存器分别锁存低32位和高22位。当TIMG_TxUPDATE_REG被硬件清零,表明锁存操作已经完成,可以从这两个寄存器中读取当前计数值。在TIMG_TxUPDATE_REG被写入新值之前,保持寄存器TIMG_TxLO_REG和TIMG_TxHI_REG的值不变,以供32位的CPU读值。

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

       15.2 硬件设计

       15.2.1 例程功能
       实现现象:程序运行后配置通用定时器,在一定的周期内触发报警事件。

       15.2.2 硬件资源

       1. LED
              LED - IO1

       2. 通用定时器

       15.2.3 原理图
       本章实验使用的通用定时器为ESP32-S3的片上资源,因此没有对应的连接原理图。

       15.3 程序设计

       15.3.1 程序流程图
       程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:

第十五章 GPTIMER实验1911.png
图15.3.1.1 GPTIMER实验程序流程图

       15.3.2 GPTIMER函数解析
       ESP-IDF提供了一套API来配置通用定时器。要使用此功能,需要导入必要的头文件:
  1. #include "driver/gptimer.h"
复制代码
       接下来,作者将介绍一些常用的GPTIMER函数,这些函数的描述及其作用如下:

       1,配置通用定时器
       该函数用于配置通用定时器,其函数原型如下所示:
  1. esp_err_t gptimer_new_timer(const gptimer_config_t *config,
  2. gptimer_handle_t *ret_timer);
复制代码
       该函数的形参描述,如下表所示:

1.png
表15.3.2.1 函数gptimer_new_timer()形参描述

       返回值:ESP_OK表示配置成功。其他表示配置失败。
       下表是gptimer_config_t类型的结构体变量描述。

2.png
表15.3.2.2 gptimer_config_t结构体参数值描述

       完成上述结构体参数配置之后,可以将结构传递给 gptimer_new_timer() 函数,用以实例化定时器实例并返回定时器句柄。

       2,计数值以及定时器周期
       该函数用于配置通用定时器的计数值以及定时器周期,其函数原型如下所示:
  1. esp_err_t gptimer_set_raw_count(gptimer_handle_t timer,
  2.                                                                  unsigned long long value);
复制代码
       该函数的形参描述,如下表所示:

3.png
表15.3.2.3 函数gptimer_set_raw_count()形参描述

       返回值:ESP_OK表示配置成功,其他表示配置失败。

       3,注册用户回调函数
       该结构体用于注册用户回调函数,其结构体原型如下所示:
  1. esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer,
  2.                                                                                         const gptimer_event_callbacks_t *cbs,                                                                                         void *user_data);
复制代码
       该函数的形参描述,如下表所示:

4.png
表15.3.2.4 函数gptimer_register_event_callbacks ()形参描述

       返回值:ESP_OK表示配置成功。其他表示配置失败。
       当定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,便可通过该函数将函数挂载至中断服务例程(ISR)。

       4,定时器报警,设置报警动作
       该函数用于配置通用定时器报事件警,其函数原型如下所示:
  1. esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer,
  2.                                                                      const gptimer_alarm_config_t *config);
复制代码
       该函数的形参描述,如下表所示:

5.png
表15.3.2.5 函数gptimer_set_alarm_action()形参描述

       返回值:ESP_OK表示配置成功,其他表示失败。
       该函数使用gptimer_alarm_config_t类型的结构体变量传入gptimer外设的报警配置参数,该结构体的定义如下所示:
  1. /**
  2. * @brief 通用定时器报警配置
  3. */
  4. typedef struct {
  5.     uint64_t alarm_count;                  /* 报警目标计数值 */
  6.     uint64_t reload_count;                 /* 报警重新加载计数值 */
  7.     struct {
  8.         uint32_t auto_reload_on_alarm: 1;/* 报警事件发生后立即通过硬件重新加载计数值 */
  9.     } flags;                                                                /* 报警配置标志 */
  10. } gptimer_alarm_config_t;
复制代码

       5,使能定时器
       该函数用于配置通用定时器报事件警,其函数原型如下所示:
  1. esp_err_t gptimer_enable(gptimer_handle_t timer);
复制代码
       该函数的形参描述,如下表所示:

6.png
表15.3.2.6 函数gptimer_enable()形参描述

       返回值:ESP_OK表示配置成功,其他表示失败。
       此函数将把定时器驱动程序的状态从初始化切换为使能状态,如果 gptimer_register_event_callbacks() 已经延迟安装回调服务函数,此函数将使能回调服务函数。

       6,启动定时器
       该函数用于配置通用定时器报事件警,其函数原型如下所示:
  1. esp_err_t gptimer_start(gptimer_handle_t timer);
复制代码
       该函数的形参描述,如下表所示:

7.png
表15.3.2.7 函数gptimer_enable()形参描述

       返回值:ESP_OK表示配置成功,其他表示失败。
       我们使能了定时器之后,并没有代表定时器已经开始运行,还需要通过调用 gptimer_start() 函数使内部计数器开始工作。

       15.3.3 GPTIMER驱动解析
       在IDF版的06_gp_timer例程中,作者在06_gp_timer \components\BSP路径下新增了一个GPTIM文件夹,用于存放gptim.c和gptim.h这两个文件。其中,gptim.h文件负责声明GPTIM相关的函数和变量,而gptim.c文件则实现了GPTIM的驱动代码。下面,我们将详细解析这两个文件的实现内容。

       1,gptim.h文件
  1. /* 参数引用 */
  2. typedef struct {
  3.     uint64_t event_count;
  4. } gptimer_event_t;

  5. extern QueueHandle_t queue;

  6. /* 函数声明 */
  7. void gptim_int_init(uint16_t counts, uint16_t resolution);        /* 初始化通用定时器 */
  8. bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer,
  9.                                                                  const gptimer_alarm_event_data_t *edata,
  10.                                                                  void *user_data);                  /* 定时器回调函数 */
复制代码

       2,gptim.c文件
  1. /**
  2. * @brief       初始化通用定时器
  3. * @param       counts: 计数值
  4. * @param       resolution: 定时器周期,resolution = 1s = 1000000μs
  5. (此处,定时器以微秒作为计算单位,)
  6. * @retval      无
  7. */
  8. void gptim_int_init(uint16_t counts, uint16_t resolution)
  9. {
  10.     gptimer_alarm_config_t alarm_config;

  11.     uint64_t count;

  12.     /* 配置通用定时器 */
  13.     ESP_LOGI("GPTIMER_ALARM", "配置通用定时器"); /* 创建通用定时器句柄 */
  14.     gptimer_handle_t g_tim = NULL;
  15.     gptimer_config_t g_tim_handle = {
  16.         .clk_src = GPTIMER_CLK_SRC_DEFAULT,  /* 选择定时器时钟源 */
  17.         .direction = GPTIMER_COUNT_UP,       /* 递增计数模式 */
  18.         .resolution_hz = resolution,         /* 计数器分辨率 */
  19.     };
  20.         gptimer_event_callbacks_t g_tim_callbacks = {
  21.         .on_alarm = gptimer_callback,        /* 注册用户回调函数 */                                            
  22.     };
  23.     alarm_config.alarm_count = 1000000;      /* 报警目标计数值 */                                          
  24.    
  25. /* 创建新的通用定时器,并返回句柄 */
  26. ESP_ERROR_CHECK(gptimer_new_timer(&g_tim_handle, &g_tim));                          

  27. /* 创建一个队列,并引入一个事件 */
  28.     queue = xQueueCreate(10, sizeof(gptimer_event_t));                                 
  29.                                  

  30.     if (!queue)
  31.     {
  32.         ESP_LOGE("GPTIMER_ALARM", "创建队列失败");  /* 创建队列失败 */                                    
  33.         return;
  34.     }

  35.     /* 设置和获取计数值 */
  36.     ESP_LOGI("GPTIMER_ALARM", "设置计数值");
  37.     ESP_ERROR_CHECK(gptimer_set_raw_count(g_tim, counts));         /* 设置计数值 */
  38.     ESP_LOGI("GPTIMER_ALARM", "获取计数值");
  39.     ESP_ERROR_CHECK(gptimer_get_raw_count(g_tim, &count));          /* 获取计数值 */
  40.     ESP_LOGI("GPTIMER_ALARM", "定时器计数值: %llu", count);

  41.     /* 注册事件回调函数 */
  42. ESP_ERROR_CHECK(gptimer_register_event_callbacks(g_tim,
  43. &g_tim_callbacks,
  44. queue));  

  45.     /* 设置报警动作 */
  46.     ESP_LOGI("GPTIMER_ALARM", "使能通用定时器");
  47.     ESP_ERROR_CHECK(gptimer_enable(g_tim));        /* 使能通用定时器 */

  48.         /* 配置通用定时器报警事件 */
  49.     ESP_ERROR_CHECK(gptimer_set_alarm_action(g_tim, &alarm_config));                    
  50.     ESP_ERROR_CHECK(gptimer_start(g_tim));         /* 启动通用定时器 */                                             
  51. }

  52. /**
  53. * @brief       定时器回调函数
  54. * @param       无
  55. * @retval      无
  56. */
  57. bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer,
  58. const gptimer_alarm_event_data_t *edata,
  59. void *user_data)
  60. {
  61.     BaseType_t high_task_awoken = pdFALSE;
  62.     queue = (QueueHandle_t)user_data;

  63.     /* 从事件数据中检索计数值 */
  64.     gptimer_event_t ele = {
  65.         .event_count = edata->count_value
  66.     };

  67.     /* 可选:通过操作系统队列将事件数据发送到其他任务 */
  68.     xQueueSendFromISR(queue, &ele, &high_task_awoken);
  69.    
  70.     /* 重新配置报警值 */
  71.     gptimer_alarm_config_t alarm_config = {
  72.         .alarm_count = edata->alarm_value + 1000000, /* 在接下来的1秒内报警 */
  73.     };
  74.     gptimer_set_alarm_action(timer, &alarm_config);
  75.    
  76.     /* 返回是否需要在ISR结束时让步 */
  77.     return high_task_awoken == pdTRUE;
  78. }
复制代码
       对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置gptimer_alarm_config_t的不同参数:

       ①:gptimer_alarm_config_t::alarm_count设置触发警报事件的目标计数值。设置警报值时还需考虑计数方向。尤其是当gptimer_alarm_config_t::auto_reload_on_alarm为true时,gptimer_alarm_config_t::alarm_count和gptimer_alarm_config_t::reload_count不能设置为相同的值,因为警报值和重载值相同时没有意义。

       ②:gptimer_alarm_config_t::reload_count代表警报事件发生时要重载的计数值。此配置仅在gptimer_alarm_config_t::auto_reload_on_alarm设置为true时生效。

       ③:gptimer_alarm_config_t::auto_reload_on_alarm标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将gptimer_alarm_config_t::reload_count的值重载到计数器中。

       要使警报配置生效,需要调用gptimer_set_alarm_action()。特别是当gptimer_alarm_config_t设置为NULL时,报警功能将被禁用。
       定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过gptimer_register_event_callbacks()将函数挂载到中断服务例程(ISR)。gptimer_event_callbacks_t中列出了所有支持的事件回调函数:gptimer_event_callbacks_t设置警报事件的回调函数。由于此函数在ISR上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有ISR后缀的FreeRTOSAPI)。函数原型在gptimer_alarm_cb_t中有所声明。也可以通过参数user_data,将自己的上下文保存到gptimer_register_event_callbacks()中。用户数据将直接传递给回调函数。
       此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在gptimer_enable()之前调用这一函数,否则将返回ESP_ERR_INVALID_STATE错误。

       15.3.4 CMakeLists.txt文件
       打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
  1. set(src_dirs            
  2.             GPTIM
  3.             LED)

  4. set(include_dirs
  5.             GPTIM
  6.             LED)
  7. set(requires
  8.             driver
  9.                         esp_timer)
  10. idf_component_register(SRC_DIRS ${src_dirs}
  11. INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
  12. component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码
       上述的红色GPTIM驱动需要由开发者自行添加,以确保GPTIM驱动能够顺利集成到构建系统中。需要注意的是我们的依赖库(requires)需要添加上ESP32-S3定时器定时器的库,这一步骤是必不可少的,它确保了GPTIM驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。

       15.3.5 实验应用代码
       打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
  1. /**
  2. * @brief       程序入口
  3. * @param       无
  4. * @retval      无
  5. */
  6. void app_main(void)
  7. {
  8.     uint8_t record;
  9.     esp_err_t ret;
  10.     gptimer_event_t g_tim_evente;
  11.     ret = nvs_flash_init();                     /* 初始化NVS */
  12. if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
  13.             ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  14.     {
  15.         ESP_ERROR_CHECK(nvs_flash_erase());
  16.         ret = nvs_flash_init();
  17.     }
  18.     led_init();                                                 /* 初始化LED */
  19.     gptim_int_init(100, 1000000);                /* 初始化通用定时器 */
  20.     while (1)
  21.     {
  22.         record = 1;
  23.             /* 打印通用定时器发生一次计数事件后获取到的值 */
  24.         if (xQueueReceive(queue, &g_tim_evente, 2000))
  25.         {
  26.             ESP_LOGI("GPTIMER_ALARM",
  27.                                     "定时器报警, 计数值: %llu",
  28.                                         g_tim_evente.event_count);   
  29.             record--;
  30.         }
  31.         else
  32.         {
  33.             ESP_LOGW("GPTIMER_ALARM", "错过一次计数事件");
  34.         }
  35.     }
  36.     vQueueDelete(queue);
  37. }
复制代码
       从上面的代码中可以看到,通用定时器的计数值为100,定时器周期设置为1000000微秒并通过创建消息队列的方式引入一个定时器事件。

       15.4 下载验证
       在完成编译和烧录操作后,可以看到板子上的LED在闪烁,在一定周期内串口打印输出定时器报警事件,报警值以及计数值等信息。

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-22 20:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表