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.rhkb.cn/news/339019.html

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

相关文章

解决kettle界面右上角的connect消失——且使用admin登录不上Kettle资源库

一、问题描述 1.1、Kettle界面右上角的connect消失了 当我们配置Kettle界面的资源库(Other Repositories)内容后,Kettle界面右上角的connect消失了;如下图所示: 1.2、使用默认的账户【admin】和密码【admin】登录不上kettle资源库 当我们切换到我们配置的数据库使用超管账…

AGM DAP-LINK 离线烧录报错信息分析

DAP-LINK 支持离线烧录。 即:先把要烧录的bin 烧录到DAP-LINK 中;然后DAP-LINK 可以脱离PC,上电后通过按键对目标板进行烧录。 CMSIS-DAP模式 跳线JGND断开,状态LED D4快闪,D3常亮(串口状态)。…

Android关闭硬件加速对PorterDuffXfermode的影响

Android关闭硬件加速对PorterDuffXfermode的影响 跑的版本minSdk33 编译SDK34 import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Port…

LabVIEW与欧陆温控表通讯的实现与应用:厂商软件与自主开发的优缺点

本文探讨了LabVIEW与欧陆温控表通讯的具体实现方法,并对比了使用厂商提供的软件与自行开发LabVIEW程序的优缺点。通过综合分析,帮助用户在实际应用中选择最适合的方案,实现高效、灵活的温控系统。 LabVIEW与欧陆温控表通讯的实现与应用&#…

【Linux】网络高级IO

欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 🏠个人专栏:Linux 目录 👉🏻五种IO模型👉🏻消息通信的同步异步与进程线程的同步异步有什么不同?&#x1f449…

YOLOv8改进(一)-- 轻量化模型ShuffleNetV2

文章目录 1、前言2、ShuffleNetV2代码实现2.1、创建ShuffleNet类2.2、修改tasks.py2.3、创建shufflenetv2.yaml文件2.4、跑通示例 3、碰到的问题4、目标检测系列文章 1、前言 移动端设备也需要既准确又快的小模型。为了满足这些需求,一些轻量级的CNN网络如MobileNe…

【2024新版】银系统源码/超市收银系统/智慧新零售/ERP进销存管理/线上商城/商户助手

>>>系统简述:本系统适用于超吃便利店,美妆母婴行业,服装鞋帽行业,食品零售行业,3C数码电子行业,食品生鲜等一切零售行业,产品功能角色介绍如下 合伙人:无限发展代理商和商…

OpenMV学习笔记3——画图函数汇总

画图,即在摄像头对应位置画出图形,对于需要反馈信息的程序来说很直观。就如上一篇文章颜色识别当中的例子一样,我们在识别出的色块上画出矩形方框,并在中间标出十字,可以直观的看到OpenMV现在识别出的色块。 目录 一…

Nginx源码编译安装

Nginx NginxNginx的特点Nginx的使用场景Nginx 有哪些进程 使用源码编译安装Nginx准备工作安装依赖包编译安装Nginx检查、启动、重启、停止 nginx服务配置 Nginx 系统服务方法一:方法二: 访问Nginx页面 升级Nginx准备工作编译安装新版本Nginx验证 Nginx N…

安卓启动 性能提升 20-30% ,基准配置 入门教程

1.先从官方下载demohttps://github.com/android/codelab-android-performance/archive/refs/heads/main.zip 2.先用Android studio打开里面的baseline-profiles项目 3.运行一遍app,这里建议用模拟器,(Pixel 6 API 34)设备运行&a…

未来已来:Spring Boot引领数据库智能化革命

深入探讨了Spring Boot如何与现代数据库技术相结合,预测并塑造未来的数据访问趋势。本书不仅涵盖了Spring Data JPA的使用技巧,还介绍了云原生数据库的概念,微服务架构下的数据访问策略,以及AI在数据访问层的创新应用。旨在帮助开…

【docker】docker的安装

如果之前安装了旧版本的docker我们需要进行卸载: 卸载之前的旧版本 卸载 # 卸载旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 卸载历史版本 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker…

Redis学习笔记【实战篇--短信登录】

开篇导读 实战篇有什么样的内容 短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更…

mfc140u.dll丢失的解决方法有哪些?怎么全面修复mfc140u.dll文件

mfc140u.dll丢失其实相对来说不太常见到,因为这个文件一般是不丢失的,不过既然有人遇到这种问题,那么小编一定满足各位,给大家详细的唠叨一下mfc140u.dll丢失的各种解决方法,教大家以最快最有效率的方法去解决mfc140u.…

Low Memory Killer in Android

目录 低内存管理(Linux vs Android) Linux内存回收 shrink_slab原理 shrink_zone原理 oom killer oom killer设计原则 OOM killer具体实现 android的lmk(Low Memory Killer) Android系统特点 oom killer在android中的不足 ​​​​​​​LMK概…

探索 Python 的 vars() 函数

大家好,在软件开发的过程中,调试是一个不可或缺的环节。无论你是在解决 bug,优化代码,还是探索代码的执行流程,都需要一些有效的工具来帮助你更好地理解和调试代码。在 Python 编程中,vars() 函数是一个非常…

国产可视化爬虫助力AI大模型训练:精准爬取汉语词典

大语言模型,可以生成流畅对话的会话聊天机器人、通畅起草文章的内容生成器。在炫酷技术的背后,数据、算力、算法,被视作生成式AI的三个核心要素。由此可见,高质量的训练数据对于AI算法的准确性至关重要。 如何获得高质量的训练数…

【嵌入式硬件】DRV8874电机驱动

目录 1 芯片介绍 1.1 特性简介 1.2 引脚配置 1.3 最佳运行条件 2 详细说明 2.1 PMODE配置控制模式 2.1.1 PH/EN 控制模式 2.1.2 PWM 控制模式 2.1.3 独立半桥控制模式 2.2 电流感测和调节 2.2.1 IPROPI电流感测 2.2.2 IMODE电流调节 3.应用 3.1设计要求 3.2 设计…

数据结构严蔚敏版精简版-绪论

1.基本概念和术语 下列概念和术语将在以后各章节中多次出现,本节先对这些概念和术语赋予确定的含义。 数据(Data):数据是客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号 的总称。 数据元素(DataElement):…

JVM运行时数据区 - 程序计数器

运行时数据区 Java虚拟机在执行Java程序的过程中,会把它管理的内存划分成若干个不同的区域,这些区域有各自的用途、创建及销毁时间,有些区域随着虚拟机的启动一直存在,有些区域则随着用户线程的启动和结束而建立和销毁&#xff0…