ESP32 IDF开发 驱动篇⑥定时器的使用方法

  • 1、博主写这篇技术文章的目的:
  • 2、Timer库的介绍
  • 3、软件设计
  • 4、实例分析
  • 5、以下是调试的结果:

别迷路-导航栏
快速导航找到你想要的(文章目录)


此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)熟悉掌握esp_timer相关API;
(2)掌握esp32 timer的使用方法;

2、Timer库的介绍

有关Timer相关函数请参考esp-idf\components\esp_common\include\esp_timer.h
Timer库也比较简单主要分3步:
配置定时器、设置定时器回调函数、启动定时器。

/**
  * @brief 初始化esp_timer库
  * @return
  *-ESP_OK成功
  *-ESP_ERR_NO_MEM,如果分配失败
  *-ESP_ERR_INVALID_STATE(如果已初始化)
  *-来自中断分配器的其他错误
  */
esp_err_t esp_timer_init(void);
/**
  * @brief 取消初始化esp_timer库
  * @note 通常不应从应用程序中调用此函数
  * @return
  *-ESP_OK成功
  *-ESP_ERR_INVALID_STATE(如果尚未初始化)
  */
esp_err_t esp_timer_deinit(void);
/**
  * @brief 创建一个esp_timer实例
  * @note 使用计时器完成后,请使用esp_timer_delete函数将其删除。
  * @param create_args指向带有计时器创建参数的结构的指针。
  *不是由库保存的,可以在堆栈上分配。
  * @param [out] out_handle输出,指向esp_timer_handle_t变量的指针
  *将保留创建的计时器句柄。
  * @return
  *-ESP_OK成功
  *-如果某些create_args无效,则为ESP_ERR_INVALID_ARG
  *-如果尚未初始化esp_timer库,则为ESP_ERR_INVALID_STATE
  *-如果内存分配失败,则为ESP_ERR_NO_MEM
  */
esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,
                           esp_timer_handle_t* out_handle);
/**
  * @brief 启动单发计时器
  *调用此函数时,计时器不应运行。
  * @param timer使用esp_timer_create创建的计时器句柄
  * @param timeout_us计时器超时,以微秒为单位,相对于当前时刻
  * @return
  *-ESP_OK成功
  *-ESP_ERR_INVALID_ARG(如果句柄无效)
  *-ESP_ERR_INVALID_STATE,如果计时器已经在运行
  */
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
/**
  * @brief 启动定期计时器
   *调用此函数时,计时器不应运行。 该功能将
  *启动计时器,它将触发每个“周期”微秒。
  * @param timer使用esp_timer_create创建的计时器句柄
  * @param period计时器周期,以微秒为单位
  * @return
  *-ESP_OK成功
  *-ESP_ERR_INVALID_ARG(如果句柄无效)
  *-ESP_ERR_INVALID_STATE,如果计时器已经在运行
  */
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);
/**
  * @brief 停止计时器
  *此功能停止使用esp_timer_start_once启动的计时器
  *或esp_timer_start_periodic。
  * @param timer使用esp_timer_create创建的计时器句柄
  * @return
  *-ESP_OK成功
  *-ESP_ERR_INVALID_STATE(如果计时器未运行)
  */
esp_err_t esp_timer_stop(esp_timer_handle_t timer);
/**
  * @brief 删除esp_timer实例
  *删除前必须停止计时器。 一次性计时器已过期
  *无需停止。
  * @param timer使用esp_timer_create分配的计时器句柄
  * @return
  *-ESP_OK成功
  *-ESP_ERR_INVALID_STATE(如果计时器未运行)
  */
esp_err_t esp_timer_delete(esp_timer_handle_t timer);
/**
  * @brief 自启动以后获取时间(以微秒为单位)
  * @return 自调用esp_timer_init以来的微秒数(通常为
  *发生在应用程序启动的之前)。
  */
int64_t esp_timer_get_time(void);
/**
  * @brief 获取预计下次超时的时间戳
  * @return 最近的计时器事件的时间戳,以微秒为单位。
  *时基与esp_timer_get_time返回的值相同。
  */
int64_t esp_timer_get_next_alarm(void);

3、软件设计

首先我们来分析一下定时器的结构体:

/**
 * @brief 定时器配置结构体
 */
typedef struct {
    esp_timer_cb_t callback;        //定时器计数到了,执行的回调函数
    void* arg;                      //传递给回调的参数
    esp_timer_dispatch_t dispatch_method;   //从任务或ISR调用回调函数
    const char* name;               //定时器名称 用于esp_timer_dump函数
} esp_timer_create_args_t;

从上面的结构我们可以看出要实现一个简单的定时器只需要给callback赋值一个回调函数指针和定时器名字name即可

const esp_timer_create_args_t periodic_timer_args =
 {
 .callback = &periodic_timer_callback,//定时器计数到了,执行的回调函数
 .name = “periodic_timer” //定时器名称
 };

创建定时器结构体之后还需要一个存储定时器句柄的结构体:

struct esp_timer {
    uint64_t alarm;
    uint64_t period;
    union {
        esp_timer_cb_t callback;
        uint32_t event_id;
    };
    void* arg;
#if WITH_PROFILING
    const char* name;
    size_t times_triggered;
    size_t times_armed;
    uint64_t total_callback_run_time;
#endif // WITH_PROFILING
    LIST_ENTRY(esp_timer) list_entry;
};

主要看period表示:定时的时间,Callback:存储回调函数,arg:回调函数的参数,知道了这个结构体,我们就可以开始:
第一步创建定时器:主要实现的功能就是为Timer结构体分配内存初始化参数:

typedef struct esp_timer* esp_timer_handle_t;

esp_err_t esp_timer_create(const esp_timer_create_args_t* args, esp_timer_handle_t* out_handle)
{
	 esp_timer_handle_t result = (esp_timer_handle_t) calloc(1, sizeof(*result));//第一步给esp_timer结构体分配内存
result->callback = args->callback;//传递回调函数,及参数,名字
result->arg = args->arg;
result->name = args->name;
 *out_handle = result;//最后导入到句柄结构体中esp_timer
}

第二步:为定时器设置定时器时间,并启动,主要实现的功能就是为esp_timer结构体赋值alarm参数和period参数

esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us)
{
    timer->alarm = esp_timer_get_time() + period_us;//设置多少us之后调用回调函数
    timer->period = period_us; //传递定时时间,单位us
}

4、实例分析

复制上一个工程改名字为 idf_timer_example即可文件名字改为 idf_timer_example.C makefile文件也改成 PROJECT_NAME := idf_timer_example即可,然后复制一下代码测试。

/**********************************************************************
*               文件名:            idf_timer_example.c
*               创建人:            
*               创建日期:           
*               修改人:
*               修改日期:           
*               版本号:            V1.1
*               备注:
*               公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "sdkconfig.h"
/***************************************************
* 创建两个计时器:
* 1.一个定期计时器,它将每0.5s运行一次,并显示一条消息
* 2.一键式定时器,将在5秒后触发,并定期重新启动
*计时器,周期为1秒。
*****************************************************/
static void periodic_timer_callback(void* arg);
static void oneshot_timer_callback(void* arg);
static const char* TAG = "timer";
/***********************************************************************
* 函数:  
* 描述:   主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main(void)
{
    const esp_timer_create_args_t periodic_timer_args = 
    {
            .callback = &periodic_timer_callback,//定时器计数到了,执行的回调函数
            .name = "periodic_timer"             //定时器名称     
    };
    esp_timer_handle_t periodic_timer;//定时器指针句柄,创建后获得定时器的句柄,类似以打开文件句柄或创建系统任务句柄
    //创建一个esp_timer
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    const esp_timer_create_args_t oneshot_timer_args = 
    {
            .callback = &oneshot_timer_callback,
            //指定参数,周期定时器句柄,方便在oneshot_timer_callback定时器中拿到periodic_timer句柄
            .arg = (void*) periodic_timer,
            .name = "one-shot"
    };
    esp_timer_handle_t oneshot_timer;//定时器指针句柄,创建后获得定时器的句柄,类似以打开文件句柄或创建系统任务句柄
    ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer));

    //启动定时
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000));//周期定时,以微秒为单位,定时0.5s
    ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000));//定时一次,以微秒为单位,定时5s

    ESP_LOGI(TAG, "current counting time: %lld us", esp_timer_get_time());
    usleep(10000000);
    //delete之前一定要先stop 
    ESP_ERROR_CHECK(esp_timer_stop(periodic_timer));
    ESP_ERROR_CHECK(esp_timer_delete(periodic_timer));
    ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer));
    ESP_LOGI(TAG, "Stopped and deleted timers");
}
/***********************************************************************
* 函数:  
* 描述:   周期回调函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void periodic_timer_callback(void* arg)
{
    int64_t time = esp_timer_get_time();
    ESP_LOGI(TAG, "Periodic timer: %lld us", time);
}
/***********************************************************************
* 函数:  
* 描述:   定时一次,再次设置定时器
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void oneshot_timer_callback(void* arg)
{
    int64_t time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "One-shot timer: %lld us", time_since_boot);
    esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg;
    //重新设置定时器时间
    ESP_ERROR_CHECK(esp_timer_stop(periodic_timer_handle));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, 1000000));
    time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "Restarted periodic timer : %lld us",time_since_boot);
}

5、以下是调试的结果:

esp32多个定时器中断进不了 esp32 定时器_vscode