FreeRTOS基础(三):动态创建任务

     上一篇博客,我们讲解了FreeRTOS中,我们讲解了创建任务和删除任务的API函数,那么这一讲,我们从实战出发,规范我们在FreeRTOS下的编码风格,掌握动态创建任务的编码风格,达到实战应用!

目录

一、任务函数

二、动态创建任务的基本步骤

2.1 使能FreeRTOS的API函数

2.2  定义动态创建任务函数的入口参数

2.3 编写任务函数

2.4 主函数进行调用

2.5 补充  

2.6 任务执行顺序

四、动态创建任务的API函数解析(选学)

五、任务优先级

六、总结


一、任务函数

         不论是动态创建任务还是静态创建任务,我们FreeRTOS都是在任务之间切换执行,那么任务函数就是我们单独要实现的功能,根据功能的不同,把裸机系统分割为⼀个个独立的无限循环且无法返回的函数。我们把这种函数称之为任务。即:任务函数是没有返回值,并且是死循环的!任务的形式:如下:

void task1(void *arg)
{//初始化代码while(1) //⽆限循环且不能返回{具体实现的功能}//延时函数
}

1、为什么FreeRTOS的任务函数没有返回值?(可以将任务理解为线程)

1. 持续运行的任务

       FreeRTOS 任务设计为长期运行,不像普通函数那样有明确的结束点。在嵌入式系统中,任务(或者称为线程)通常负责特定的功能,这些功能需要一直运行。例如,处理传感器数据、管理通信协议或维护系统健康状态等。这些功能需要持续监控和响应外部事件或内部条件,因此任务函数通常设计为死循环。

2. 任务调度

       FreeRTOS 是一个实时操作系统,负责在多个任务之间进行调度。任务函数进入死循环后,会周期性地调用 FreeRTOS 提供的 API 函数(如 vTaskDelayxQueueReceive),这些 API 会将任务置于阻塞状态,直到特定条件满足(延时时间到或者信号量接收到)。这种设计允许 RTOS 进行有效的任务切换,确保系统的实时性和多任务处理能力。

3. 没有返回值

      由于任务函数设计为长期运行,因此它们不需要返回值。任务的结束通常不是通过函数返回来实现的,而是通过其他机制,如任务删除 (vTaskDelete)。任务函数的主要目的是在系统运行过程中持续执行特定操作,而不是像传统函数那样在执行完特定操作后返回。

4. 系统稳定性和资源管理

       任务函数设计为死循环还有助于系统的稳定性和资源管理。在 RTOS 中,任务的生命周期由系统管理,任务函数一旦启动,便由调度器根据优先级和调度策略进行管理。死循环的设计简化了任务的生命周期管理,避免了频繁创建和销毁任务带来的资源开销和复杂性。

2、为什么FreeRTOS任务函数的主体是一个死循环?

1、实时性:

       通过使用死循环,任务可以及时检查事件状态并作出相应的处理,以满足实时性

2、持续性:

       将任务放在一个循环中,可以持续执行。如果任务函数没有死循环,而是在任务完成后直接返回,那么任务将会自动退出。这可能导致任务被删除并释放资源,而无法再次调度执行

3、提高资源的利用率:

     只要任务不退出,就不需要重新获取资源,提高效率。

二、动态创建任务的基本步骤

2.1 使能FreeRTOS的API函数

      在使用FreeRTOS任务创建函数之前,我们需要在配置文件里(FreertosConfig.h)将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1,此时便支持动态创建。利用Ctrl+F搜索即可。

2.2  定义动态创建任务函数的入口参数

        通过上一讲我们知道动态创建任务的API函数如下:

其实,我们需要定义的入口参数就是这个API函数的参数,提前定义好,然后传入参数,他就会自动的为我们创建好对应的任务,并且处于一种就绪态。   从上面我们可以看到:

1、任务函数指针:

       其实就是函数名,我们知道函数名就是函数的入口地址,就是一个函数指针

2、任务名字:

        其实也就是函数名对应的字符串,要用双引号括起来

3、任务堆栈大小:

        动态创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 自动从 FreeRTOS 管理的堆中分配,但是我们需要定义好任务栈的大小,使用宏:

#define     START_TASK_STACK_SIZE  128   //定义任务堆栈大小为128字(1字等于4字节)

4、传递给任务的参数:

       不需要传参,我们直接给NULL即可;

5、任务优先级:

        我们使用的是硬件的方式,因此,它要在0-31之间,使用宏定义即可:

#define     START_TASK_PRIO      1    //定义任务优先级,0-31根据任务需求

6、任务句柄:

        这个参数是指向任务控制块的指针,任务控制块TCB其实就是描述任务属性的一个结构体,一次他就是一个结构体指针,我们后续对任务的删除等操作,都是通过该任务句柄进行操作,因此,我们需要提前定义好,然后传入即可,使用宏即可:

TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)

      从上面我们可以知道:其实我们只需要提前利用宏定义好三个参数即可,其他的参数只要任务函数编写好,便可以确定。示例如下:

/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
void start_task(void* args);

注意:

  1. 为了编码规范,我们使用的宏都是大写,虽然较长,但是通俗易懂;
  2. 使用API函数进行任务创建,里面的参数需要进行强制转换,以免报错。
  3. 为了任务执行的顺序是按照我们设定好的优先级执行的,我们可以在创建任务的任务中,使用临界段保护,那么在这个任务体中,可以屏蔽中断(中断优先级在5-15之内)比如切换任务的PendSV,此时,我们创建任务的过程中,不会进行任务的调度,然后我们创建任务结束后,在打开临界段保护,此时不会对所有中断进行屏蔽,也就是任务切换PendSV(中断)才会进行任务调度。如下代码所示,在创建任务开始之前和创建任务之后加入,后面详细讲解。
  4. 动态创建任务函数,有返回值,我们可以在编程时,对返回值进行判断,由此可以知道任务是否创建成功!
#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
void start_task(void* args);/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK1_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK1_PRIO         2             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task1_handler;           //定义任务句柄(结构体指针)
void task1(void* args);/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK2_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK2_PRIO         3             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task2_handler;           //定义任务句柄(结构体指针)
void task2(void* args);/**********************TASK3任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK3_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK3_PRIO         4            //定义任务优先级,0-31根据任务需求
TaskHandle_t   task3_handler;           //定义任务句柄(结构体指针)
void task3(void* args);
开始任务用来创建其他三个任务,只创建一次,不能是死循环,同时创建完3个任务后删除开始任务本身
void start_task(void* args)
{taskENTER_CRITICAL();        /*进入临界区*/BaseType_t xReturn;        //定义接收函数返回值的变量xTaskCreate( (TaskFunction_t)         task1,(char *)     "task1",  ( configSTACK_DEPTH_TYPE)   TASK1_STACK_SIZE,(void *)      NULL,(UBaseType_t) TASK1_PRIO ,(TaskHandle_t *)  &task1_handler );//任务1创建结果的判断if( xReturn == pdPASS){printf("LED_Task create SUCCESS\n");}else{printf("LED_Task create FALL\n");}xTaskCreate( (TaskFunction_t)         task2,(char *)     "task2",  ( configSTACK_DEPTH_TYPE)   TASK2_STACK_SIZE,(void *)      NULL,(UBaseType_t) TASK2_PRIO ,(TaskHandle_t *)  &task2_handler );	//任务2创建结果的判断if( xReturn == pdPASS){printf("LED_Task create SUCCESS\n");}else{printf("LED_Task create FALL\n");}xTaskCreate( (TaskFunction_t)          task3,(char *)     "task3",  ( configSTACK_DEPTH_TYPE)   TASK3_STACK_SIZE,(void *)      NULL,(UBaseType_t) TASK3_PRIO ,(TaskHandle_t *)  &task3_handler );	//任务3创建结果的判断if( xReturn == pdPASS){printf("LED_Task create SUCCESS\n");}else{printf("LED_Task create FALL\n");}vTaskDelete(NULL);    //删除开始任务自身,传参NULLtaskEXIT_CRITICAL();   /*退出临界区*///临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式						
}

2.3 编写任务函数

    对每个任务具体实现的功能进行函数的实现:需要注意,任务函数没有返回值并且是死循环的!

/********其余三个任务的任务函数,无返回值且是死循环***********//***任务1:实现LED0每500ms翻转一次*******/
void task1(void* args)
{while(1){printf("任务1正在运行!\n");GPIO_ToggleBits(GPIOF,GPIO_Pin_9 );vTaskDelay(500);       //FreeRTOS自带的延时函数,会进行任务切换调度}}/***任务2:实现LED1每500ms翻转一次*******/
void task2(void* args)
{while(1){printf("任务2正在运行!\n");GPIO_ToggleBits(GPIOF,GPIO_Pin_10 );vTaskDelay(500);       //FreeRTOS自带的延时函数,会进行任务切换调度}}/***任务3:判断按键KEY0,按下KEY0,任务1删除*******/
void task3(void* args)
{while(1){printf("任务3正在运行!\n");if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)  //表示按键按下{if(task1_handler!=NULL)  //防止重复删除{printf("删除任务1!\n");vTaskDelete(task1_handler);    //删除任务1,传任务1的句柄task1_handler=NULL;}}	  vTaskDelay(10);    //FreeRTOS自带的延时函数,会进行任务切换调度}}

      此外,我们再自定义一个入口函数,用来创建开始任务,然后将要创建的任务全部放在这个开始任务中,主函数只需调用这个入口函数,即可在这个开始任务中 , 创建其他的任务,这样做,规范代码,梳理代码逻辑,清晰易懂任务的运行顺序!如下所示:

//FreeRTO入口例程函数,无参数,无返回值,用来创建开始任务
void freertos_demo(void)
{xTaskCreate( (TaskFunction_t)     start_task,(char *)     "start_task",  ( configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,(void *)      NULL,(UBaseType_t) START_TASK_PRIO ,(TaskHandle_t *)  &start_task_handler );vTaskStartScheduler();  //开启任务调度器}

2.4 主函数进行调用

        在完成上述的编写后,主函数内部只需要引入对应的头文件,然后在函数内部调用相应的函数对使用到的外设进行初始化,然后调用入口函数即可进行按照我们设定的优先级进行任务的调度,如下所示:

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "myled.h"
#include "mykey.h"
#include "myusart.h"#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"    //可以用来单独存放任务函数的声明以及配置相关的宏定义,然后直接引入头文件使用extern TaskHandle_t Start_Handle;  
/*使用任务句柄可以对任务操作,如果没有添加上面的单独头文件存放,
那么使用其他文件的全局变量利用extern关键字引入即可。*/int main(void)
{//1、外设初始化My_UsartInit();LED_Init();KEY_Init();//2、调用入口函数freertos_demo();}

2.5 补充  

       为进行模块化的编程,我们可以将创建相应的头文件可以用来单独存放任务函数的声明以及任务配置相关的宏定义,然后在主函数直接引入头文件使用即可,这样工程结构清晰易懂!

2.6 任务执行顺序

        编写完程序后,一定要进行验证,验证程序是否按照我们设定的顺序及进行执行,类似于操作系统的线程同步问题!

       首先主函数调用入口函数,在入口函数内部创建开始任务函数,该开始任务进入就绪状态,启用任务调度器,调度器启动后,FreeRTOS 将接管系统控制,开始调度任务。此时CPU就会去执行开始任务,然后,在开始任务中创建三个任务,注意:由于使用了临界保护:taskENTER_CRITICAL();        /*进入临界区*/  它会对5-15优先级的中断进行屏蔽,即不会发生作用,其中PendSV是用来任务切换的内核中断,它的优先级是13,因此,会被屏蔽,也就是说,我在创建三个任务的过程中,不会进行其他任务的切换,保证我的开始任务创建其他的三个任务不会被打断!!!创建完三个任务后,它们都进入了就绪态,然后,再删除这个开始任务(因为每个任务只需要创建一次,多次创建占用堆栈内存,造成栈溢出!)此时,我在关闭临界区保护,taskEXIT_CRITICAL();   /*退出临界区*/,也就是打开所有中断,此时PendSV中断就会被打开,按照任务的优先级进行抢占式调度,分别执行任务3、任务2、任务1,在三个任务执行的过程中,加入适当的延时,他就会进行任务的切换,去就绪列表寻找优先级最高的任务去运行!

四、动态创建任务的API函数解析(选学)

五、任务优先级

     在 FreeRTOS 中,任务的优先级决定了任务在系统中的调度顺序和执行时机。设定任务优先级是 FreeRTOS 任务创建过程中一个重要的步骤。

1、优先级的范围

FreeRTOS 任务优先级的范围由 configMAX_PRIORITIES 宏定义。该宏在 FreeRTOSConfig.h 文件中定义。通常,优先级的范围是从 0 到 configMAX_PRIORITIES - 1,优先级数值越大,优先级越高。

2、注意事项

  1. 优先级的相对性:任务的优先级是相对的,系统中最高优先级的任务将获得最多的 CPU 时间。如果多个任务具有相同的优先级,调度器会按照时间片轮转或其他调度策略在它们之间切换。

  2. 优先级反转:在某些情况下,低优先级的任务可能会持有高优先级任务所需的资源,导致优先级反转问题。FreeRTOS 提供了优先级继承机制来解决这个问题。

  3. 优先级设定的策略:设定优先级时,需要考虑任务的重要性和时间敏感性。实时性要求高的任务应设定较高的优先级,而非实时任务可以设定较低的优先级。

  4. 避免过高优先级:设定任务优先级时要避免将所有任务都设为过高的优先级,这样会导致系统缺乏灵活性,可能导致低优先级任务得不到执行。

六、总结

         通过以上的介绍,是不是觉得相比裸机开发确实提升了不少的难度,这就是实时性带来的,万事有利必有弊,多看几遍,相信你对动态创建任务的过程会有清晰的认识,其实步骤也是非常简单的,接下来去实践吧!熟练后就不难了,万事开头难!

温馨提示: 

       对于某个需要知道具体函数的实现的,我们可以双击函数然后直接跳转到定义处,或者Ctrl+F 搜索,也可以去官网查看对应的使用实例:https://www.freertos.org/。

      至此,动态创建任务就已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/20226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用贪心算法进行10进制整数转化为2进制数

十进制整数转二进制数用什么方法?网上一搜,大部分答案都是用短除法,也就是除2反向取余法。这种方法是最基本最常用的,但是计算步骤多,还容易出错,那么还有没有其他更好的方法吗? 一、短除反向取…

AdroitFisherman模块安装日志(2024/5/31)

安装指令 pip install AdroitFisherman-0.0.29.tar.gz -v 安装条件 1:Microsoft Visual Studio Build Tools 2:python 3.10.x 显示输出 Using pip 24.0 from C:\Users\12952\AppData\Local\Programs\Python\Python310\lib\site-packages\pip (python 3.10) Processing c:\u…

matlab GUI界面设计

【实验内容】 用MATLAB的GUI程序设计一个具备图像边缘检测功能的用户界面,该设计程序有以下基本功能: (1)图像的读取和保存。 (2)设计图形用户界面,让用户对图像进行彩色图像到灰度图像的转换…

2.1 OpenCV随手简记(二)

为后续项目学习做准备,我们需要了解LinuxOpenCV、Mediapipe、ROS、QT等知识。 一、图像显示与保存 1、基本原理 1.1 图像像素存储形式 首先得了解下图像在计算机中存储形式:(为了方便画图,每列像素值都写一样了)。对于只有黑白颜色的灰度…

[有监督学习]2.详细图解正则化

正则化 正则化是防止过拟合的一种方法,与线性回归等算法配合使用。通过向损失函数增加惩罚项的方式对模型施加制约,有望提高模型的泛化能力。 概述 正则化是防止过拟合的方法,用于机器学习模型的训练阶段。过拟合是模型在验证数据上产生的误…

Java文件IO

White graces:个人主页 🙉专栏推荐:Java入门知识🙉 🙉 内容推荐:JUC常见类🙉 🐹今日诗词:东风吹柳日初长,雨馀芳草斜阳🐹 ⛳️点赞 ☀️收藏⭐️关注💬卑微小博主&…

Three.js 研究:4、创建设备底部旋转的科技感圆环

1、实现效果 2、PNG转SVG 2.1、原始物料 使用网站工具https://convertio.co/zh/png-svg/进行PNG转SVG 3、导入SVG至Blender 4、制作旋转动画 4.1、给圆环着色 4.2、修改圆环中心位置 4.3、让圆环旋转起来 参考一下文章 Three.js 研究:1、如何让物体动起来 Thre…

“论SOA在企业集成架构设计中的应用”必过模板,突击2024软考高项论文

考题部分 企业应用集成(Enterprise Application Integration, EAI)是每个企业都必须要面对的实际问题。面向服务的企业应用集成是一种基于面向服务体系结构(Service-OrientedArchitecture,SOA)的新型企业应用集成技术,强调将企业和组织内部的资源和业务功…

VSCode界面Outline只显示类名和函数名,隐藏变量名

参考链接 https://blog.csdn.net/Zjhao666/article/details/120523879https://blog.csdn.net/Williamcsj/article/details/122401996 VSCode中界面左下角的Outline能够方便快速跳转到文件的某个类或函数,但默认同时显示变量,导致找某个函数时很不方便。…

mimkatz获取windows10明文密码

目录 mimkatz获取windows10明文密码原理 lsass.exe进程的作用 mimikatz的工作机制 Windows 10的特殊情况 实验 实验环境 实验工具 实验步骤 首先根据版本选择相应的mimikatz 使用管理员身份运行cmd 修改注册表 ​编辑 重启 重启电脑后打开mimikatz 在cmd切换到mi…

Seq2Seq模型:详述其发展历程、深远影响与结构深度剖析

Seq2Seq(Sequence-to-Sequence)模型是一种深度学习架构,专为处理从一个输入序列到一个输出序列的映射任务设计。这种模型最初应用于机器翻译任务,但因其灵活性和有效性,现已被广泛应用于自然语言处理(NLP&a…

医院该如何应对网络安全?

在线医生咨询受到很多人的关注,互联网医疗行业的未来发展空间巨大,但随着医院信息化建设高速发展 医院积累了大量的患者基本信息、化验结果、电子处方、生产数据和运营信息等数据 这些数据涉及公民隐私、医院运作和发展等多因素,医疗行业办…

【QEMU中文文档】1.关于QEMU

本文由 AI 翻译(ChatGPT-4)完成,并由作者进行人工校对。如有任何问题或建议,欢迎联系我。联系方式:jelin-shoutlook.com。 QEMU 是一款通用的开源机器仿真器和虚拟化器。 QEMU 可以通过几种不同的方式使用。最常见的用…

OrangePi AIpro--新手上路

目录 一、SSH登录二、安装VNC Sevice(经测试Xrdp远程桌面安装不上)2.1安装xface桌面2.2 配置vnc服务2.2.1 设置vnc server6-8位的密码2.2.2 创建vnc文件夹,写入xstartup文件2.2.3 给xstartup文件提高权限2.2.4 在安装产生的vnc文件夹创建xsta…

【Uniapp小程序】自定义导航栏uni-nav-bar滚动渐变色

效果图 新建activityScrollTop.js作为mixins export default {data() {return {navBgColor: "rgba(0,0,0,0)", // 初始背景颜色为完全透明navTextColor: "rgba(0,0,0,1)", // 初始文字颜色};},onPageScroll(e) {// 设置背景const newAlpha Math.min((e.s…

PPP认证两种:PAP和CHAP,两次握手和三次握手

CHAP(Challenge-Handshake Authentication Protocol,质询握手认证协议)的设计理念是增强网络认证过程的安全性。在CHAP的三次握手过程中,不直接传送用户的明文密码,以此来提高安全性,具体步骤如下&#xff…

springboot结合mybatis使用多数据源的方式

背景 最近有一个需求,有两个库需要做同步数据,一个Doris库,一个mysql库,两边的表结构一致,这里不能使用navicat等工具提供的数据传输之类的功能,只能使用代码做同步,springboot配置多数据…

如何设置手机的DNS

DNS 服务器 IP 地址 苹果 华为 小米 OPPO VIVO DNS 服务器 IP 地址 中国大陆部分地区会被运营商屏蔽网络导致无法访问,可修改手机DNS解决。 推荐 阿里的DNS (223.5.5.5)或 114 (114.114.114.114和114.114.115.115) 更多公开DNS参考: 苹果…

ESP32-C3模组上实现蓝牙BLE配网功能(1)

本文内容参考: 《ESP32-C3 物联网工程开发实战》 乐鑫科技 蓝牙的名字由来是怎样的?为什么不叫它“白牙”? 特此致谢! 一、蓝牙知识基础 1. 什么是蓝牙? (1)简介 蓝牙技术是一种无线数据和…

Camunda BPM架构

Camunda BPM既可以单独作为流程引擎服务存在,也能嵌入到其他java应用中。Camunda BPM的核心流程引擎是一个轻量级的模块,可以被Spring管理或者加入到自定义的编程模型中,并且支持线程模型。 1,流程引擎架构 流程引擎由多个组件构成,如下所示: API服务 API服务,允许ja…