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

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。

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

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

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

所以,对于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 #include #include #include #include #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) ; }}

倚天Ⅱ自由世界