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