FreeRTOS 从入门到精通-任务调度

news/2024/11/18 18:46:16/

初写FreeRTOS 从入门到精通系列文章之初,笔者只是当作可以随时回顾的学习笔记来写,并没有想到这些偏技术的文章收获了意料之外的阅读量和关注。首先当然很欣喜自己的文章能够得到了读者们的认可,但同时也有种使命感,既期望启迪并与大家共同提高嵌入式开发的技术,因此我会根据个人的体会和读者的反馈不时添加和补充理论篇文章的内容。相信读者通过系列文章只是了解理论的话都会有种意犹未尽的感觉吧。现在,笔者将结合自己的经验和所阅读的书籍内容,通过一些典型的代码来充分展示FreeRTOS操作系统的特性和能力。本人才疏学浅,这个实战篇系列也只是抛砖引玉。

读者须知

在实战篇系列的文章中,笔者的开发平台将采用ESP32平台。ESP32单片机相较传统的单片机多了WI-FI无线网和蓝牙的功能支持,同时具有更高的CPU处理速度和更大的SRAM存储空间,使之更适用于物联网(IoT),移动设备和可穿戴设备的应用场景。ESP32开发板可以使用Arduino IDE简单上手的开发环境,敏捷快速地用于产品原型机开发和功能验证,方便读者快速复现实战篇的内容。并且,ESP32是双核心芯片,可以用于测试验证FreeRTOS在多核心下的支持程度和性能表现。所有ESP32的代码经过简单修改适配都能运用在任意单片机平台上。

关于ESP32更多的内容以及如何搭建开发平台可以参考笔者的文章

奔腾的心:ESP32 IoT学习实战笔记1- 初识ESP32与搭建Arduino IDE开发环境3 赞同 · 0 评论文章​编辑

任务调度的概念

FreeRTOS对任务的调度采用基于时间片(time slicing)的方式。时间片,顾名思义,把一段时间等分成了很多个时间段,在每一个时间段保证优先级最高的任务能执行,同时如果几个任务拥有相等的优先级,则它们会轮流使用每个时间段占用CPU资源。调度器会在每个时间片结束的时候通过周期中断(tick interrupt)执行一次,调度器根据设置的抢占式还是合作式模式选择哪个任务在下一个时间片会运行。

时间片的大小由configTICK_RATE_HZ这个参数设置。如果configTICK_RATE_HZ设置为10HZ,则时间片的大小为100ms。configTICK_RATE_HZ的值由应用需求决定,通常设为100HZ(时间片大小相应为10ms)。

任务调度的演示

在上图任务调度的演示中,Kernel表示系统内核即调度程序,Task1和Task2是两个优先级相同的任务。t1到t2是一个时间片,t2到t3是另一个时间片。在每一个时间片快结束的时候,调度程序通过周期中断(tick interrupt)被调用并选择在下一个时间片要执行的任务(红色部分代表调度程序Kernel在运行)。此时因为两个任务的优先级相同,调度程序会让两个任务轮流占用时间片进行运行(蓝色部分代表Task1在运行,绿色部分代表Task2在运行)。

案例一

第一个案例将用于测量时间片(time slicing)的长度和看相同优先级的轮流调度

代码如下,可以直接复制到Arduino中运行

TaskHandle_t Task1;
TaskHandle_t Task2;
#define GPIO 16
#define PRIORITY 1 
#define APP_CPU 1static void gpio_on(void *argp) {for (;;) {digitalWrite(GPIO,HIGH);}
}static void gpio_off(void *argp) {for (;;) {digitalWrite(GPIO,LOW);}
}void setup() {pinMode(GPIO,OUTPUT);delay(100);xTaskCreatePinnedToCore(gpio_on,"gpio_on",1024,NULL,PRIORITY,&Task1,APP_CPU);xTaskCreatePinnedToCore(gpio_off,"gpio_off",1024,NULL,PRIORITY,&Task2,APP_CPU);
}void loop() {vTaskDelete(xTaskGetCurrentTaskHandle());
}

本例程创建了两个任务,gpio_on任务用于置IO16口为高,gpio_off任务用于置IO16口为低。ESP32有两个核心CPU0和CPU1, xTaskCreatePinnedToCore函数用把任务指定绑定某个核心运行。最后一个参数APP_CPU为1指定两个任务都在CPU1中运行。vTaskDelete函数用于删除任务,loop()循环中删除自身循环函数用于确保CPU1中只有gpio_on和gpio_off两个任务。因为两个任务的优先级PRIORITY都设置为1,所以调度器会轮流调度两个任务,调度的间隔便是时间片(time slicing)。

我们把IO16口接在示波器,在程序运行时可以关注到如下的波形图

案例一波形图

波形图中间隔为500微秒。可以观察到gpio_on的任务执行时间为1毫秒,gpio_off的任务执行时间为1毫秒,两个任务交替执行,调度器的执行时间可以忽略不计。由此可以推断FreeRTOS在ESP32的Arduino开发环境下时间片为1毫秒,这个又称为滴答周期(tick period)。理论上调度器在1秒至少可以执行1000次任务调度(如果任务在一个时间片内提前结束的话任务调度次数还会更多)

案例二

案例二相较于案例一稍微修改了gpio_on的任务内容,允许在任务内调用调度器直接进行任务切换

TaskHandle_t Task1;
TaskHandle_t Task2;
#define GPIO 16
#define PRIORITY 1 
#define APP_CPU 1static void gpio_on(void *argp) {for ( short x=0; x<1000; ++x ){digitalWrite(GPIO,HIGH);}taskYIELD();
}static void gpio_off(void *argp) {for (;;) {digitalWrite(GPIO,LOW);}
}void setup() {pinMode(GPIO,OUTPUT);delay(100);xTaskCreatePinnedToCore(gpio_on,"gpio_on",1024,NULL,PRIORITY,&Task1,APP_CPU);xTaskCreatePinnedToCore(gpio_off,"gpio_off",1024,NULL,PRIORITY,&Task2,APP_CPU);
}void loop() {vTaskDelete(xTaskGetCurrentTaskHandle());
}

案例二中gpio_on在执行1000次循环后通过taskYIELD函数调用调度器直接进行任务切换到gpio_off任务。

观察到的波形图如下

案例二波形图

波形图中间隔为200微秒,可以看到IO16口下降沿到上升沿之间的间隔小于时间片1毫秒。下降沿发生的时刻在gpio_on通过taskYIELD函数切换到gpio_off任务,然后gpio_off会在gpio_on所在时间片的剩余时间内进行执行直到下次时间片开始调度器重新进行调度。由此可以看出来,每个任务无法保证一定拥有一个完整的时间片。当一个任务在时间片内进行任务调度时,剩余相同优先级的任务会通过“Round Robin”轮流调度,直到下一个时间片开始重新调度。这是应该值得关注的一个现象。


http://www.ppmy.cn/news/1041467.html

相关文章

测试平台的周期性任务(定时任务)设计.

因为我们使用java语言,所以选择了java中最流行的quartz框架 周期性任务配置 启用周期性任务后,前端提供表单, 用户配置: 定时任务的开始结束时间, 循环方式 按照每天或者每个月 或者每个星期, 没分钟 每个时间段下,有二级菜单, 例如, 每x分钟,的第x秒 每x小时 的第y分钟z秒 按…

浅谈SpringBoot启动流程

手写SpringCloud项目地址&#xff0c;深入理解微服务核心原理. github:https://github.com/huangjianguo2000/spring-cloud-lightweight gitee:https://gitee.com/huangjianguo2000/spring-cloud-lightweigh 序目 SpringBoot启动流程就是创建IOC容器的过程。 版本不一样&am…

批量删除文件名前的数字编号?

批量删除文件名前的数字编号&#xff1f;如果你在网上经常下载文件&#xff0c;你会发现下载的文件名称前面一般都会有很的数字编号&#xff0c;这些数字编号有时候会非常的长&#xff0c;导致文件的名称也非常的长&#xff0c;这样对于文件的管理和查找使用是不利的。所以为了…

无监督学习之主成分分析-半导体制造高维数据如何降维

数据降维不只存在于半导体数据中&#xff0c;它是存在于各行各业的&#xff0c;我们要分析的数据维数较多的时候全部输入维数较大这时就要采取降维的方法综合出主要的几列用于我们的分析。 PCA的哲学理念是要抓住问题的主要矛盾进行分析&#xff0c;是将多指标转化为少数几个…

网络编程基础(1)

目录 网络编程解决是跨主机的进程间通讯 1、网络 2、互联网 3、ip地址 &#xff08;1&#xff09;ipv4: &#xff08;2&#xff09;ipV6:1 &#xff08;3&#xff09;IP地址的组成&#xff1a; (4)Linux查看IP地址&#xff1a;ifconfig 4、mac地址 5、ping Ip地址 6…

Vue 2 动态组件和异步组件

先阅读 【Vue 2 组件基础】中的初步了解动态组件。 动态组件与keep-alive 我们知道动态组件使用is属性和component标签结合来切换不同组件。 下面给出一个示例&#xff1a; <!DOCTYPE html> <html><head><title>Vue 动态组件</title><scri…

c# 泛型约束

在C#中&#xff0c;泛型约束用于指定泛型类型参数的限制条件&#xff0c;以确保类型参数满足特定的条件。以下是C#中常见的泛型约束&#xff1a; where T : struct&#xff1a; 这个约束要求类型参数必须是一个值类型&#xff08;如int、float等&#xff09;。 where T : cla…

UE Json Operate 解析嵌套数组

演示如何使用 DTJsonOperate 插件&#xff0c;在蓝图中解析嵌套数组。 比如这个Json {"name": [[[1, 2]],[3, 4],[5, 6]] } 操作演示 最后打印 本功能需要插件支持&#xff0c;插件下载地址。