【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時,由於報警使能位在第一次使能之後已經被清零,計數器繼續向上計數。

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