提示:本博客作为学习笔记,有错误的地方希望指正
一、ESP32定时器介绍
参考资料:ESP IDF编程手册V4.4
1.1、概述
??尽管FreeRTOS提供了软件计时器,但这些计时器有一些限制:
- 最大分辨率等于RTOS周期
- 定时器回调从低优先级任务中分派
??硬件计时器不受这两种限制,但通常使用起来不太方便。例如,应用程序组件可能需要定时器事件在未来的特定时间触发,但硬件定时器只包含一个用于中断生成的“compare”值。这意味着需要在硬件计时器之上构建一些工具来管理挂起的事件列表,当相应的硬件中断发生时,可以为这些事件分派回调。 ??处理程序的中断级别取决于CONFIG_ESP_TIMER_INTERRUPT_LEVEL选项。它允许设置这个:1,2或3级别(默认为1)。提高级别,中断处理程序可以减少定时器处理延迟。 ??esp_timer api集提供了一次性定时器和周期性定时器,微秒时间分辨率和64位范围。 ??在内部,esp_timer使用64位硬件计时器,其中实现取决于目标。LAC定时器用于ESP32。 ??定时器回调可以通过两种方法分派:
- ESP_TIMER_TASK
- ESP_TIMER_ISR。仅当CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD开启(默认关闭)时可用。
??ESP_TIMER_TASK。计时器回调是由高优先级esp_timer任务分派的。因为所有的回调都是从同一个任务调度的,所以建议只从回调本身执行尽可能少的工作,而是使用队列将事件提交给低优先级的任务。 ??如果其他优先级高于esp_timer的任务正在运行,回调调度将被延迟,直到esp_timer任务有机会运行。例如,如果正在进行一个SPI Flash操作,就会发生这种情况。 ??ESP_TIMER_ISR。定时器回调是直接从定时器中断处理程序调度的。这个方法在一些简单的回调中很有用,目的是降低延迟。 ??创建和启动计时器,以及调度回调需要一些时间。因此,一次性esp_timer超时值有一个下限。如果esp_timer_start_once()在超时值小于20us的情况下被调用,回调只会在大约20us之后才会被调度。 ??周期性esp_timer还对最小定时器周期施加了50us的限制。周期小于50us的定期软件计时器是不实用的,因为它们会消耗大部分CPU时间。如果需要小周期的定时器,可以考虑使用专用的硬件外设或DMA特性。
1.2、使用esp_timer api
??单个定时器由esp_timer_handle_t类型表示。计时器有一个与之相关的回调函数。每当计时器超时时,esp_timer任务就会调用这个回调函数。
- 要创建计时器,可以调用esp_timer_create()。
- 要在不再需要时删除计时器,请调用esp_timer_delete()。
??定时器可以一次性启动,也可以定时启动。 - 要以一次性模式启动计时器,请调用esp_timer_start_once(),并传递应在之后调用回调的时间间隔。当调用回调时,计时器被认为已停止。
- 要以周期模式启动计时器,请调用esp_timer_start_periodic(),传递应该调用回调的周期。计时器将一直运行,直到调用esp_timer_stop()。
??注意,当调用esp_timer_start_once()或esp_timer_start_periodic()时,定时器不能运行。要重新启动正在运行的计时器,首先调用esp_timer_stop(),然后调用一个启动函数。
1.3、回调函数
??注解 ??保持回调函数尽可能短,否则它会影响所有的计时器。 ??由ESP_TIMER_ISR方法处理的定时器回调不应该调用上下文切换调用—portYIELD_FROM_ISR(),而应该使用esp_timer_isr_dispatch_need_yield()函数。如果系统需要,上下文切换将在处理完所有ISR调度计时器之后进行。
1.4、esp_timer在浅睡眠期间
??在浅睡眠期间,esp_timer计数器停止并且不调用回调函数。相反,时间是由RTC计数器计算的。在唤醒时,系统获取计数器之间的差值,并调用一个提高esp_timer计数器的函数。由于计数器已被提前,系统将开始调用睡眠期间未调用的回调函数。回调的数量取决于睡眠的持续时间和定时器的周期。它可能导致一些队列溢出。这只适用于周期性计时器,一次性计时器将被调用一次。 ??这种行为可以通过在睡眠前调用 esp_timer_stop() 来改变。在某些情况下,这可能很不方便,您可以在 esp_timer_create() 期间使用 skip_unhandled_events 选项来代替停止功能。当 skip_unhandled_events 为真时,如果周期性计时器在轻度睡眠期间到期一次或多次,则唤醒时仅调用一次回调。 ??使用带有自动轻度睡眠的 skip_unhandled_events 选项(请参阅电源管理 API)有助于减少系统处于轻度睡眠时的消耗。浅睡眠的持续时间也由 esp_timers 决定。带有 skip_unhandled_events 选项的计时器不会唤醒系统。
1.5、处理回调
??esp_timer 旨在实现高分辨率低延迟计时器和处理延迟事件的能力。如果计时器迟到了,那么回调将尽快被调用,它不会丢失。在最坏的情况下,当定时器超过一个周期(对于周期性定时器)没有被处理时,在这种情况下,回调将一个接一个地被调用,而无需等待设置的周期。这对某些应用程序来说可能很糟糕,并且引入了 skip_unhandled_events 选项来消除这种行为。如果设置了skip_unhandled_events,那么一个已过期多次而无法调用回调的周期性定时器在可以处理时仍将仅导致一个回调事件。
1.6、获取当前时间
??esp_timer 还提供了一个方便的函数来获取自启动以来经过的时间,精度为微秒:esp_timer_get_time()。此函数返回自 esp_timer 初始化以来的微秒数,这通常发生在调用 app_main 函数之前不久。 ??与 gettimeofday 函数不同,esp_timer_get_time() 返回的值:
- 芯片从深度睡眠唤醒后从零开始
- 未应用时区或 DST 调整
二、硬件设计
??这里只涉及到ESP32系统资源,不需要外部硬件。
三、实现代码
??在历程中实现两个定时器的初始化,创建定时器,然后执行定时器,第二个定时器中断回调中改变第一个定时器的频率,其中还有将定时器挂起,系统进入休眠状态。 ??初始化流程 ??初始化代码如下
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "sdkconfig.h"
static void periodic_timer_callback(void* arg);
static void oneshot_timer_callback(void* arg);
static const char* TAG = "example";
void app_main(void)
{
const esp_timer_create_args_t periodic_timer_args = {
.callback = &periodic_timer_callback,
.name = "periodic"
};
esp_timer_handle_t periodic_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,
.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));
ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000));
ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time());
for (int i = 0; i < 5; ++i) {
ESP_ERROR_CHECK(esp_timer_dump(stdout));
usleep(2000000);
}
ESP_LOGI(TAG, "Entering light sleep for 0.5s, time since boot: %lld us",esp_timer_get_time());
ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(500000));
esp_light_sleep_start();
ESP_LOGI(TAG, "Woke up from light sleep, time since boot: %lld us",esp_timer_get_time());
usleep(2000000);
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_since_boot = esp_timer_get_time();
ESP_LOGI(TAG, "Periodic timer called, time since boot: %lld us", time_since_boot);
}
static void oneshot_timer_callback(void* arg)
{
int64_t time_since_boot = esp_timer_get_time();
ESP_LOGI(TAG, "One-shot timer called, time since boot: %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 with 1s period, time since boot: %lld us",time_since_boot);
}
四、定时器实验演示结果
&esmp;&esmp;定时器执行的结果以log的形式打印显示的
五、ESP32 Timer函数API
5.1、esp_timer.h文件中的内容的API
??esp_timer.h文件中的简约版本的API不带参数解释的部分,内容基本上初始化定时器结构体,这里面使用了typedef的形式定义了typedef void (esp_timer_cb_t)(void arg);的函数指针的,在定时器初始化中申明函数,运用挺巧妙的。
struct esp_timer {
uint64_t alarm;
uint64_t period:56;
flags_t flags:8;
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;
size_t times_skipped;
uint64_t total_callback_run_time;
#endif
LIST_ENTRY(esp_timer) list_entry;
};
typedef struct esp_timer* esp_timer_handle_t;
typedef void (*esp_timer_cb_t)(void* arg);
typedef enum {
ESP_TIMER_TASK,
} esp_timer_dispatch_t;
typedef struct {
esp_timer_cb_t callback;
void* arg;
esp_timer_dispatch_t dispatch_method;
const char* name;
bool skip_unhandled_events;
} esp_timer_create_args_t;
esp_err_t esp_timer_early_init(void);
esp_err_t esp_timer_init(void);
esp_err_t esp_timer_deinit(void);
esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,esp_timer_handle_t * out_handle);
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);
esp_err_t esp_timer_stop (esp_timer_handle_t timer);
esp_err_t esp_timer_delete (esp_timer_handle_t timer);
int64_t esp_timer_get_time(void);
int64_t esp_timer_get_next_alarm(void);
int64_t esp_timer_get_next_alarm_for_wake_up(void);
esp_err_t esp_timer_dump(FILE* stream);
# ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
void esp_timer_isr_dispatch_need_yield(void);
# endif
bool esp_timer_is_active (esp_timer_handle_t计时器);
??esp_timer.h文件中的带参数版本的API部分
struct esp_timer {
uint64_t alarm;
uint64_t period:56;
flags_t flags:8;
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;
size_t times_skipped;
uint64_t total_callback_run_time;
#endif
LIST_ENTRY(esp_timer) list_entry;
};
typedef struct esp_timer* esp_timer_handle_t;
typedef void (*esp_timer_cb_t)(void* arg);
typedef enum {
ESP_TIMER_TASK,
} esp_timer_dispatch_t;
typedef struct {
esp_timer_cb_t callback;
void* arg;
esp_timer_dispatch_t dispatch_method;
const char* name;
bool skip_unhandled_events;
} esp_timer_create_args_t;
esp_err_t esp_timer_early_init(void);
esp_err_t esp_timer_init(void);
esp_err_t esp_timer_deinit(void);
esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,esp_timer_handle_t * out_handle);
esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);
esp_err_t esp_timer_stop (esp_timer_handle_t timer);
esp_err_t esp_timer_delete (esp_timer_handle_t timer);
int64_t esp_timer_get_time(void);
int64_t esp_timer_get_next_alarm(void);
int64_t esp_timer_get_next_alarm_for_wake_up(void);
esp_err_t esp_timer_dump(FILE* stream);
# ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
void esp_timer_isr_dispatch_need_yield(void);
# endif
bool esp_timer_is_active (esp_timer_handle_t计时器);
|