【ESP-IDF FreeRTOS】概括介绍&API命名规则&延时函数

我们的ESP-IDF已经将FreeRTOS 作为组件集成到 ESP-IDF 中了。

并且因为原生FreeRTOS是单核的RTOS,而我们的ESP32是有双核的,因此乐鑫为了支持多核,将FreeRTOS 内核移植到 ESP 芯片的所有可用架构中了。移植改过之后的叫ESP-IDF FreeRTOS,ESP-IDF FreeRTOS 是基于原生 FreeRTOS v10.5.1 的 FreeRTOS 实现,其中包含支持 SMP 的大量更新。ESP-IDF FreeRTOS 最多支持两个核(即双核 SMP)。

所以我们可以通过ESP-IDF FreeRTOS来学习FreeRTOS。

这边说一下,一般来说我们说ESP32,是指代ESP32这一大类芯片,而我上面说“ESP32是有双核的”意思就是有部分型号的ESP32是没有双核的。

比如说ESP32-S2,ESP32-H2,ESP32-C等就是单核的,如果想使用ESP-IDF FreeRTOS的双核功能的话(单核芯片可以使用单核功能),在购买前就需要确认好芯片是否为双核。

我们一般买的开发板是ESP32型号的(没错,有个型号就叫ESP32,所以我在上一段强调了一下ESP32的这种说法,大家根据语境自行判断ESP32是指特定型号还是泛指这类芯片),基本是支持双核的,但还是有个版本是单核的。

单核双核其实对于我们学习FreeRTOS是无所谓的,只不过ESP-IDF FreeRTOS中有些函数是双核才能使用的,使用的时候注意一下就好了。

其实我们之前一直都在使用ESP-IDF FreeRTOS,因为我们的主函数是app_main,这其实就是ESP-IDF FreeRTOS的一个任务。

我们的app_main是由一个叫main的任务调用的。

除了main任务,还有一些任务是ESP-IDF FreeRTOS自动创建的。

这些任务做什么的我们可以不用知道,我们只需要保证我们后续创建的任务不重名即可。另外如果使用了蓝牙或是WiFi,那么它们还会另外创建一些后台任务。

这边推荐三个网站来学习,第一个是ESP-IDF的官方文档,有对应的ESP-IDF FreeRTOS的章节。

FreeRTOS (IDF) - ESP32-S3 - — ESP-IDF 编程指南 v5.2.2 文档icon-default.png?t=N7T8https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.2/esp32s3/api-reference/system/freertos_idf.html

第二个是FreeRTOS的官网。

FreeRTOS™ - FreeRTOS™icon-default.png?t=N7T8https://www.freertos.org/

第三个是百问网的FreeRTOS相关内容。

百问网《FreeRTOS入门与工程实践-基于STM32F103》教程-基于DShanMCU-103(STM32F103) | 百问网百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发icon-default.png?t=N7T8https://rtos.100ask.net/zh/FreeRTOS/DShanMCU-F103/

先包含下头文件。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

接下来开始介绍一下关于延时的API。

在介绍API之前我们先简单聊聊FreeRTOS的命名规则。

如果我们大致的翻翻FreeRTOS的API就会看到有很多奇奇怪怪的前缀。

常见的有v,p,x之类的。

v表示的是void,用在函数前缀则表示这个函数的返回值是void。

比如说我们下面这个之前常用的延时函数。

p表示的是pointer,也就是指针。

x表示的是非标准类型,也就是自定义类型。

e表示的是enum,也就是枚举类型。

但也不是一定的,比如说pdTRUE和pdFALSE就貌似和pointer没什么关系。

我们先来看看延时函数。

void vTaskDelay(const TickType_t xTicksToDelay)

这也是我们常用的延时函数。上面介绍一大堆啰啰嗦嗦的,实际上我们就传入一个数,然后这个函数就会延时这么多个时钟周期。

那么一个时钟周期是多久呢?默认应该是10ms(应该哈,我用的ESP32S3,其他型号的没玩过)。

具体怎么查看呢?

我们打开VSCode的下面一个任务栏,点击这个齿轮,等待加载片刻就会有个页面出现在VSCode里面。

然后我们搜索Tick就可以看到频率是100,那么周期自然就是1s/100 = 10ms了。

可以修改吗,当然可以。修改完记得保存。

然后就会发现编译失败。

实际上你打开设置之后,就算什么都不做,编译也是失败的。

这时候我们只需要先clean一下再编译就行了。

clean之后设置界面就消失了,但如果设置完之后保存了,那么修改的内容还是有效的。

一般有什么需要改的配置,最好是在一开始就改好,因为每次设置完需要clean,clean之后重新编译就会像第一次编译那样比较久了。

那我们每次写程序之前都需要打开设置看看时钟周期是多少吗?

当然不用,我们不需要查看时钟周期也可以指定时间。

那就是使用宏。下面介绍两种。portTICK_PERIOD_MSpdMS_TO_TICKS

portTICK_PERIOD_MS的使用方法就是像上面这样拿一个数去除它,得到的数就是我们要延时的毫秒数对应的时钟周期的数量了。像上面这样1000/portTICK_PERIOD_MS得到的数就是我们延时1000ms所需的时钟周期数。

我们再进一步看看portTICK_PERIOD_MS内部是什么。

它里面是1000/另一个宏,注意这边的1000和我上面例子中的1000没有关系,纯属巧合。

而里面这个宏又是另一个宏。

这时我们看到这另外一个宏的名字开头是config,就是设置的意思。

最终我们可以看到,它的值就是我们一开始设置的值。所以理论上说我们修改这个值的效果跟打开设置修改的效果是一样的。

最后我们合起来看,portTICK_PERIOD_MS的值实际上就是10。而我们传给vTaskDelay的值起码得是1吧,所以我们至少得延时10ms(1个时钟周期),但这个我们可以通过设置去修改时钟周期,不过不建议修改,如果需要延时更小单位的时间,建议是使用usleep(#include <unistd.h>)

另一个pdMS_TO_TICKS顾名思义 就是将我们传入的ms转为对应的tick。

其实本质上和上面的portTICK_PERIOD_MS是一样的,所以用哪个都可以。

另外还有个宏是pdTICKS_TO_MS,是把时钟周期数转换成毫秒数的,不要搞混了。

除了vTaskDelay之外,还有另一个延时函数。

BaseType_t xTaskDelayUntil(TickType_t *const pxPreviousWakeTime, const TickType_t xTimeIncrement)

它需要俩参数,第一个是上一次唤醒时间,第二个是时钟周期数。

看不懂对吧,一开始我也看不懂。

举个例子吧,比如说我第一个参数给它传入8点,第二个参数给它传入俩小时,那么不管我在几点的时候调用它,我都会延时到8+2 = 10点,如果我八点调用,那么就是延时俩小时,如果我九点调用,那么我还是延时到10点,也就是延时一小时。

vTaskDelay是不管什么时候调用,都延时固定的时间。而xTaskDelayUntil则是指定一个时间点,以及延时的时间,不管什么什么时候调用,都只延时到一个时间点。

再举个例子,如果我要ESP32每隔10s执行一个任务,那么我可以在一个死循环里先执行任务再用vTaskDelay延时10s,也可以先执行任务,然后使用xTaskDelayUntil传入任务执行前的时间点以及10s对应的时钟周期数。

如果任务很简单,那么二者几乎没差别。

可如果任务很复杂,执行一个任务就需要花费5s,那么使用vTaskDelay的方案中每次循环的时间就会是5(执行任务)+10(延时),也就是15s。

但使用xTaskDelayUntil的方案则是5(执行任务)+5(延时),正好10s,因为xTaskDelayUntil是按照时间点延时的。

并且xTaskDelayUntil调用结束之前还会自动将参数一给加上参数二的值,也就是可以循环使用。

那么就剩最后一个问题了,我们需要获取到时间点,也就是我程序运行到此刻所花费的时钟周期数。

我们用下面两个函数即可获得。

两个函数差别在于一个是普通使用的,另一个是中断函数内使用的。

接下来给个示例,大家就知道怎么使用了。

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

void app_main(void){
    TickType_t lastTime = xTaskGetTickCount();
    while(1){
        printf("hello world\r\n");
        xTaskDelayUntil(&lastTime,pdMS_TO_TICKS(1000));
    }
}

这样就会以一秒为周期打印一次hello world了。

另外这里的延时并不是真正的延时,而是将任务先挂起,等到延时的时间到了再接着执行任务。这里有个好处,那就是不会像裸机延时那样空循环干耗时间,因为FreeRTOS是多任务的,因此当前任务挂起,就可以让CPU去执行其他任务,等到延时时间结束,再接着执行之前的任务,这样可以充分利用CPU的性能。

&lt;think&gt;嗯,用户问的是如何在ESP-IDF中使用ESP32-S3实现IO中断。首先,我需要确认自己对ESP32-S3的GPIO中断机制是否了解。ESP32-S3的中断配置和之前的型号类似,但可能有细微差别,比如GPIO编号或者中断处理函数的注册方式。 用户可能已经知道基本的GPIO操作,比如设置输入输出,但需要具体的中断实现步骤。我应该先回忆ESP-IDF的中断相关API,比如gpio_install_isr_service,gpio_isr_handler_add这些函数。另外,ESP32-S3支持外部中断的GPIO引脚可能和ESP32有所不同,需要确认是否所有GPIO都可用或者有特定限制。 接下来,考虑中断服务程序(ISR)的编写注意事项。比如,ISR应该尽量简短,避免阻塞操作,可能需要使用任务通知或者队列来处理耗时操作。用户可能想知道如何避免中断抖动,可能需要提到硬件消抖或者软件消抖的方法,比如使用定时器延迟检测。 还要考虑不同触发模式,比如上升沿、下降沿、双边沿等,用户可能需要根据具体应用场景选择合适的触发方式。另外,ESP-IDF中的配置步骤,比如设置GPIO模式、上下拉电阻、安装ISR服务等,都是关键点。 用户可能遇到的问题包括中断不触发,这可能是由于GPIO配置错误、中断服务未安装、或者中断标志未清除等原因。在解答时需要提醒检查这些方面。此外,电源管理和低功耗模式下中断的行为也可能需要注意,比如在某些睡眠模式下GPIO中断是否能唤醒芯片。 最后,示例代码的结构很重要,需要展示完整的配置流程,从GPIO初始化到ISR处理函数,再到主循环中的处理。可能还需要提到使用队列在ISR和任务之间传递数据,确保线程安全。&lt;/think&gt;在ESP-IDF框架下为ESP32-S3实现GPIO中断的步骤如下: 一、实现原理 ESP32-S3的GPIO中断通过以下机制实现: 1. 硬件中断控制器自动检测GPIO电平变化 2. 触发条件可配置为:上升沿、下降沿、高低电平、双边沿 3. 通过中断服务程序(ISR)响应事件 二、开发步骤 1. 配置GPIO参数 ```c // 配置GPIO为输入模式 gpio_config_t io_conf = { .pin_bit_mask = (1ULL &lt;&lt; GPIO_NUM), // 设置GPIO编号 .mode = GPIO_MODE_INPUT, // 输入模式 .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_NEGEDGE // 下降沿触发 }; gpio_config(&amp;io_conf); ``` 2. 安装中断服务 ```c // 安装GPIO ISR服务 esp_err_t ret = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1); ESP_ERROR_CHECK(ret); // 注册中断处理函数 gpio_isr_handler_add(GPIO_NUM, isr_handler, (void*)GPIO_NUM); ``` 3. 编写中断处理函数 ```c // IRAM_ATTR确保函数存放于IRAM中 static void IRAM_ATTR isr_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; // 获取中断时间戳 uint64_t intr_time = esp_timer_get_time(); // 发送事件到任务队列(需提前创建) xQueueSendFromISR(gpio_evt_queue, &amp;gpio_num, NULL); } ``` 三、关键注意事项 1. 中断响应时间:典型响应时间&lt;1&mu;s(无其他高优先级中断时) 2. 消抖处理: - 硬件消抖:推荐外接RC电路(0.1&mu;F电容+10k&Omega;电阻) - 软件消抖:在ISR中延时10-50ms后二次检测 3. 中断限制: - 每个GPIO只能注册一个中断处理程序 - 不同GPIO可共享同一ISR,通过参数区分 4. 低功耗模式: - 在Light-sleep模式下仍可响应GPIO中断 - Deep-sleep模式需使用RTC_GPIO唤醒 四、调试建议 1. 常见问题排查: ```c // 检查中断是否使能 esp_log_level_set(&quot;gpio&quot;, ESP_LOG_VERBOSE); // 查看GPIO寄存器状态 printf(&quot;GPIO_STATUS: 0x%08x\n&quot;, GPIO.status); ``` 2. 性能优化: - 使用`IRAM_ATTR`确保ISR在IRAM中运行 - 避免在ISR中调用阻塞式API - 优先使用任务通知代替队列传输 五、完整示例流程 1. 创建FreeRTOS队列 2. 配置GPIO参数 3. 安装ISR服务 4. 创建数据处理任务 5. 在任务循环中处理队列事件 建议参考ESP-IDF官方示例:`examples/peripherals/gpio/generic_gpio`,该示例已针对ESP32-S3优化,包含中断优先级配置和电源管理实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值