【FreeRTOS】详细讲解FreeRTOS中任务管理并通过示例讲述其用法

    任务状态

    在FreeRTOS中一个任务经创建后会有多个状态,通常可分为以下几种状态:

    • 就绪态:新创建的任务一般处于就绪态。处于就绪态的任务表明其已经存在于就绪列表中,其已经具备所有的任务执行需要条件 ,只等待调度器调度运行
    • 运行态:运行态,表明该任务正在占用处理器执行任务 。调度器永远都只会从就绪列表中选取优先级最高任务运行
    • 挂起态:处于挂起态的任务一般都是长时间不允许运行的任务,此时CUP不会处理该任务的任何信息;
    • 阻塞态:任务处于阻塞态,说明该任务不在就绪列表中 ,其正在等待某个时序或者是某个外部中断。通常任务挂起任务延时任务等待信号量都属于阻塞。

    既然如此,各个状态间转换关系又是怎么的呢?

    • 创建任务--->就绪态1:任务经过创建 函数(xTaskCreate/xTaskCreateStatic)后可以直接进入就绪态
    • 就绪态--->运行态2:任务发生切换时,调度器总从就绪列表中选取优先级最高 的任务进入运行态开始运行
    • 运行态--->就绪态3:当有更高优先级任务被创建或恢复 后,调度器就会将新任务变成运行态 ,而原来的任务就会变成阻塞状态 ,直到新任务执行完毕后原任务才会继续运行;看到这大家有没有觉得这玩意有点像中断呢?🤣🤣🤣
    • 运行态--->阻塞态4:当正在执行的任务发生阻塞 (挂起、延时、读取信号量等)时,该任务就会变成阻塞态 ,并且任务还会从就绪列表中删除 ,最后调度器会从就绪列表中执行当前优先级最高的任务
    • 阻塞态--->就绪态5:当阻塞任务恢复 (任务恢复、延时超时、读取信号量超时等)后,该任务会变成就绪态 ,并且重新加入到就绪列表 ,此时调度器仍然执行就绪列表中优先级最高的任务
    • 就绪态--->挂起态6:无论任务处于哪个状态一旦调用API(vTaskSuspend)任务都会切换到挂起状态 ,挂起任务不会获得CPU使用权,更不会参与调度器的调度 ,挂起后调度器仍然调度就绪列表中优先级最高的任务运行
    • 阻塞态--->挂起态7:与6相同;
    • 运行态--->挂起态8:与6相同;
    • 挂起态--->就绪态9:处于挂起态的任务经任务恢复vTaskResume)后,任务会切换到就绪态 ,此时调度器仍然调度就序列表中优先级最高的任务运行

    任务函数

    启动任务调度器 函数原型

    c 复制代码
    void vTaskStartScheduler(void);

    函数参数

    函数说明   函数vTaskStartScheduler是FreeRTOS中一个非常重要的函数,无论是什么样的FreeRTOS程序都需要使用vTaskStartScheduler函数启动调度器,该函数就像一个总开关,只有将其打开才能够使得FreeRTOS正常工作。

    静态任务创建 函数原型

    c 复制代码
    TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                    const char * const pcName, 
                                    const uint32_t ulStackDepth,
                                    void * const pvParameters,
                                    UBaseType_t uxPriority,
                                    StackType_t * const puxStackBuffer,
                                    StaticTask_t * const pxTaskBuffer );
        

    参数解析

    • TaskFunction_t pxTaskCode:设置创建任务所需执行的任务函数;
    • const char * const pcName:设置任务名字;
    • const uint32_t ulStackDepth:设置任务栈长度;
    • void * const pvParameters:传递给任务的参数;
    • UBaseType_t uxPriority:设置任务优先级;
    • StackType_t * const puxStackBuffer:设置任务堆栈;
    • StaticTask_t * const pxTaskBuffer:任务控制块;

    函数说明   静态创建任务函数xTaskCreateStatic,含有七个参数、一个返回值(表示任务是否创建成功)。需要注意的是使用其创建任务时需要传入任务栈

    动态任务创建 函数原型

    c 复制代码
        BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                                const char * const pcName,
                                const configSTACK_DEPTH_TYPE usStackDepth,
                                void * const pvParameters,
                                UBaseType_t uxPriority,
                                TaskHandle_t * const pxCreatedTask);

    参数解析

    • TaskFunction_t pxTaskCode:设置创建任务所需执行的任务函数;
    • const char * const pcName:设置任务名字;
    • const configSTACK_DEPTH_TYPE usStackDepth:设置任务栈大小;
    • void * const pvParameters:传递任务函数参数;
    • UBaseType_t uxPriority:设置任务优先级;
    • TaskHandle_t * const pxCreatedTask:保存任务控制块;

    函数说明   动态创建任务函数,共含有六个参数、一个返回值(返回值也表示任务是否创建成功),与静态创建任务不同之处是该函数不用传入任务栈,其任务栈使用动态方式创建。

    单任务挂起 函数原型

    c 复制代码
    void vTaskSuspend( TaskHandle_t xTaskToSuspend );

    参数解析

    • TaskHandle_t xTaskToSuspend:任务的控制权柄;

    函数说明   通过该函数可以使得某任务进入挂起状态,等待下次任务恢复才有可能继续运行。

    多任务挂起 函数原型

    c 复制代码
    void vTaskSuspendAll( void );

    参数解析

    函数说明   通过函数vTaskSuspendAll能够使得所有的任务都进入挂起状态 ,看起来将所有任务都挂起了,实际上仅仅锁住调度器也就是挂起任务调度器 )。使用该函数虽然锁住了调度器,但系统中断依旧可以正常使用

    单任务恢复 函数原型

    c 复制代码
    void vTaskResume( TaskHandle_t xTaskToResume );

    参数解析

    • TaskHandle_t xTaskToSuspend:任务的控制权柄;

    函数说明   通过函数vTaskResume可以使由挂起函数vTaskSuspend挂起的任务重新恢复。   无论同一任务 在挂起时候调用过多少次vTaskSuspend()函数,也只需调用一次 vTaskResume()函数即可将任务恢复运行;当然,无论调用多少次vTaskResume()函数 ,也只有在任务是挂起态的时候才进行恢复

    多任务恢复 函数原型

    c 复制代码
    BaseType_t xTaskResumeAll( void );

    参数解析

    • 返回值:类型为BaseType_t,用于表示恢复任务是否成功;

    函数说明   通过函数xTaskResumeAll可以使得所有被挂起的任务都恢复。   调度器恢复可以调用xTaskResumeAll()函数,调用多少次的 vTaskSuspendAll()就要调用多少次xTaskResumeAll()进行恢复

    在中断中恢复任务 函数原型

    c 复制代码
    BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );

    参数解析

    • TaskHandle_t xTaskToResume:任务控制权柄;
    • 返回值:类型为BaseType_t,用于表示任务恢复是否成功;

    函数说明   函数xTaskResumeFromISR是在中断中恢复被挂起任务。   使用该函数时必须将INCLUDE_vTaskSuspend INCLUDE_vTaskResumeFromISR 都定义为 1 才有效 。但任务还没有处于挂起态 的时候,调用xTaskResumeFromISR()函数是没有任何意义的

    任务删除 函数原型

    c 复制代码
    void vTaskDelete( TaskHandle_t xTaskToDelete );

    参数解析

    • TaskHandle_t xTaskToDelete:任务的控制句柄;

    函数说明   使用函数vTaskDelete()前需要将INCLUDE_vTaskDelete定义为 1;被删除任务可以从所有就绪、阻塞、挂起和事件列表中删除 。   通过函数vTaskDelete()可以删除某个任务;当删除自身时,传入参数NULL ,但自己的内存并没有得到释放

    任务相对延时 函数原型

    c 复制代码
    void vTaskDelay( const TickType_t xTicksToDelay );

    参数解析

    • const TickType_t xTicksToDelay:延时时间,单位为系统节拍时间 (tick);

    函数说明   使用vTaskDelay()函数前,必须把INCLUDE_vTaskDelay 定义为 1。   函数vTaskDelay()相对地阻塞延时 ,调用该函数后,任务将进入阻塞状态 ,并且让出 CPU 资源。延时时长由形参 xTicksToDelay 决定,单位为系统节拍周期, 比如系统的时钟节拍周期为 1ms,那么调用vTaskDelay(10)的延时时间则为10ms,那么经过从调用vTaskDelay()相对的10ms后任务会解除阻塞,因此,函数vTaskDelay()不适用于周期性执行任务的场合,并且其他任务与中断也会影响该函数正常工作

    任务绝对延时 函数原型

    c 复制代码
    BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime, 
    							const TickType_t xTimeIncrement );

    参数解析

    • TickType_t *pxPreviousWakeTime:指针,指向一个变量,该变量保存任务最后一次解除阻塞的的时刻。第一次使用时,该变量必须初始化为当前时间,之后这个变量会在vTaskDelayUntil()函数内自动更新。
    • const TickType_t xTimeIncrement:周 期 循 环 时 间 。 当 时 间 等 于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数 xTimeIncrement 的值,调用该函数的任务会按照固定频率执行。
    • 返回值:数据类型为BaseType_t,用于检验任务是否实际延迟的值。

    函数说明   在实际使用过程中,一般使用vTaskDelayUntil函数,但是它的实现本质上还是依靠函数xTaskDelayUntil实现。函数重声明如下:

    c 复制代码
    /*
     * vTaskDelayUntil() is the older version of xTaskDelayUntil() and does not
     * return a value.
     */
    #define vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement )                   \
        do {                                                                        \
            ( void ) xTaskDelayUntil( ( pxPreviousWakeTime ), ( xTimeIncrement ) ); \
        } while( 0 )

    使用函数vTaskDelayUntil前,必须把INCLUDE_vTaskDelayUntil 定义为 1。   函数vTaskDelayUntil()延时是绝对性的。   功能上看,函数vTaskDelayUntil() 与函数vTaskDelay () 都是用来实现任务的周期性延时 。但vTaskDelay ()的延时是相对的,是不确定的,它的延时是 vTaskDelay ()调用完毕后开始计算**的。并且 vTaskDelay ()延时时间到了之后,如果有高优先级的任务或者中断正在执行,被延时阻塞的任务并不会马上解除阻塞,所有每次执行任务的周期并不完全确定。vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务 。当(*pxPreviousWakeTime +xTimeIncrement)时间到达后,vTaskDelayUntil()函数立刻返回,如果此任务是最高优先级的,则任务会立马解除阻塞


    示例

    示例1   利用FreeRTOS的空闲任务计时实现LED1、LED2灯闪烁。

    • 步骤一:创建两个FreeRTOS的任务,其优先级都设置为1;
    • 步骤二:完成两个FreeRTOS任务的函数体,每次执行一次任务后就使用函数vTaskSuspend将任务挂起;
    • 步骤三:完成空闲任务函数函数体,每执行一个函数就将计数值加 1 。当空闲任务执行次数到达指定次数时,就使用函数vTaskResume恢复函数。
    c 复制代码
    void task1(void);
    void task2(void);
    
    unsigned int count[2] = {0,0};
    
    /*****************************************
    * 函数功能:freertos工作函数
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void freertosWork(void)
    {
    	/*创建任务两个LED灯闪烁任务*/
    	//存储创建任务的返回值
    	BaseType_t xReturn[2] ;
    	
    	//动态创建任务1
    	xReturn[0] = xTaskCreate(
    				(TaskFunction_t )task1,//任务入口函数
    				(const char *)"task1",//任务名字
    				(uint16_t)512,//任务栈大小
    				(void*)NULL,//任务入口参数
    				1,//任务优先级  优先级越高,任务优先选越高
    				&xHandleTsak[0]//任务控制块
    				);
    	
    	//动态创建任务2
    	xReturn[1] = xTaskCreate(
    				(TaskFunction_t )task2,//任务入口函数
    				(const char *)"task2",//任务名字
    				(uint16_t)512,//任务栈大小
    				(void*)NULL,//任务入口参数
    				1,//任务优先级  优先级越高,任务优先选越高
    				&xHandleTsak[1]//任务控制块
    				);					
    		
    	//创建成功 
    	if (pdPASS == xReturn[0] == xReturn[1])
    		//启动任务,开启调度 
    		vTaskStartScheduler(); 
    	//创建失败
    	else
    		//点亮LED6
    		changeLedStateByLocation(LED6,ON);
    	return 0;
    }
    
    /*****************************************
    * 函数功能:freertos的任务1
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void task1(void)
    {
    	while(1)
    	{
    		//LED1反转状态
    		rollbackLedByLocation(LED1);
    		//挂起任务
    		vTaskSuspend(xHandleTsak[0]);
    	}
    }
    
    /*****************************************
    * 函数功能:freertos的任务2
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void task2(void)
    {
    	while(1)
    	{
    		//LED2状态反转
    		rollbackLedByLocation(LED2);
    		//挂起任务
    		vTaskSuspend(xHandleTsak[1]);
    	}
    }
    
    /*****************************************
    * 函数功能:freertos的空闲任务
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void vApplicationIdleHook (void)
    {
    		++count[0];		
    		if(count[0] % 900000 == 0)
    			//恢复任务1
    			vTaskResume(xHandleTsak[1]);
    		if(count[0] % 1000000 == 0)
    			//恢复任务0
    			vTaskResume(xHandleTsak[0]);
    		if(count[0] % 500000 == 0)
    			rollbackLedByLocation(LED8);
    }

    结果   其结果为LED1、LED2根据空闲任务执行次数来切换LED1与LED2的状态;

    示例2   利用FreeRTOS的空闲任务计时实现LED1、LED2、LED3轮换灯闪烁。

    • 步骤一:创建三个FreeRTOS的任务,其优先级都设置为1;
    • 完成三个FreeRTOS任务的函数体,每次执行一次任务后就使用函数vTaskDelayUntil将任务周期性阻塞,切换各个任务;
    c 复制代码
    void task1(void);
    void task2(void);
    void task3(void);
    
    unsigned int count[2] = {0,0};
    
    /*****************************************
    * 函数功能:freertos工作函数
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void freertosWork(void)
    {
    	/*创建任务两个LED灯闪烁任务*/
    	//存储创建任务的返回值
    	BaseType_t xReturn[2] ;
    	
    	//动态创建任务1
    	xReturn[0] = xTaskCreate(
    				(TaskFunction_t )task1,//任务入口函数
    				(const char *)"task1",//任务名字
    				(uint16_t)512,//任务栈大小
    				(void*)NULL,//任务入口参数
    				1,//任务优先级  优先级越高,任务优先选越高
    				&xHandleTsak[0]//任务控制块
    				);
    	
    	//动态创建任务2
    	xReturn[1] = xTaskCreate(
    				(TaskFunction_t )task2,//任务入口函数
    				(const char *)"task2",//任务名字
    				(uint16_t)512,//任务栈大小
    				(void*)NULL,//任务入口参数
    				1,//任务优先级  优先级越高,任务优先选越高
    				&xHandleTsak[1]//任务控制块
    				);					
    	
    	//动态创建任务3
    	xReturn[2] = xTaskCreate(
    				(TaskFunction_t )task3,//任务入口函数
    				(const char *)"task3",//任务名字
    				(uint16_t)512,//任务栈大小
    				(void*)NULL,//任务入口参数
    				1,//任务优先级  优先级越高,任务优先选越高
    				&xHandleTsak[2]//任务控制块
    				);		
    	//创建成功 
    	if (pdPASS == xReturn[0] == xReturn[1] == xReturn[2])
    		//启动任务,开启调度 
    		vTaskStartScheduler(); 
    	//创建失败
    	else
    		//点亮LED6
    		changeLedStateByLocation(LED6,ON);
    	return 0;
    }
    
    /*****************************************
    * 函数功能:freertos的任务1
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void task1(void)
    {
    	//保存上次任务执行的时间,调用后会再次刷新时间
    	static portTickType PreviousWakeTime;
    	const volatile TickType_t xDelay900ms = pdMS_TO_TICKS( 900UL );
    	//获取当前时间
    	PreviousWakeTime = xTaskGetTickCount();
    	while(1)
    	{
    		//LED1反转状态
    		rollbackLedByLocation(LED1);
    		vTaskDelayUntil( &PreviousWakeTime,xDelay900ms );
    	}
    }
    
    /*****************************************
    * 函数功能:freertos的任务2
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void task2(void)
    {
    
    	//保存上次任务执行的时间,调用后会再次刷新时间
    	static portTickType PreviousWakeTime;
    	const volatile TickType_t xDelay1400ms = pdMS_TO_TICKS( 1400UL );
    	//获取当前时间
    	PreviousWakeTime = xTaskGetTickCount();
    	while(1)
    	{
    		//LED2状态反转
    		rollbackLedByLocation(LED2);	
    		vTaskDelayUntil( &PreviousWakeTime,xDelay1400ms );
    	}
    }
    
    /*****************************************
    * 函数功能:freertos的任务3
    * 函数参数:无
    * 函数返回值:无
    *****************************************/
    void task3(void)
    {
    	
    	//保存上次任务执行的时间,调用后会再次刷新时间
    	static portTickType PreviousWakeTime;
    	const volatile TickType_t xDelay2000ms = pdMS_TO_TICKS( 2000UL );
    	//获取当前时间
    	PreviousWakeTime = xTaskGetTickCount();
    	while(1)
    	{
    		//LED2状态反转
    		rollbackLedByLocation(LED3);	
    		vTaskDelayUntil( &PreviousWakeTime,xDelay2000ms );
    	}
    }

    结果   FreeRTOS创建的三个任务,分别以900ms、1400ms、2000ms的时间间隔轮流执行,视觉上就是LED1、LED2、LED3轮流闪烁。


    小编这里还有一篇关于定时器的文章,也欢迎各位点击观看😉😉😉【FreeRTOS】详细讲解FreeRTOS的软件定时器及通过示例讲述其用法

    最后 ,也欢迎大家留言或私信交流,大家共同进步!😁😁😁

相关推荐
WZF-Sang20 小时前
Linux—进程学习-01
linux·服务器·数据库·学习·操作系统·vim·进程
Goboy1 天前
0帧起步:3分钟打造个人博客,让技术成长与职业发展齐头并进
程序员·开源·操作系统
结衣结衣.2 天前
【Linux】Linux管道揭秘:匿名管道如何连接进程世界
linux·运维·c语言·数据库·操作系统
OpenAnolis小助手2 天前
龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
ai·开源·操作系统·龙蜥社区·服务器操作系统·anolis os
小蜗的房子3 天前
SQL Server 2022安装要求(硬件、软件、操作系统等)
运维·windows·sql·学习·microsoft·sqlserver·操作系统
邂逅岁月4 天前
【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)
java·开发语言·操作系统·线程·进程·并发编程·javaee
CXDNW6 天前
【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、死锁
笔记·操作系统·线程·进程·互斥·死锁
Anemone_6 天前
MIT 6.S081 Lab3
操作系统
掘了7 天前
持久化内存 | Persistent Memory
c++·架构·操作系统
结衣结衣.8 天前
【Linux】掌握库的艺术:我的动静态库封装之旅
linux·运维·服务器·c语言·操作系统·