【ESP32】【Timer学习】


1.Timer API 介绍:


ESP32内置4个64bit的通用定时器:每个定时器包含一个16bit预分频器和一个64bit可自动重新加载向上/向下计数器。

定时器分为两组,一组两个:

Timer_Group0:Timer_0, Timer_1;
Timer_Group1:Timer_0, Timer_1;

Timer的workflow如下:

·Timer Initialization:	初始化Timer参数;
·Timer Control:	 读取Timer计数值;开始、暂停Timer;
·Alarms:  报警功能;
·Interrupts:  中断功能;

1.1 Timer Initialization:

组间区分结构体: timer_group_t

typedef enum {
	TIMER_GROUP_0 = 0, /*!<Hw timer group 0*/
	TIMER_GROUP_1 = 1, /*!<Hw timer group 1*/
	TIMER_GROUP_MAX,
} timer_group_t;

组内区分结构体: timer_idx_t

typedef enum {
	TIMER_0 = 0, /*!<Select timer0 of GROUPx*/
	TIMER_1 = 1, /*!<Select timer1 of GROUPx*/
	TIMER_MAX,
} timer_idx_t;

初始化Timer之前,我们需要对结构体 timer_config_t 内参数进行初始化:

typedef struct {
	bool alarm_en;      /*!< Timer alarm enable */
	bool counter_en;    /*!< Counter enable */
	timer_intr_mode_t intr_type; /*!< Interrupt mode */
	timer_count_dir_t counter_dir; /*!< Counter direction  */
	bool auto_reload;   /*!< Timer auto-reload */
	uint32_t divider;   /*!< Counter clock divider. The divider's range is from from 2 to 65536. */
} timer_config_t;

· divider: 预分频值,APB_CLK(80MHz)/divider为Timer计数的基石,即一个这样的时钟对应计数器增加/减少1。divider可以对APB时钟进行2~65536的分频。特别说明,divider为1或2时,时钟分频为2;divider为0时,时钟分频为65536.

· counter_dir: 计数器方向。取自 timer_count_dir_t

typedef enum {
	TIMER_COUNT_DOWN = 0, /*!< Descending Count from cnt.high|cnt.low*/
	TIMER_COUNT_UP = 1,   /*!< Ascending Count from Zero*/
	TIMER_COUNT_MAX
} timer_count_dir_t;

· counter_en: 计数器使能。假如使能的话,再调用 timer_init() 函数之后计数器立即开始计数。其值取自
timer_start_t

typedef enum {
	TIMER_PAUSE = 0, /*!<Pause timer counter*/
	TIMER_START = 1, /*!<Start timer counter*/
} timer_start_t;

初始化结构体时,通常设置为 *.counter_en = TIMER_PAUSE 。

· alarm_en: 报警使能。其值取自 timer_alarm_t

typedef enum {
	TIMER_ALARM_DIS = 0,  /*!< Disable timer alarm*/
	TIMER_ALARM_EN = 1,   /*!< Enable timer alarm*/
	TIMER_ALARM_MAX
} timer_alarm_t;

· auto_reload: 自动重载。计数报警后,是否自动重载如指定的值。

bool型变量:
0	---		without auto_reload
1	--- 	with auto_reload

· intr_type: 中断类型。计数报警后是否产生中断,其值取自 timer_intr_mode_t

typedef enum {
	TIMER_INTR_LEVEL = 0,  /*!< Interrupt mode: level mode*/
	//TIMER_INTR_EDGE = 1, /*!< Interrupt mode: edge mode, Not supported Now*/
	TIMER_INTR_MAX
} timer_intr_mode_t;

调用 timer_get_config() 可获取当前的Timer配置参数。


1.2 Timer Control:

获取计数器开始至当前的时间间隔:

timer_get_counter_value(): 返回时间单位 微妙;
timer_get_counter_time_sec(): 返回时间单位 秒;

设定特定的计数开始时间:

timer_set_counter_value()

暂停&开始:

timer_pause(): 可以在任何时间暂停计数器;
timer_start()

两种改变 Timer Operation 的方法:

1:调用 timer_init() 重新初始化;
2:调用专有函数改变结构体内的配置参数:
	· divider value:timer_set_divider()
	· Mode :timer_set_counter_mode()
	· Auto Reload:timer_set_auto_reload()

1.3 Timer Alarms:

timer_set_alarm_value() 设定计数器的报警值;
timer_set_alarm() 使能报警;

报警之后,根据配置两个action可能发生:

1. 如果配置了中断使能,则触发中断;
2. 如果auto_reload使能,则自动从预设值加载。报警使能后,报警使能位自动清零。如果报警时自动加载未被使能,时基计数器会在报警后继续向上计数或向下计数。

1.4 Timer Interrupts:

中断相关函数:

timer_isr_register():	注册中断;

timer_group_intr_enable():	enable interrupts for timer group;
timer_group_intr_disable():	disable interrupts for timer group;

timer_enable_intr():  enable interrupts for a specific timer;
timer_disable_intr():  disable interrupts for a specific timer;

更多资料参考: Espressif Timer API


2.Timer Periodic and Once:

2.1 设计目的:

调用 esp_timer_start_periodic()esp_timer_start_once() 函数:

esp_timer_start_periodic() : 周期(1s)重复运行定时器。计数20s后,闪烁LED灯,5s后重启系统。

esp_timer_start_periodic() : 单次运行定时器。定时10s之后停止,并删除定时器。

2.2 测试代码:

#include <stdio.h>
#include "esp_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "soc/timer_group_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "esp_timer.h"

#define LED_R_GPIO	27

//define two Timer handles
esp_timer_handle_t peri_timer_handle = 0;
esp_timer_handle_t once_timer_handle = 0;

//Periodic Timer callback function;
void peri_timer_cb(void *arg) 
{
	//int8_t index = 0;
	//get time interval after Timer starts;
	int64_t tick = esp_timer_get_time();
	printf("%s \t, time interval after Timer start is: %lld \r\n", __func__, tick);

	if(tick > (20 * 1000 * 1000)) //30s 
	{
		//Timer stop and delete;
		esp_timer_stop(peri_timer_handle);
		esp_err_t err = esp_timer_delete(peri_timer_handle);
		printf("Periodic Timer stop and delete: \t %s", err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
		printf("LED turn on... \r\n");
		
		for(int8_t index=5; index > 0; index--){
			
			printf("system will reboot after [%d]s... \r\n", index);
			for(int8_t index_t = 0; index_t<5; index_t++){
				
				gpio_set_level(LED_R_GPIO, (index_t%2));
				vTaskDelay(200 / portTICK_PERIOD_MS);
			}
		}
		esp_restart();
	}
}

//Once Timer callback function;
void once_timer_cb(void *arg) 
{
	//get time interval after Timer starts;
	int64_t tick = esp_timer_get_time();
	printf("%s \t, time interval after Timer start is: %lld \r\n", __func__, tick);
	
	//Timer stop and delete;
	esp_timer_stop(once_timer_handle);
	esp_err_t err = esp_timer_delete(once_timer_handle);
	printf("***Once Timer stop and delete : %s *** \r\n", err == ESP_OK ? "ok!" : "failed!");
	
}

//main function
void app_main() {
	
	//initialization GPIO which will control LED on/off;
	gpio_pad_select_gpio(LED_R_GPIO);
	gpio_set_direction(LED_R_GPIO, GPIO_MODE_OUTPUT);
	gpio_set_level(LED_R_GPIO, 0);

	//initialization periodic Timer structure
	esp_timer_create_args_t peri_timer = 
	{ 
		.callback = &peri_timer_cb, 	
		.arg = NULL, 				
		.name = "peri_timer" 			
	};
	//initialization once Timer structure
	esp_timer_create_args_t once_timer = 
	{ 
		.callback = &once_timer_cb, 	
		.arg = NULL, 				
		.name = "once_timer" 			
	};

	printf("\r\n\n");
	//periodic Timer creation and start; 
	esp_err_t err = esp_timer_create(&peri_timer, &peri_timer_handle);
	err = esp_timer_start_periodic(peri_timer_handle, 1000 * 1000);	//1s callback
	printf("Periodic Timer create and start: %s", err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
	
	//once Timer creation and start;
	err = esp_timer_create(&once_timer, &once_timer_handle);
	err = esp_timer_start_once(once_timer_handle, 10 * 1000 * 1000);	//10s callback
	printf("Once Timer create and start: %s", err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
	
}

2.3 测试结果:


3.Timer Reload:

3.1 设计目的:

调用定时器 TIMER_Group0 的两个定时器:

定时器0不使能报警,从0开始向上计时至5.78s时,不执行Reload,继续向上计数。

定时器1使能报警,从0开始向上计时至5.78s时,执行Reload,重加载预设值(0)后继续计数。
再次计时至5.78s时,由于auto_reload在第一次报警后清零,不执行Reload操作,计数器继续向上计数。

3.2 测试代码:

#include <stdio.h>
#include "esp_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "soc/timer_group_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"

#define TIMER_DIVIDER         16       //  Hardware timer clock divider, 80M/16 =5MHz,
// 这里的 TIMER_BASE,比如 80MHz/16=5MHz,意思就是5M个计数为1s;
#define TIMER_SCALE           (TIMER_BASE_CLK / TIMER_DIVIDER)  // convert counter value to seconds
#define TIMER_INTERVAL0_SEC   (3.4179) // sample test interval for the first timer
#define TIMER_INTERVAL1_SEC   (5.78)   // sample test interval for the second timer
#define TEST_WITHOUT_RELOAD   0        // testing will be done without auto reload
#define TEST_WITH_RELOAD      1        // testing will be done with auto reload


// timer 初始化
static void example_tg0_timer_init(int timer_idx, bool auto_reload, double timer_interval_sec)
{
	timer_config_t config;
	config.divider = TIMER_DIVIDER;
	config.counter_dir = TIMER_COUNT_UP;
	config.counter_en = TIMER_PAUSE;
	config.alarm_en = TIMER_ALARM_EN;
	config.intr_type = TIMER_INTR_LEVEL;
	config.auto_reload = auto_reload;
	timer_init(TIMER_GROUP_0, timer_idx, &config);

	timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL);

	timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE);

	timer_start(TIMER_GROUP_0, timer_idx);
}

static void timer_example_evt_task(void *arg)
{
	while (1) {

    	uint64_t task_counter_value;
    
    	timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value);
    	printf("\n******\n");
    	printf("Timer_group0_timer0 Counter: 0x%08x%08x\n", (uint32_t) (task_counter_value >> 32), (uint32_t) (task_counter_value));
    	printf("Timer_group0_timer0 Time   : %.2f s\n", (double) task_counter_value / TIMER_SCALE);
    
    	timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &task_counter_value);
    	printf("\nTimer_group0_timer1 Counter: 0x%08x%08x\n", (uint32_t) (task_counter_value >> 32), (uint32_t) (task_counter_value));
    	printf("Timer_group0_timer1 Time   : %.2f s\n", (double) task_counter_value / TIMER_SCALE);
    	printf("******\n\n");
    
    	vTaskDelay(1000 / portTICK_PERIOD_MS);

	}
}

void app_main()
{
	example_tg0_timer_init(TIMER_0, TEST_WITHOUT_RELOAD, TIMER_INTERVAL1_SEC);
	example_tg0_timer_init(TIMER_1, TEST_WITH_RELOAD,    TIMER_INTERVAL1_SEC);
	xTaskCreate(timer_example_evt_task, "timer_evt_task", 2048, NULL, 5, NULL);
}

3.3 测试结果:

如上图高亮所示:
timer0未使能报警,计数至5.78之后继续向上计数;
timer1使能报警,计数至5.78之后,重加载预设值0,后继续向上计数;

当timer1再次计数至5.78时,由于报警使能位在第一次使能之后已经被清零,计数器继续向上计数。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章