ESP32 之 ESP

您所在的位置:网站首页 timer组件 ESP32 之 ESP

ESP32 之 ESP

2024-01-08 08:02| 来源: 网络整理| 查看: 265

本文章 来自原创专栏《ESP32教学专栏 (基于ESP-IDF)》,讲解如何使用 ESP-IDF 构建 ESP32 程序,发布文章并会持续为已发布文章添加新内容! 每篇文章都经过了精打细磨!

↓↓↓通过下方对话框进入专栏目录页↓↓↓ CSDN 请求进入目录       _ O x是否进入ESP32教学导航(基于ESP-IDF)?       确定

注意:

本API将在 ESP-IDF-v5.0 之后被弃用,届时你可能仍然可以使用本Timer API,但是建议迁移至新的 GPTimer API。

ESP-IDF v4.x.x 不受影响

如果不必使用硬件定时器,则建议使用软件定时器:

[推荐] ESP-IDF 高分辨率软件定时器 #include "esp_timer.h" 。点击链接至此文章FreeRTOS 提供的Timer API

文章目录 〇、写在前面一、配置定时器1、定时器初始化函数`timer_init()`简介配置结构体`timer_config_t`简介 2、硬件定时器里的数学关系3、装载定时器初值和警报阈值4*、定时器反初始化 二、配置、启用定时器中断1、打开定时器的中断使能2、配置定时器中断1)方式一:配置回调法【推荐】① 创建 ISR Callback 函数② 添加、删除定时器Callback(注册中断) 2)方式二:自定义 ISR 法① 创建ISR中断服务程序(interrupt service routine)② 注册中断 三、示例程序1、配置回调法2、自定义 ISR 法

〇、写在前面

  ESP32 硬件定时器涉及的内容较多,如果理解起来困难,可以看下一篇文章学习一下 软件定时器      本文涉及了一些FreeRTOS基础知识,有可能难理解的内容在本文有讲解。如仍有不懂可评论,可私信。

一、配置定时器

ESP32内置了两个定时器组 Timer Group,每个定时器组都有两个64位定时器Timer。支持向上、向下两个方向计数。支持设置警报阈值。

1、定时器初始化

定时器初始化需要通过函数timer_init传递一个timer_config_t结构体来完成对定时器的初始化。

函数timer_init()简介

1、功能简介 用于初始化定时器 2、返回值

类型名称意义esp_err_tESP_OK配置成功同上ESP_ERR_INVALID_ARG参数错误3、参数类型名称意义---timer_group_ttimer_group目标定时器组索引timer_idx_ttimer_index定时器索引timer_config_tconfig配置结构体 配置结构体timer_config_t简介 struct timer_config_t{ //定时器中断开(1)关(0) timer_alarm_t alarm_en; //定时器运行开(1)关(0) timer_start_t counter_en; //中断类型(一般不需要改动) timer_intr_mode_t intr_type; //向上计数(1)/向下计数(0) timer_count_dir_t counter_dir; //是(1)否(0)自动重装载 timer_autoreload_t auto_reload; //分频倍数 uint32_t divider; } 2、硬件定时器里的数学关系

定时器计数频率关系: f 0 = F c l k k (1) f_{0}=\frac{F_{clk}}{k} \tag{1} f0​=kFclk​​(1) 其中 f 0 f_{0} f0​为定时器的计数频率( H z \rm Hz Hz), F c l k F_{clk} Fclk​为定时器时钟频率( H z \rm Hz Hz), k k k为分频比 (即下文的宏定义TIMER_DRIVER,取 k = 16 k=16 k=16)

定时器值与时间的关系: t = N f 0 (2) t=\frac{N}{f_{0}} \tag {2} t=f0​N​(2) 公式(2)表示时间 t t t 和定时器计数 N N N 的关系,即下文中的(double)val / TIMER_FREQ计算时间(秒)

3、装载定时器初值和警报阈值

1、装载初值 —— 调用函数timer_set_counter_value 三个参数分别为:

【定时器组索引】【定时器索引】【初值】 ( 类型为uint64_t )

例如:

timer_set_counter_value(0, 0, 0x00ull); //初值为0

2、设置警报阈值(遵循计数方向)—— 调用函数timer_set_alarm_value() 三个参数分别为:

【定时器组索引】【定时器索引】【警报阈值】 ( 类型为uint64_t ) 4*、定时器反初始化

取消定时器初始化状态 调用函数timer_deinit 例如

timer_deinit(0, 0); 二、配置、启用定时器中断 1、打开定时器的中断使能

调用函数timer_enable_intr(定时器组索引 , 定时器索引) 例如,打开第 0 组,第 0 个定时器中断使能,使定时器可以触发中断

timer_enable_intr(0, 0); 2、配置定时器中断

ESP32 的硬件定时器中断有两种处理方式:

① 回调配置法   ESP32 硬件定时器在默认情况下有一个 isr 程序,称作 “总 ISR 程序” 。它为我们执行我们配置的定时器的回调。换句话说,我们写的回调函数,配置后将属于这个ISR程序的一部分。因此这个回调函数又称为ISR 回调(ISR callback)   这样做要求你的回调程序要尽可能的简短。② 自定义 ISR 法   如果使用此函数重新注册 ISR,则需要编写完整的 ISR。例如需要清除中断标志位,配置自旋锁之类(详见下文) 1)方式一:配置回调法【推荐】

调用函数timer_isr_callback_add()添加回调 调用函数timer_isr_callback_remove()移除回调

① 创建 ISR Callback 函数

ISR Callback函数结构:

static bool timer_callback(void *args){ uint64_t val; BaseType_t pxHigherPriorityTaskWoken = pdFALSE; // xQueueSendFromISR(queue, (void*)&val, &pxHigherPriorityTaskWoken); return pxHigherPriorityTaskWoken;//请看第 3 行 }

硬件定时器的isr callback函数 需要返回一个bool类型的值:

返回true、pdTRUE等定义为 1 的宏,或直接返回1,意味着ISR程序结束时发生一次上下文切换 返回false、pdFALSE等定义为 0 的宏,或直接返回0,意味着ISR程序结束后不发生上下文切换

上面有一句话BaseType_t pxHigherPriorityTaskWoken = pdFALSE;是为了与FreeRTOS 一致。例如第 5 行注释掉了一个xQueueSendFromISR()宏定义函数,它的第三个参数名为pxHigherPriorityTaskWoken,传入一个BaseType_t类型的指针。作用是如果宏定义函数xQueueSendFromISR()造成了更高优先级任务进入就绪状态,则将这个指针指向的变量赋值为pdTRUE,否则则不发生改变。你可以选择根据这个变量决定有没有必要发生一次上下文切换。对于此callback函数,直接返回这个值即可。如果你在任何情况下,ISR程序结束时都不想发生上下文切换,则一直返回 false 或者FreeRTOS风格的 pdFALSE即可

② 添加、删除定时器Callback(注册中断) 函数timer_isr_callback_add()简介为相应的定时器添加ISR句柄回调。回调应该返回一个bool值,以确定是否需要在ISR结束时执行YIELD。请注意这个ISR处理程序将从一个ISR中调用。这个ISR处理程序不需要处理中断状态,应该保持简短。如果您想实现某些特定的应用程序或编写整个ISR,您可以调用timer_isr_register(…)来注册ISR。函数原型esp_err_t timer_isr_callback_add(timer_group_tgroup_num, timer_idx_ttimer_num, timer_isr_tisr_handler, void *arg, int intr_alloc_flags)返回值ESP_OK SuccessESP_ERR_INVALID_ARG Parameter error参数group_num定时器组索引timer_num定时器索引isr_handlerisr callback函数*arg为 callback 函数传递的参数intr_alloc_flags中断分配标志,请参见esp_intr_alloc.h 函数timer_isr_callback_remove()简介移除通过上一个函数添加的中断函数函数原型esp_err_t timer_isr_callback_remove(timer_group_tgroup_num, timer_idx_ttimer_num)返回值ESP_OK SuccessESP_ERR_INVALID_ARG Parameter error参数group_numTimer group numbertimer_numTimer index of timer group 2)方式二:自定义 ISR 法 ① 创建ISR中断服务程序(interrupt service routine)

一般情况下必要的事:

获取自旋锁timer_spinlock_take(参数:定时器组索引)显式清除中断状态timer_group_clr_intr_status_in_isr(group_idx, index)再次使能中断timer_group_enable_alarm_in_isr(group_idx, index);释放自旋锁timer_spinlock_take(参数:定时器组索引)

自旋锁 是为实现保护共享资源而提出一种锁机制,与互斥锁类似,原理是保证任何时刻最多只能有一个执行单元获得锁,同时获得执行代码的权限。与互斥锁不同的是,互斥锁获取不到时进程将会进入阻塞状态/睡眠状态,直至获取到。而自旋锁则不会进入,而是循环检测是否可以获取自旋锁,进程不会进入睡眠状态。阻塞和唤醒线程都是需要高昂的开销的,如果同步代码块中的内容不复杂,那么可能转换线程带来的开销比实际业务代码执行的开销还要大。所以,使用自旋锁而不是互斥锁在定时器下利大于弊。

之所以需要使用自旋锁,是因为 定时器中断是频繁发生的。不使用锁频繁调用公共资源,危险性很大。同时,在程序设计方面,定时器由于频繁发生,所以代码执行时间要尽可能的小,如果有耗时的任务,可以使用任务通知,FreeRTOS事件,队列等转到任务中处理。

ISR结构

void IRAM_ATTR timerIsr(void *arg){ timer_spinlock_take(0); /* 代码区一 */ timer_group_clr_intr_status_in_isr(0, 0); timer_group_enable_alarm_in_isr(0, 0); /* 代码区二 */ timer_spinlock_give(0); }

注意:必须显式清除中断标志,以及重新使能中断。如上代码区一和代码区二之间的两个函数。(两个函数参数都是定时器组索引和定时器索引)

② 注册中断

调用函数timer_isr_register配置

例如,为第 0 组,第 0 个定时器注册中断

参数简介:

前两个参数分别为 定时器组索引 和 定时器索引(本例为0,0)第三个参数为 ISR函数名第四个参数为 需要传递给ISR函数的参数值 通过 指针传递第五个参数为 用于分配中断的标志 。一个或多个(ORred)ESP_INTR_标志_*值。有关更多信息,请参见esp_intr_alloc.h。接下来的示例我们选用ESP_INTR_FLAG_IRAM作为中断分配标志第六个参数为指向 返回句柄 的指针。如果非空,中断的句柄将在这里返回.

例如,为定时器组0中的定时器0配置一个完整的ISR程序

timer_isr_register(0, 0, timerIsr, &config, ESP_INTR_FLAG_IRAM, NULL); 三、示例程序

以下示例中,定义一个硬件定时器,时长为3秒,每一次中断向一个任务传递定时器计数值。中断后并转到任务来处理(本例使用队列queue)在任务中将其转换为时间(秒)

1、配置回调法 #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/timer.h" #include "freertos/queue.h" #define TIMER_DIVIDER 16 // 硬件定时器分频器 #define TIMER_FREQ (TIMER_BASE_CLK / TIMER_DIVIDER) // 定时器计数频率 //count = TIMER_SCALE * time(second); //time(second) = count / TIMER_SCALE; static xQueueHandle queue; static bool timer_callback(void *args){ uint64_t val; BaseType_t pxHigherPriorityTaskWoken = pdFALSE; val = timer_group_get_counter_value_in_isr(0, 0); /* * 上行代码: * ———————————————————— * 将定时器的值传给一个任务 *(由于本示例使用的自动重装载模式, * 所以在本示例中这个val值无意义。 * 只是为了展示在isr callback中获 * 取定时器值函数的使用【必须 * 调用带有_in_isr的函数】) * ———————————————————— */ //通过队列将 val 传给任务 xQueueSendFromISR(queue, (void*)&val, &pxHigherPriorityTaskWoken); return pxHigherPriorityTaskWoken; } static void task(void *arg){ uint64_t counts = 0ull; while(1){ counts ++; uint64_t val; if(xQueueReceive(queue, &val, portMAX_DELAY)){ // 将传过来的值转化为时间调用下行被注释掉的代码(当定时器处于非自动重载模式时)本示例不演示 // double time = (double)val / TIMER_SCALE; printf("定时器第 %llu 次中断\n", counts); } } } void app_main(void) { queue = xQueueCreate(10, sizeof(uint64_t)); timer_config_t config = { .alarm_en = 1, .counter_en = 0, .counter_dir = TIMER_COUNT_UP, .auto_reload = 1, .divider = 16, }; timer_init(0, 0, &config); timer_set_counter_value(0, 0, 0x00ull); timer_set_alarm_value(0, 0, TIMER_FREQ * 3); timer_enable_intr(0, 0); timer_isr_callback_add(0, 0, timer_callback, NULL, ESP_INTR_FLAG_IRAM); xTaskCreate(task, "timer_test_task", 2048, NULL, 5, NULL); timer_start(0, 0); printf("定时器启动成功!"); } 2、自定义 ISR 法 #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/timer.h" #include "freertos/queue.h" #define TIMER_DIVIDER 16 // Hardware timer clock divider #define TIMER_FREQ (TIMER_BASE_CLK / TIMER_DIVIDER) // convert counter value to seconds //转换公式 //count = TIMER_SCALE * time(second); //time(second) = count / TIMER_SCALE; static xQueueHandle queue; //回调函数 static void IRAM_ATTR timerIsr(void *arg){ timer_spinlock_take(0); uint64_t val = 0; val = timer_group_get_counter_value_in_isr(0, 0); /* * 上行代码: * ———————————————————— * 将定时器的值传给一个任务 *(由于本示例使用的自动重装载模式, * 所以在本示例中这个val值无意义。 * 只是为了展示在isr callback中获 * 取定时器值函数的使用【必须 * 调用带有_in_isr的函数】) * ———————————————————— */ timer_group_clr_intr_status_in_isr(0, 0); timer_group_enable_alarm_in_isr(0, 0); xQueueSendFromISR(queue, &val, NULL); timer_spinlock_give(0); } static void task(void *arg){ uint64_t counts = 0ull; while(1){ counts ++; uint64_t val; if(xQueueReceive(queue, &val, portMAX_DELAY)){ printf("定时器第 %llu 次中断\n", counts); } } } void app_main(void) { queue = xQueueCreate(10, sizeof(uint64_t)); timer_config_t config = { .alarm_en = 1, .counter_en = 0, .counter_dir = TIMER_COUNT_UP, .auto_reload = 1, .divider = 16, }; timer_init(0, 0, &config); timer_set_counter_value(0, 0, 0x00ull); timer_set_alarm_value(0, 0, TIMER_FREQ * 3); timer_enable_intr(0, 0); timer_isr_register(0, 0, timerIsr, &config, ESP_INTR_FLAG_IRAM, NULL); xTaskCreate(task, "timer_test_task", 2048, NULL, 5, NULL); timer_start(0, 0); printf("定时器启动成功!") }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3