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 #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) ; }}
倚天Ⅱ自由世界