【stm32_9.2】FreeRTOS的任务管理:任务策略,调度器启用,任务创建、删除、挂起、恢复

目录

[1. FreeRTOS任务的概念](#1. FreeRTOS任务的概念)

[2. FreeRTOS任务的状态](#2. FreeRTOS任务的状态)

[3. FreeRTOS任务优先级](#3. FreeRTOS任务优先级)

[4. FreeRTOS任务的策略](#4. FreeRTOS任务的策略)

[5. 上下文切换](#5. 上下文切换)

[6. FreeRTOS任务的创建](#6. FreeRTOS任务的创建)

任务的创建xTaskCreate的示例

[7. FreeRTOS启动调度器](#7. FreeRTOS启动调度器)

[8. FreeRTOS任务的删除](#8. FreeRTOS任务的删除)

任务删除vTaskDelete的示例

[9. FreeRTOS任务的挂起](#9. FreeRTOS任务的挂起)

[10. FreeRTOS任务的恢复](#10. FreeRTOS任务的恢复)

任务挂起vTaskSuspend和任务恢复vTaskResume的示例

细节汇总


FreeRTOS的任务管理

1. FreeRTOS任务的概念

FreeRTOS 是一个支持多任务的实时操作系统

  • 之前裸机开发时采用的轮询系统而言,主程序是一个死循环,CPU按照死循环中的流程执行代码。
  • 而在多任务系统中,用户可以把整个系统分割为多个独立的且不能返回(死循环)的函数,这些函数就被称为任务。

应用程序中的任务都是由FreeRTOS的调度器进行调度每个任务具有独立的 空间栈空间其实就是单片机中RAM的一段空间,通常可以提前定义一个全局数组,或者在创建任务的时候对任务的栈空间进行动态的分配,可以参考FreeRTOS的官方资料。

调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。

调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。

重复启停:

做不到并行,所以要频繁的切换任务,来达到每个任务都能跑的效果。

每个任务有自己的堆栈空间,即使任务停止了,处理好的没处理好的,都提前备份好。下一次又执行的时候,再从堆栈空间中的数据恢复回来,再执行一点,再把数据再备份。诸如此类一点一点地更新。

2. FreeRTOS任务的状态

对于FreeRTOS中的任务而言,FreeRTOS的调度器会根据任务的状态决定运行哪个任务,任务的状态一共有四种:运行态、就绪态、挂起态、阻塞态。

如果进入阻塞态和挂起态,CPU会被让渡出去。

3. FreeRTOS任务优先级

FreeRTOS可以为每一个创建的任务分配一个优先级,当然也可以让多个任务使用相同的优先级。

FreeRTOS的任务优先级是没有限制的,并且任务数量也没有限制。

优先级数字越小,则任务的优先级越低。

不要把任务优先级和中断优先级混为一谈。

FreeRTOS只是对应用层做优化,中断是MCU内部资源,中断是照样使用的。

设置时间片轮询的宏定义"configUSE_TIME_SLICING"

4. FreeRTOS任务的策略

FreeRTOS只有两种调度策略:

  1. 抢占式的调度策略
  2. 时间片轮询的调度策略

调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。

调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。

5. 上下文切换

++在任务挂起时++ 保存任务的上下文,++操作系统内核负责确保上下文保持不变++。

++在任务恢复时++ ,其保存的上下文在执行之前由++操作系统内核恢复++。

6. FreeRTOS任务的创建

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,

const char * const pcName,

const uint16_t usStackDepth,

void * const pvParameters,

UBaseType_t uxPriority,

TaskHandle_t * const pxCreatedTask )

  • 默认处于就绪态

参数

|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| TaskFunction_t pxTaskCode | 任务的地址 |
| const char * const pcName | 任务的描述 |
| const uint16_t usStackDepth | 任务的堆栈大小 (stm32的MCU是32bit的,所有堆栈的宽度是32bit。每个任务的堆栈大小应该由任务的复杂度决定。 堆栈宽度 = 宽度 * 参数大小 " configMINIMAL_STACK_SIZE "任务的堆栈大小不要小于这个宏定义的值 " 130 "。) |
| void * const pvParameters | 任务接口的参数,无需传参则填NULL |
| UBaseType_t uxPriority | 任务的优先级,范围 0 ~ configMAX_PRIORITIES - 1 |
| TaskHandle_t * const pxCreatedTask | 任务的句柄,是为了提供给其他函数使用的(建议把任务的句柄进行存储,可以理解为任务的句柄) |

函数调用的嵌套深度:从一个函数跳到另一个函数,出现嵌套,每嵌套一次都要有一块空间来存储函数的堆栈内容。

uxStackDepth = 堆栈宽度 * 自定义uxStackDepth

堆栈宽度:MCU当前为32bit

任务的创建xTaskCreate的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"

void Task( void * pvParameters )
{
	for(;;)
	{
		//控制灯默认不亮
	GPIO_ResetBits(GPIOF,GPIO_Pin_9);
	}
}

int main()
{
	//1.硬件的初始化
	LED_Config();
	
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;			//用于存储创建的任务句柄
	
	//2.创建一个任务
	xReturned = xTaskCreate(
													Task,			//任务的地址
													"Task",		//任务的描述
													64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
													NULL,			//任务接口的参数,无需传参则填NULL
													1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
													&xHandle 	//任务的句柄,是为了提供给其他函数使用的
												);
	//错误处理
	if(xReturned != pdPASS)
	{
		
	}
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

7. FreeRTOS启动调度器

void vTaskStartScheduler( void ):

当创建了多个任务,是需要FreeRTOS的调度器利用相关的调度策略对任务进行调度,如果不启动调度器,则任务是不会被执行。

调度器在运行的时候会生成一个空闲任务(优先级为0)

空闲任务不需要手动创建,它在调度器启动时,自动创建空闲任务。

除非内存不足,否则不会执行到 " vTaskStartScheduler() "后面的代码。

一旦调度器启用之后,所有的任务全面由调度器接管,到底执行哪个任务由调度器的调度策略决定,调度策略由优先级决定。

8. FreeRTOS任务的删除

void vTaskDelete( TaskHandle_t xTask ):

被删除的任务将从所有的就绪、阻塞、挂起和事件的列表中移除。

调用vTaskDelect(),则之前申请的空间就会释放掉。但 " 任务代码分配的内存 "(eg.mallco申请的内存)就不会释放,就要在删除之前释放。

参数

xTask 待删除的任务的句柄。传递NULL将导致调用任务被删除

任务删除vTaskDelete的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"

TaskHandle_t xHandle1 = NULL;			//用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;

void Task1( void * pvParameters )
{
	SendComputerString("task_1 is start\r\n");
	for(;;)
	{
		SendComputerString("task_1 is running\r\n");
		
		vTaskDelete(NULL);
	}
}

void Task2( void * pvParameters )
{
	SendComputerString("task_2 is start\r\n");
	for(;;)
	{
		SendComputerString("task_2 is running\r\n");
		
		vTaskDelete(NULL);
	}
}

int main()
{
	/*现象预判:1.task_2 is start
							2.task_2 is running
							3.task2把自己的任务删除
							4.task_1 is start
							5.task_1 is running
							6.task1把自己的任务删除
	*/
	
	//1.硬件的初始化
	USART1_Config(9600);
	
	//2.创建一个任务
	xTaskCreate(
								Task1,			//任务的地址
								"Task1",		//任务的描述
								64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
								NULL,			//任务接口的参数,无需传参则填NULL
								1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
								&xHandle1 	//任务的句柄,是为了提供给其他函数使用的
							);
	
	xTaskCreate(
								Task2,		
								"Task2",	
								64,			
								NULL,		
								2, 			
								&xHandle2 	
							);
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

9. FreeRTOS任务的挂起

void vTaskSuspend( TaskHandle_t xTaskToSuspend ).

  • task. h
  • 必须将 INCLUDE_vTaskSuspend 定义为 1 才能使用此函数
  • 当任务被挂起之后,自己是无法恢复的
  • 对 vaskSuspend 的调用不会累积次数(就是说,对某个任务挂起多次,恢复该任务只要恢复1次即可)

参数

xTaskToSuspend 被挂起的任务句柄。参数为NULL表示挂起自身

10. FreeRTOS任务的恢复

void vTaskResume( TaskHandle_t xTaskToResume ):

  • 必须将 INCLUDE vTaskSuspend 定义为1才能使用此函数。
  • 挂起的次数是不会累加的,所以恢复一次 " 被挂起的任务 " 就行

参数

xTaskToResume 要恢复的任务句柄。

任务挂起vTaskSuspend和任务恢复vTaskResume的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"

TaskHandle_t xHandle1 = NULL;			//用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;

void Task1( void * pvParameters )
{
	SendComputerString("task_1 is start\r\n");
	for(;;)
	{
		SendComputerString("task_1 is running\r\n");
		
		vTaskResume(xHandle2);
	}
}

void Task2( void * pvParameters )
{
	SendComputerString("task_2 is start\r\n");
	for(;;)
	{
		SendComputerString("task_2 is running\r\n");
		
		vTaskSuspend(NULL);
	}
}

int main()
{
	/*现象预判:1.task_2 is start
							2.task_2 is running
							3.task2把自己挂起了,cpu让渡给task1
							4.task_1 is start
							5.task_1 is running
							6.task1把task2恢复了,task2优先级比task1高且当前系统是抢占式调度策略,task2抢占cpu资源
							7.task_2 is running
							8.task2把自己挂起了,cpu让渡给task1
							9.task_1 is running
							...循环往复
	*/
	
	//1.硬件的初始化
	USART1_Config(9600);
	
	//2.创建一个任务
	xTaskCreate(
								Task1,			//任务的地址
								"Task1",		//任务的描述
								64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
								NULL,			//任务接口的参数,无需传参则填NULL
								1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
								&xHandle1 	//任务的句柄,是为了提供给其他函数使用的
							);
	
	xTaskCreate(
								Task2,		
								"Task2",	
								64,			
								NULL,		
								2, 			
								&xHandle2 	
							);
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

细节汇总

FreeRTOS操作系统要移植到MCU上,MCU上有内核,内核和RTOS做了整合,确保可以正常运行操作系统。

FreeRTOS使用FreeRTOSConfig.h进行定制。

工程在运行的时候是要先运行汇编文件的 " xxx.s ",再从汇编文件跳转到main函数里。

FreeRTOSConfig.h和应用程序(程序/代码)有关,在内核源码RTOS中是找不到的。

这个头文件不能放在 " 与FreeRTOS相关的目录 " 里,必须放在自己的应用程序目录

Linux系统下创建的多线程,是为了用线程去访问进程中的资源。

任务的运行是需要资源的。

把创建的任务交给系统的调度器(管家)。

多任务系统通过调度器和调度策略,可以确保这些任务都能在规定的时间内完成处理和响应。

要搭载"多任务系统"需要调度器,操作系统提供调度器。==> 所以要在MCU上移植操作系统。

抢占式的调度策略无法并发 ,为了不一直运行优先级最高的任务,应该人为的挂起或阻塞最高的优先级,把CPU让渡出来

重用某个任务,让它的优先级最高,但为了让其他任务也能执行,→ 所以给最高优先级的任务使用 "挂起" 或 "延时阻塞".

①"挂起" 自己,但自己无法让自己 "恢复"到就绪态,而其他任务不一定会帮最高优先级task恢复,所以挂起有风险。

②"延时阻塞" 可以把CPU资源让出去一段时间,时间到了,最高优先级task从 "阻塞" 回答 "就绪"。

优先级相同的就绪任务,RTOS采用时间片轮询的调度策略。

优先级不同的就绪任务,RTOS采用抢占式的调度策略。

上下文切换:任务在执行的时候要访问内存空间RAM和ROM、寄存器、堆栈空间等...

每个任务在自己的上下文执行 ==> 每个任务都有自己的堆栈

Q :重复启停每个任务

A :任务有自己的堆栈空间,就算任务现在停止了,就可以把处理好的、没处理好的提前先备份好。等到下一次又能够执行时,在从堆栈空间中把数据恢复回来再执行一点,再把数据备份,在到下次执行把数据从堆栈里读取出来。

优先级低的任务正在运行态,优先级高的任务是就绪态,当前算法是 "抢占式调度策略",此时 "优先级高的任务" 会 "抢占优先级低的任务" 的CPU资源,就算 "优先级低的时间片" 还没用完。

Q :单核MCU,如何让多个任务同时运行?

A :利用调度器,对任务进行频繁的切换。

Q :切换的原则 / 以什么样的规则去切换?

A :调度策略:抢占式调度策略、时间片的调度策略

任务都有优先级,FreeRTOS对任务的数量和优先级没有限制,所以如果使用抢占式的调度策略,我可以给每个任务分配一个任务优先级。

如果使用时间片轮询的调度策略,我可以给任务分配相同的任务优先级。

Q :优先级的规则是什么?

A :在FreeRTOS中的任务优先级是 "优先级的数值越小,则任务的优先级越低"。

Q :如何在MCU上移植FreeRTOS?

A :上章有写

Q :FreeRTOS的功能是如何使用的?

A :FreeRTOS是使用配置文件对实时操作系统进行定制的。

"FreeRTOSConfig.h" ,要启用或禁用某些功能,FreeRTOS提供了若干的宏定义,在"FreeRTOSConfig.h"中去设置宏定义的值,如果设置为0,则表示禁用,非0则表示启用。

Q :为什么通过宏定义就实现了?

A :条件编译。

相关推荐
国产电子元器件1 小时前
ACS712国产替代推荐:电流检测芯片选型指南
单片机·嵌入式硬件·物联网
徐怀江2 小时前
ModusToolbox for vscode使用小记
ide·vscode·单片机·mcu·infineon
谙弆悕博士2 小时前
【附C源码】二叉搜索树的C语言实现
c语言·开发语言·数据结构·算法·二叉树·项目实战·数据结构与算法
洋九八3 小时前
STM32 串口(USART)配置
stm32·单片机·嵌入式硬件
华科大胡子3 小时前
单片机IO不够?ULN2003A救急方案
单片机
时空自由民.3 小时前
MCU 串口 printf 耗时优化方案
单片机·嵌入式硬件
开发者联盟league3 小时前
在cursor中配置c/c++开发环境
c语言·开发语言·c++
『昊纸』℃3 小时前
C语言简介
c语言·操作系统·编程语言·应用领域·历史发展
金色光环4 小时前
【DSP学习】增强型脉宽调制 EPWM 实验-基于普中DSP开发攻略
单片机·学习·dsp开发