EPS32 软件定时器的基本使用

概述

​ esp_timer相当于一个高分辨率的软件定时器,并且可以将ESP32从睡眠中唤醒。可以将其类比为FreeRTOS的定时器,但是这二者之间也是有区别的。

​ FreeRTOS的定时器定时周期不得小于其RTOS的滴答周期,并且FreeRTOS的定时器是从一个优先级较低的任务中调用的(虽然这个任务的优先级也是可以调高的)。但是esp_timer就不受这两者的限制。

​ 虽然如此但也是要注意,由于esp_timer回调函数的优先级很高,所以回调函数中不能执行很多命令(很大程度上会影响系统的实时性),还是老规矩,中断中尽量只是标记信号量,处理函数还是放到任务中去执行。

​ 并且这个定时器的回调函数也是会被其他更高优先级的任务抢占的,比如说 SPI FALSH操作。但是这种一般可以暂时先不考虑。

​ 周期性esp_timer还会对最小计时器周期施加50us的限制。周期小于50us的定期软件计时器不切实际,因为它们会占用大部分CPU时间。如果发现需要一个短周期的计时器,请考虑使用专用的硬件外设或DMA功能。(太短的定时也不太现实)

利用esp_timer完成一次性定时

需要包含的库

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

定义相关变量

esp_timer_handle_t timer_once_handle;    //定义一个定时器的句柄

//这个结构体的作用就是配置定时器的回调函数以及为这个定时器起一个名字
esp_timer_create_args_t timer_once_arg = 
&#123; 
    .callback = &timer_once_cb,          // 设置回调函数
    .arg = NULL,                          // 不携带参数
    .name = "TestOnceTimer"               // 定时器名字
&#125;;

编写定时器回调函数

//编写这个定时器的定时器回调函数
void timer_once_cb(void *arg) 
&#123;
    int64_t tick = esp_timer_get_time();
    printf("方法回调名字: %s , 距离定时器开启时间间隔 = %lld \r\n", __func__, tick);
    esp_err_t err = esp_timer_delete(timer_once_handle);
    printf("要删除的定时器名字:%s , 是否停止成功:%s", timer_once_arg.name,
            err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
&#125;

__func__这个函数是C++中的函数,作用就是获取当前调用函数的名字

创建及启动定时器

void app_main(void)
&#123;
    esp_timer_init();             // 使用定时器API函数,先调用接口初始化

    //创建一个单次执行的定时器
    esp_timer_create(&timer_once_arg, &timer_once_handle);
    esp_timer_start_once(timer_once_handle, 1 * 1000 * 1000);//这里定时了1s

    while(1)
    &#123;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    &#125;
&#125;

完整代码

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

esp_timer_handle_t timer_once_handle;

void timer_once_cb(void *arg);

// 定义一个单次运行的定时器结构体
esp_timer_create_args_t timer_once_arg = 
&#123; 
    .callback = &timer_once_cb,  // 设置回调函数
    .arg = NULL,                      // 不携带参数
    .name = "TestOnceTimer"           // 定时器名字
&#125;;

void timer_once_cb(void *arg) 
&#123;
    int64_t tick = esp_timer_get_time();
    printf("方法回调名字: %s , 距离定时器开启时间间隔 = %lld \r\n", __func__, tick);
    esp_err_t err = esp_timer_delete(timer_once_handle);
    printf("要删除的定时器名字:%s , 是否停止成功:%s", timer_once_arg.name,
            err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
&#125;


void app_main(void)
&#123;
    esp_timer_init(); // 使用定时器API函数,先调用接口初始化

    //创建一个单次执行的定时器
    esp_timer_create(&timer_once_arg, &timer_once_handle);
    esp_timer_start_once(timer_once_handle, 1 * 1000 * 1000);//这里定时了10s

    while(1)
    &#123;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    &#125;
&#125;

测试

利用esp_timer实现周期定时器

利用esp_timer实现周期定时器也是十分的简单,只需要将原函数中的esp_timer_start_once函数变为esp_timer_start_periodic即可。

重新启动呢?重新再调用函数esp_timer_start_periodic即可完成重启的过程,并且定时计数器并不会清零。(这个64位的计时器如果要是清零的话那得584,942年,几辈子不断电都加不满,所以不用担心计数溢出的问题。)

这里使用周期性定时器来实现了一个2s的定时器,在其完成了10s的定时之后将调用函数esp_timer_stop将其停止,并设置了一个标志位,同时在主循环函数中再次将其开启。(周期循环这个过程)

具体代码如下

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

esp_timer_handle_t timer_period_handle;
uint8_t flag;

void timer_period_cb(void *arg);

// 定义一个单次运行的定时器结构体
esp_timer_create_args_t timer_period_arg = 
&#123; 
    .callback = &timer_period_cb,  // 设置回调函数
    .arg = NULL,                      // 不携带参数
    .name = "TestPeriodTimer"           // 定时器名字
&#125;;

void timer_period_cb(void *arg) 
&#123;
    static uint8_t time;

    int64_t tick = esp_timer_get_time();
    printf("方法回调名字: %s , 距离定时器开启时间间隔 = %lld \r\n", __func__, tick);

    time++;

    if(time >= 5)
    &#123;
        time = 0;
        // 停止定时器工作,并获取是否停止成功
        esp_err_t err = esp_timer_stop(timer_period_handle);
        printf("要停止的定时器名字:%s , 是否停止成功:%s", timer_period_arg.name,
                err == ESP_OK ? "ok!\r\n" : "failed!\r\n");

        // err = esp_timer_delete(timer_period_handle);
        // printf("要删除的定时器名字:%s , 是否停止成功:%s", timer_period_arg.name,
        //         err == ESP_OK ? "ok!\r\n" : "failed!\r\n");

        flag = 1; 
    &#125;

&#125;

void app_main(void)
&#123;
    esp_timer_init(); // 使用定时器API函数,先调用接口初始化

    //创建一个单次执行的定时器
    esp_timer_create(&timer_period_arg, &timer_period_handle);
    esp_timer_start_periodic(timer_period_handle, 2 * 1000 * 1000);//这里定时了10s

    while(1)
    &#123;
        //要是停止了,5s之后函数中会再次启动这个函数
        if(flag == 1)
        &#123;
            flag = 0;
            esp_timer_start_periodic(timer_period_handle, 2 * 1000 * 1000);//这里定时了10s
        &#125;

        vTaskDelay(5000 / portTICK_PERIOD_MS);
    &#125;
&#125;

测试

使用ESP32软定时器的思路

  1. 首先先定义一个定时器的句柄变量esp_timer_handle_t xxx
  2. 为这个定时器编写一个中断回调函数
  3. 定义一个结构体用于将回调函数与定时器绑定起来esp_timer_create_args_t
  4. 在主函数中使用esp_timer_init();来初始化定时器
  5. 利用函数esp_timer_create来为之前的定时器句柄创建一个定时器
  6. 利用esp_timer_start_once或者esp_timer_start_periodic函数来按要求启动定时器
  7. 根据用户需求自行调用定时器删除或者停止函数。

函数总结(便于查阅)

用于初始化定时器(一般在函数初始化时使用)
esp_err_t esp_timer_init(void);                

用于注销定时器(一般不使用)
esp_err_t esp_timer_deinit(void);                

用于创建一个定时器(在不再需要定时器时应该将其删除)    
esp_err_t esp_timer_create(constesp_timer_create_args_t *create_args, esp_timer_handle_t *out_handle)    

开启一次性定时器(调用此函数时,定时器应该处于停止状态)    
esp_err_t esp_timer_start_once(esp_timer_handle_ttimer, uint64_t timeout_us)    

开启一个周期定时器(调用此函数时,定时器应该处于停止状态)    
esp_err_t esp_timer_start_periodic(esp_timer_handle_ttimer, uint64_t period)    

停止一个定时器(但是不会使其计数值清零)    
esp_err_t esp_timer_stop(esp_timer_handle_ttimer)    

删除一个定时器(删除定时器时,定时器应处于停止状态,可以先使其停止再删除)    
esp_err_t esp_timer_delete(esp_timer_handle_ttimer)    

获取当前定时器的计数值(定时器自启动以来的技术值)    
int64_t esp_timer_get_time(void)    

更多的esp_timer相关的函数以及介绍还请参考:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/system/esp_timer.html?highlight=esp_timer_get_time#high-resolution-timer

注意

esp_timer_start_periodic以及esp_timer_start_once的定时周期都是以us为单位的。