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个步骤:
- 创建定时器(Timer);
- 设置警报(alarm);
- 注册警报回调事件;
- 使能定时器;
- 启动定时器。
如果需要一个定时器,这个定时器一直运行,应用仅需要从定时器读取计时值,那么步骤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_count 和 alarm_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) ;}
}