当前位置: 首页 > news >正文

ESP32-S3学习笔记<7>:GP Timer的应用

ESP32-S3学习笔记<7>:GP Timer的应用

  • 1. 头文件包含
  • 2. GP(general purpose) timer的配置
    • 2.1 创建定时器
      • 2.1.1 clk_src/设置时钟源
      • 2.1.2 direction/设置计数方向
      • 2.1.3 resolution_hz/设置计数频率
      • 2.1.4 intr_priority/设置中断优先级
      • 2.1.5 intr_shared/设置中断共享
      • 2.1.6 backup_before_sleep/设置睡眠时的定时器寄存器保存
    • 2.2 设置警报(alarm)
      • 2.2.1 alarm_count/设置触发警报的定时器值
      • 2.2.2 reload_count/重载值
      • 2.2.3 auto_reload_on_alarm/设置是否在警报发生时重载计数值
    • 2.3 注册警报回调事件
    • 2.4 使能定时器
    • 2.5 启动定时器
  • 3. 应用示例

1. 头文件包含

#include "driver/gptimer.h"

2. GP(general purpose) timer的配置

GP Timer的配置主要分为3个步骤:

  1. 创建定时器(Timer);
  2. 设置警报(alarm);
  3. 注册警报回调事件;
  4. 使能定时器;
  5. 启动定时器。

如果需要一个定时器,这个定时器一直运行,应用仅需要从定时器读取计时值,那么步骤2和步骤3都不需要。

2.1 创建定时器

使用如下函数创建定时器:

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

第二个参数 ret_timer 为出参数,返回一个定时器句柄。后续设置定时器的其他功能,或者使能、启动定时器时使用。

第一个参数 config 为定时器的配置参数。其结构定义如下:

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 */uint32_t backup_before_sleep: 1; /*!< If set, the driver will backup/restore the GPTimer registers before/after entering/exist sleep mode.By this approach, the system can power off GPTimer's power domain.This can save power, but at the expense of more RAM being consumed */} flags;                             /*!< GPTimer config flags*/
} gptimer_config_t;

2.1.1 clk_src/设置时钟源

成员 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;

可见,可用的时钟源,有APB时钟源和XTAL时钟源。前者是处理器内部总线的时钟,后者是外部晶振的时钟。选择时钟源需要考虑一个问题:系统使能DFS(dynamic frequency scaling,动态调频)的时候,APB时钟可能降频,导致定时器运行不准。如果浅睡眠(light sleep) 模式也被开启, PLL 和 XTAL 时钟都会被默认关闭,从而导致 GPTimer 的计时不准确。

2.1.2 direction/设置计数方向

成员 direction 的定义是:

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

分别为向下计数或者向上计数。

2.1.3 resolution_hz/设置计数频率

成员 resolution_hz 设置计数频率。例如设置1000,则每毫秒进行一次计数。设置1000000,则每微秒进行一次计数。

2.1.4 intr_priority/设置中断优先级

设置为0时,由驱动分配一个较低的优先级。

2.1.5 intr_shared/设置中断共享

指定定时器中断是否和其他外设共享。建议设置为0,非共享。但是应用较复杂时除外。根据实际情况分析。

2.1.6 backup_before_sleep/设置睡眠时的定时器寄存器保存

设置CPU进入睡眠时是否需要保存定时器的寄存器。低功耗应用时使用。

2.2 设置警报(alarm)

用以下函数设置警报。所谓警报,就是定时器值达到某个设定值时,产生中断供用户处理。

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

函数的第一个参数 timer ,为上一步中返回的定时器句柄。
函数的第二个参数 config ,用于配置警报发声的条件。其定义如下:

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;

2.2.1 alarm_count/设置触发警报的定时器值

成员 alarm_count 设置触发警报的定时器值。当定时器计数值达到此值时,将触发中断,执行用户编写的中断服务函数。

2.2.2 reload_count/重载值

成员 reload_count 设置重载值。所谓重载值,即当定时器计数值到达 alarm_count,将计数器置位的值。在单片机应用中,定时器常用来定时执行某项任务。计数大多从0开始到达某个值,所以此时 reload_count 就设置为0。当警报执行的时候就重载计数值为0,从而重新启动一轮定时。
假设 reload_countalarm_count设置为相同,会发生什么?例如都设置为1000。则当定时器的计数值到达1000时,立刻触发警报,同时硬件又将 reload_count (1000)再置入计数值。这或者会导致立即发声第二次警报,或者硬件跳过值继续计数。前者会导致死循环,后者则毫无意义。所以驱动里说明这个两个值不能设置为一致,否则驱动会报错。

这里还有一个细节问题。如果定时器的计数频率为1000Hz,则定时器计数周期为1ms。那么假设我们需要一个精准的1s告警中断,那么 alarm_count 应该设置为1000,还是999呢?之前开发STM32的定时器,如果需要精准的1s溢出,是要设置为999的,因为重载值是0,计数从0开始累加。但是ESP32-S3的例程,这里设置了(类似于)1000,来产生1s的事件。那么究竟应该设置1000,还是999呢?我编写了一个程序,每次中断就翻转一个GPIO。用逻辑分析仪抓取GPIO的翻转:

当设置为999,抓到下图所示。可以看到,高电平或低电平持续时间几乎等于999ms。

在这里插入图片描述

当设置为1000,抓到下图所示。可以看到,高电平或低电平持续时间几乎等于1000ms。

在这里插入图片描述

所以,对于ESP32-S3的开发,需要这种精确定时,填写alarm_count,按照需要的定时时间除以定时器计数周期即可,无需减1。

2.2.3 auto_reload_on_alarm/设置是否在警报发生时重载计数值

字面意思。

2.3 注册警报回调事件

注册警报回调事件,首先要定义一个 gptimer_event_callbacks_t 结构体(要用全局变量,因为传递指针给注册函数)。

gptimer_event_callbacks_t  g_stGPTimerEventCb ;

在结构体中,将写好的回调函数赋值给结构体:

g_stGPTimerEventCb.on_alarm = __cb_TEST_GPTIMER_EventAlarm ;

而后,用如下函数注册回调函数:

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

第一个参数是定时器句柄。
第三个参数是传给回调函数的自定义参数。

2.4 使能定时器

用如下函数使能定时器:

esp_err_t gptimer_enable(gptimer_handle_t timer);

2.5 启动定时器

用如下函数启动定时器:

esp_err_t gptimer_start(gptimer_handle_t timer);

3. 应用示例

以下示例,创建一个定时器,并设置每秒发声一次警报事件。警报事件发声时,翻转一个GPIO的电平。
test_gptimer.h文件:

void TEST_GPTIMER_GPIOConfig(void) ;
void TEST_GPTIMER_GPTIMERConfig(void) ;
void TEST_GPTIMER_GPTIMERStart(void) ;

test_gptimer.c文件:

#include "driver/gpio.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "test_gptimer.h"gptimer_handle_t           g_pstGPTimerHandler ;
gptimer_event_callbacks_t  g_stGPTimerEventCb ;
unsigned long long         g_ullTimer = 0 ;static bool __cb_TEST_GPTIMER_EventAlarm(gptimer_handle_t pstTimer, const gptimer_alarm_event_data_t *pstEventData, void *pvUserCtx)
{int iGPIOLevel ;iGPIOLevel = gpio_get_level(GPIO_NUM_4) ;gpio_set_level(GPIO_NUM_4, 0x00000001 & (~iGPIOLevel)) ;return true ;
}void TEST_GPTIMER_GPIOConfig(void)
{esp_err_t           iErrCode ;const gpio_config_t stGPIOConfig = {.pin_bit_mask = 1ull << GPIO_NUM_4 ,.mode         = GPIO_MODE_INPUT_OUTPUT ,.pull_up_en   = GPIO_PULLUP_DISABLE ,.pull_down_en = GPIO_PULLDOWN_DISABLE ,.intr_type    = GPIO_INTR_DISABLE } ;iErrCode = gpio_config(&stGPIOConfig) ;if(ESP_OK != iErrCode){ESP_LOGE("test_gptimer", "Config GPIO error! Return value is %d\n", iErrCode) ;}return ;
}void TEST_GPTIMER_GPTIMERConfig(void)
{esp_err_t              iErrCode ;const gptimer_config_t stGPTimerConfigInfo = {.clk_src                   = GPTIMER_CLK_SRC_XTAL ,.direction                 = GPTIMER_COUNT_UP ,.resolution_hz             = 1000 ,.intr_priority             = 0 ,.flags.intr_shared         = 0 ,.flags.backup_before_sleep = 0 } ;const gptimer_alarm_config_t stGPTimerAlarmConfigInfo = {.alarm_count                = 1000 ,.reload_count               = 0 ,.flags.auto_reload_on_alarm = 1} ;iErrCode = gptimer_new_timer(&stGPTimerConfigInfo, &g_pstGPTimerHandler) ;if(ESP_OK != iErrCode){ESP_LOGE("test_gptimer", "Create timer error! Return value is %d\n", iErrCode) ;}iErrCode = gptimer_set_alarm_action(g_pstGPTimerHandler, &stGPTimerAlarmConfigInfo) ;if(ESP_OK != iErrCode){ESP_LOGE("test_gptimer", "Set timer alarm error! Return value is %d\n", iErrCode) ;}g_stGPTimerEventCb.on_alarm = __cb_TEST_GPTIMER_EventAlarm ;iErrCode = gptimer_register_event_callbacks(g_pstGPTimerHandler, &g_stGPTimerEventCb, 0) ;if(ESP_OK != iErrCode){ESP_LOGE("test_gptimer", "Register event callbacks error! Return value is %d\n", iErrCode) ;}return ;}void TEST_GPTIMER_GPTIMERStart(void)
{gptimer_enable(g_pstGPTimerHandler) ;gptimer_start(g_pstGPTimerHandler) ;return ;
}

main.c文件:

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"#include "test_gptimer.h"void app_main(void)
{TEST_GPTIMER_GPIOConfig() ;TEST_GPTIMER_GPTIMERConfig() ;TEST_GPTIMER_GPTIMERStart() ;while (true){vTaskDelay(1000) ;}
}

http://www.dtcms.com/a/300953.html

相关文章:

  • 力扣热题100----------41.缺少的第一个正数
  • JavaScript单线程实现异步
  • [ The Missing Semester of Your CS Education ] 学习笔记 shell篇
  • 浅谈如何解决多组件系统相互依赖、调用导致接口复杂问题
  • 深入理解Java内存与运行时机制:从对象内存布局到指针压缩
  • 命令行和neovim的git操作软件-lazygit
  • 探索 Vim:Linux 下的高效文本编辑利器
  • Unity Catalog与Apache Iceberg如何重塑Data+AI时代的企业数据架构
  • Windows 11 Qt 5.15.x 源码编译,支持C++20
  • 字节跳动Coze Studio开源了!架构解析
  • 01人工智能中优雅草商业实战项目视频字幕翻译以及声音转译之底层处理逻辑阐述-卓伊凡|莉莉
  • go mod教程、go module
  • docker 自定义网桥作用
  • JavaScript手录07-数组
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-38,(知识点:晶体管放大电路频率特性,下限截止频率)
  • 将 JsonArray 类型的数据导出到Excel文件里的两种方式
  • 内存泄漏问题排查
  • mmap的调用层级与内核态陷入全过程
  • java8+springboot2.5.4环境Markdwon转word
  • 设计模式(十四)行为型:职责链模式详解
  • add新增管理员功能、BaseController类的简介--------示例OJ
  • linux安装nvm教程
  • Windows 11修复损坏的 ISO 文件
  • 二、搭建springCloudAlibaba2021.1版本分布式微服务-Nacos搭建及服务注册和配置中心
  • RHEL9 网络配置入门:IP 显示、主机名修改与配置文件解析
  • 【C++】红黑树实现
  • logstash采集springboot微服务日志
  • 使用Python,OpenCV,K-Means聚类查找图像中最主要的颜色
  • C语言:函数
  • AI大模型前沿:Muyan-TTS开源零样本语音合成技术解析