3.4 FreeRTOS任务创建和删除

任务创建和删除API

任务创建是我们要学习的第一个API函数,而任务创建也是众多API函数中,最复杂的一个。

任务的创建和删除本质就是调用FreeRTOS的API函数。

动态创建任务:任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配 。动态任务创建所申请的空间,是由FreeRTOS所管理的。

静态创建任务:任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供 。

动态创建任务函数

BaseType_t xTaskCreate
( 
	TaskFunction_t 				pxTaskCode,	/* 指向任务函数的指针 */				
	const char * const 			pcName, 	/* 任务名字,最大长度configMAX_TASK_NAME_LEN */
	const 	configSTACK_DEPTH_TYPE 		usStackDepth, 	/* 任务堆栈大小,注意字为单位 */
	void * const 				pvParameters,	/* 传递给任务函数的参数 */
	UBaseType_t 				uxPriority,	/* 任务优先级,范围:0 ~ configMAX_PRIORITIES - 1 */
	TaskHandle_t * const 			pxCreatedTask 	/* 任务句柄,就是任务的任务控制块 */
)

pxTaskCode:指向任务函数的指针,比如我们创建了一个任务,我们这个任务所实现的功能,就是放在这个函数里面。

pcName:这个就是任务名字,任务名字的长度取决于在FreeRTOSConfig中configMAX_TASK_NAME_LEN宏的定义,我们在其中定义了16,所以任务名字最大的长度就是16字符。

usStackDepth:任务的堆栈大小,注意是“字”为单位,比如我们传参的数据是100,实际是100字,400字节。

pvParameters:传递给任务函数的参数,这个我们一般用不到,所以写NULL。

uxPriority:每个任务都有自己的优先级,取值范围,如果是硬件,并且你在设置的FreeRTOSConfig中设置的是0~31,那么这里就可以选择0~31。

pxCreatedTask:我们进行删除任务,就是通过任务句柄来操作的,任务句柄其实就是任务控制块,后续我们会讲解任务控制块是什么。

我们可以看到这个函数是有返回值的,返回的类型是BaseType:

错误的主要原因就是,分配的堆栈太大,我们说过,动态创建实在FreeRTOS所管理的堆栈中去申请的,如果超过了FreeRTOS所能承受的空间,就会申请失败。

实现动态创建任务的流程三步骤:

1.将宏,支持动态申请内存 configSUPPORT_DYNAMIC_ALLOCATION 配置为 1。(FreeRTOSConfig)

2.定义函数的入口参数,我们刚才所说的那6个入口参数。

3.编写任务函数,我们创建这个任务,具体要实现哪些功能。

使用xTaskCreate函数创建的任务,会立刻进入就绪态,由任务调度器调度运行。

当然,上面的三个步骤只是我们写代码会使用到的,其中的内部实现我们这里简单图示一下:

任务控制块结构体成员介绍(TCB结构体):

typedef struct tskTaskControlBlock       
{
    	volatile StackType_t 		* pxTopOfStack; /* 任务栈栈顶,必须为TCB的第一个成员 */
   	ListItem_t 			xStateListItem; /* 任务状态列表项 */      
	ListItem_t 			xEventListItem; /* 任务事件列表项 */     
    	UBaseType_t 			uxPriority; /* 任务优先级,数值越大,优先级越大 */
    	StackType_t 			* pxStack;/* 任务栈起始地址 */
    	char 				pcTaskName[ configMAX_TASK_NAME_LEN ]; 	/* 任务名字 */		
	…
	省略很多条件编译的成员
} tskTCB;

第一个成员是栈顶指针,必须是TCB的第一成员,因为后续我们任务的切换、上下文保存、恢复都息息相关。

状态列表项,当前任务处于什么状态,就保存在这里。

事件列表项,如果当前正在等待某些事件,那么就会使用的事件列表项。

任务优先级,数值越大优先级越大。

任务栈起始地址,就是任务的堆栈的首地址会保存在这里。

…

简单地讲,任务控制块就相当于任务的身份证,保留了这个任务的特征。

静态创建任务函数

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		/* 任务控制块指针,由用户分配 */
); 	

我们可以看到,静态创建任务函数的入口参数同样也是非常多的,大部分是和动态创建任务函数是类似的。

不同点有两个:

任务堆栈,任务堆栈一般是数组,是由用户自己定义的,这就和动态不一样了,动态我们只需要给一个任务堆栈大小,剩下的由FreeRTOS自己来操作。

控制块的指针,就是控制块的地址,这也是由我们自己分配的。

我们可以看到这个函数是有返回值的,返回的类型是TaskHandle_t:

实现静态创建任务的流程五步骤:

1.需将宏,支持静态申请内存configSUPPORT_STATIC_ALLOCATION 配置为 1。

2.定义空闲任务&软件定时器任务的任务堆栈及TCB(任务控制块)。

空闲任务是必须要有的,因为在我们RTOS中,我们的CPU是不能停止的,空闲的时候就需要去执行空闲任务。

软件定时器任务,他是可选的,如果你使能了软件定时器,就需要,这也是在FreeRTOSConfig中使能。

3.实现两个接口函数:

vApplicationGetIdleTaskMemory( );空闲任务的内存赋值。

vApplicationGetTimerTaskMemory ( )*(可选);软件定时器的内存赋值。

4.定义函数的入口参数。

5.编写任务函数。

xTaskCreateStatic()函数创建完成后会立刻进入就绪态,由任务调度器调度运行。

当然,上面的五个步骤只是我们写代码会使用到的,其中的内部实现我们这里简单图示一下:

任务删除函数

用于删除已被创建的任务 ,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除 。

void vTaskDelete(TaskHandle_t xTaskToDelete);

注意:

1.

当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

比如说,我们创建了两个任务:Task1、Task2,我们在Task1的任务函数中调用了一个任务删除,任务删除的句柄是Task2,就会删除Task2任务;如果任务删除的句柄是NULL,那么删除自身即Task1。

2.

动态:空闲任务会负责释放被删除任务中由系统分配的内存,我们动态创建任务,他的内存是由FreeRTOS分配的,静态分配,也就是说,当你的任务是动态任务,是由空闲任务释放内存的。但是有个条件,那就是你是在删除任务本身的时候(NULL),你才会在空闲任务中,执行释放。 如果你删除的是其他任务,那么你就是在当前任务被删除的,而不是在空闲任务被删除。

静态:是由用户自己定义的,但是由用户在任务删除前申请的内存, 则需要由用户在任务被删除前提前释放,否则将导致内存泄露。

实现任务删除的流程两步骤:

1.使用删除任务函数,需将宏INCLUDE_vTaskDelete 配置为 1。

2.入口参数输入需要删除的任务句柄(NULL代表删除本身) 。

当然,上面的两个步骤只是我们写代码会使用到的,其中的内部实现我们这里简单图示一下:

动态任务详解

创建和删除实现

学习目的:学会 xTaskCreate( ) 和 vTaskDelete( ) 的使用。

目标设计:将设计四个任务:start_task、task1、task2、task3。

四个任务的功能如下:

start_task我们只执行1次,为什么?

因为我们创建的任务,我们只需要创建一次,如果我们重复创建会导致什么?

动态创建它的堆栈是在FreeRTOS所管理的内存进行申请的,如果我们一直重复创建,堆栈就肯定不够了。

所以开始任务就只执行一次,然后调用任务删除,把自己给删掉。

实现动态创建任务的流程三步骤:

1.将宏,支持动态申请内存 configSUPPORT_DYNAMIC_ALLOCATION 配置为 1。(FreeRTOSConfig)

2.定义函数的入口参数,我们刚才所说的那6个入口参数。

3.编写任务函数,我们创建这个任务,具体要实现哪些功能。

使用xTaskCreate函数创建的任务,会立刻进入就绪态,由任务调度器调度运行。

根据我们上面所学的三步走,开始第一步骤,将宏改为1:

#define configSUPPORT_DYNAMIC_ALLOCATION    1       /* 1: 支持动态申请内存, 默认: 1 */

第二步,实现函数的入口参数:

在这一步我们需要注意,有下面几个代码,我们第一次看到:

vTaskStartScheduler(),这是开启任务调度器,我们创建完任务,这个函数必须要开启,不然任务是无法调度的。

taskENTER_CRITICAL(),taskEXIT_CRITICAL(),这是进入临界区和退出临界区,进入临界区有什么作用呢?进入临界区之后,新创建的任务就算优先级比较高也不会打断正在运行的任务,正在运行的任务退出临界区后,任务调度器才开始起作用,保护关键代码不被中断。

/*为什么要堆栈定义128?我们这里是偏大定义的,因为你保证不了你在后续在这个任务里要操作什么,可能会定义一个比较大的数组。
每个任务都定义的偏大,但总的空间就这么大,任务太多空间肯定是不够的,所以FreeRTOS就有个API函数,他是可以查询该任务所剩余的历史最小堆栈。
我们可以根据他所剩余的大小,再来重新定义堆栈大小。*/

//开始任务参数
#define START_TASK_STACK_SIZE 128 //定义堆栈大小是128字,即512字节
#define START_TASK_PRIO 				1 //任务优先级
TaskHandle_t start_task_handler;

//任务1参数
#define TASK1_STACK_SIZE 128 //定义堆栈大小是128字,即512字节
#define TASK1_PRIO 				2  //任务优先级
TaskHandle_t task1_handler;

//任务2参数
#define TASK2_STACK_SIZE 128 //定义堆栈大小是128字,即512字节
#define TASK2_PRIO 				3  //任务优先级
TaskHandle_t task2_handler;

//任务3参数
#define TASK3_STACK_SIZE 128 //定义堆栈大小是128字,即512字节
#define TASK3_PRIO 				4  //任务优先级
TaskHandle_t task3_handler;



void freertos_demo(void)
{
	
	xTaskCreate( (TaskFunction_t) start_task, //开始任务函数指针
               (char *) "start_task", //任务名称
               (configSTACK_DEPTH_TYPE)START_TASK_STACK_SIZE,//堆栈大小
               (void *) NULL, //我们一般不用,NULL
               (UBaseType_t) START_TASK_PRIO,//任务优先级
               (TaskHandle_t *) &start_task_handler );//该任务的句柄
															
	vTaskStartScheduler();//开启任务调度器,我们创建完任务,这个函数必须要开启,不然任务是无法调度的。
	
}

//开始任务函数
void start_task(void *pvParameters)
{
		taskENTER_CRITICAL();//进入临界区
	
	//创建任务1
		xTaskCreate( (TaskFunction_t) task1, //开始任务函数指针
               (char *) "task1", //任务名称
               (configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,//堆栈大小
               (void *) NULL, //我们一般不用,NULL
               (UBaseType_t) TASK1_PRIO,//任务优先级
               (TaskHandle_t *) &task1_handler );//该任务的句柄
	//创建任务2
		xTaskCreate( (TaskFunction_t) task2, //开始任务函数指针
               (char *) "task2", //任务名称
               (configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,//堆栈大小
               (void *) NULL, //我们一般不用,NULL
               (UBaseType_t) TASK2_PRIO,//任务优先级
               (TaskHandle_t *) &task2_handler );//该任务的句柄
	//创建任务3		 
		xTaskCreate( (TaskFunction_t) task3, //开始任务函数指针
               (char *) "task3", //任务名称
               (configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,//堆栈大小
               (void *) NULL, //我们一般不用,NULL
               (UBaseType_t) TASK3_PRIO,//任务优先级
               (TaskHandle_t *) &task3_handler );//该任务的句柄
							 
	//删除自身任务
		vTaskDelete(NULL);
	
	taskEXIT_CRITICAL();//退出临界区					 
}

第三步,编写任务函数,实现具体功能:

//任务1,实现LED0每500ms翻转一次。
void task1(void *pvParameters)
{
	while(1)
	{
		LED0_turn();
		vTaskDelay(500);//使用FreeRTOS自带的延时函数
	}
}
//任务2,实现LED1每500ms翻转一次。
void task2(void *pvParameters)
{
	while(1)
	{
		LED1_turn();
		vTaskDelay(500);//使用FreeRTOS自带的延时函数
	}
}
//任务3,判断按键KEY0,按下KEY0,删除task1。
void task3(void *pvParameters)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_Scan();
		if(key == 1)
		{
			//删除任务1
			vTaskDelete(task1_handler);
		}
		vTaskDelay(10);//按键延时
	}
}

以上,我们的三步走就完成了,只需要在主逻辑函数调用freertos_demo(),程序就可以运行啦~

#include "FreeRTOS.h"
#include "task.h"
#include "stm32f4xx.h"                  // Device header
#include "FreeRTOS_Demo.h"
#include "LED.h"
#include "Key.h"

 int main(void)
 {
	LED_Init();
	Key_Init();
	 
	freertos_demo();
}
其他初始化代码

LED初始化

void LED_Init(void){
	    // 使能GPIOF时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

    // 初始化结构体
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // 替换为实际引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速度
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上拉下拉
    GPIO_Init(GPIOF, &GPIO_InitStructure);	
	
		GPIO_SetBits(GPIOF,GPIO_Pin_9); //默认LED9关闭
		GPIO_SetBits(GPIOF,GPIO_Pin_10); //默认LED10关闭
}
void LED0_turn(void){
	if(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_9))
	{
		GPIO_ResetBits(GPIOF,GPIO_Pin_9);
	}
	else
	{
		GPIO_SetBits(GPIOF,GPIO_Pin_9);
	}
}
void LED1_turn(void){
	if(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_10))
	{
		GPIO_ResetBits(GPIOF,GPIO_Pin_10);
	}
	else
	{
		GPIO_SetBits(GPIOF,GPIO_Pin_10);
	}
}

按键初始化

void Key_Init(void)
{
		// 使能GPIOE、GPIOA时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
  	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    // 初始化结构体
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//KEY1
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
    GPIO_Init(GPIOE, &GPIO_InitStructure);	
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//KEY2
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
    GPIO_Init(GPIOE, &GPIO_InitStructure);	
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //KEY0
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
    GPIO_Init(GPIOE, &GPIO_InitStructure);	
		
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//KEY_UP
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; // 下拉
    GPIO_Init(GPIOA, &GPIO_InitStructure);	
}

uint8_t Key_Scan(void){
	
    static uint8_t key_up = 1;  /* 按键按松开标志 */
	
    uint8_t keyval = 0;

    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        delay_ms(10);           /* 去抖动 */
        key_up = 0; //key_up置0,防止多次触发。

        if (KEY0 == 0)  keyval = 1;  /* KEY0按下 */    
        if (KEY1 == 0)  keyval = 2;  /* KEY1按下 */   
        if (KEY2 == 0)  keyval = 3;  /* KEY2按下 */
        if (WK_UP == 1) keyval = 4;  /* KEY_UP按下(即WK_UP) */
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)         /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;              /* 返回键值 */
	
}

 

创建和删除详解

我们已经学会了怎么使用动态创建和删除的API函数,接下来我们对动态创建和删除进行详细的解释,如果你只是想要使用该API函数,而不想了解其是如何实现的,可以跳过一部分。

我这一部分先鸽了,有时间再学  :)

静态任务详解

创建和删除实现

学习目的:学会 xTaskCreateStatic( ) 的使用。

目标设计:将设计四个任务:start_task、task1、task2、task3。

四个任务的功能如下:

其实可以发现,和动态创建任务的方式唯一区别就是,创建的方式不一样,所以我们这三个任务函数是不需要修改的,只需要修改start_task()和freertos_demo()。

实现静态创建任务的流程五步骤:

1.需将宏,支持静态申请内存configSUPPORT_STATIC_ALLOCATION 配置为 1。

2.定义空闲任务&软件定时器任务的任务堆栈及TCB(任务控制块)。

空闲任务是必须要有的,因为在我们RTOS中,我们的CPU是不能停止的,空闲的时候就需要去执行空闲任务。

软件定时器任务,他是可选的,如果你使能了软件定时器,就需要,这也是在FreeRTOSConfig中使能。

3.实现两个接口函数:

vApplicationGetIdleTaskMemory( );空闲任务的内存赋值。

vApplicationGetTimerTaskMemory ( )*(可选);软件定时器的内存赋值。

4.定义函数的入口参数。

5.编写任务函数。

第一步,将支持静态申请内存的宏配置为1.

#define configSUPPORT_STATIC_ALLOCATION   1        /* 1: 支持静态申请内存, 默认: 0 */

我们配置好这个宏之后,编译一下,可以发现有两个报错:

Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

Error: L6218E: Undefined symbol vApplicationGetTimerTaskMemory (referred from timers.o).

这就是我们的第二、三步骤,定义空闲任务&软件定时器任务的任务堆栈及TCB并实现两个接口函数:

可以看到这个报错是出现在tasks.c这个文件中,我们找到这个文件,搜索这个报错函数,这个函数其实是我们开启任务调度器里面会调用的,当我们把支持静态申请内存的宏配置为1时,那么任务调度器就会调用这两个函数。

这个IdleTaskMemory函数有什么作用呢?

我们说过,FreeRTOS他在空闲的时候,会执行一个空闲任务,这是FreeRTOS的基础任务,必须要创建的。

动态创建时,空闲任务的任务堆栈和任务控制块是由FreeRTOS自动分配的。

静态创建时,空闲任务的任务堆栈和任务控制块需要人为的手动提供给他。

静态创建时,通过什么分配空闲任务的内存?就是vApplicationGetIdleTaskMemory。

vApplicationGetIdleTaskMemory(&pxIdleTaskTCBBuffer,&pxIdleTaskStackBuffer, &ulIdleTaskStackSize );

&pxIdleTaskTCBBuffer:指向任务控制块的指针。

&pxIdleTaskStackBuffer:指向任务堆栈的指针。

&ulIdleTaskStackSize:任务堆栈的大小。

我们要人为的去实现这个函数:

//空闲任务内存分配
//定义任务控制块
StaticTask_t idle_task_tcb;
//空闲任务的任务堆栈,128字
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, 
	                                  StackType_t ** ppxIdleTaskStackBuffer, 
                                    uint32_t *pulIdleTaskStackSize )
{
	* ppxIdleTaskTCBBuffer = &idle_task_tcb;
	* ppxIdleTaskStackBuffer = idle_task_stack;
	*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
	

这个TimerTaskMemory 函数有什么作用呢?

这个是定时器任务的内存分配,在静态创建下,也是需要我们人为的去分配的。

//*************************************
//软件定时器内存分配
//定义任务控制块
StaticTask_t timer_task_tcb;
//软件定时器的任务堆栈,128字
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetTimerTaskMemory(StaticTask_t ** ppxTimerTaskTCBBuffer,
																		StackType_t ** ppxTimerTaskStackBuffer, 
																		uint32_t *pulTimerTaskStackSize )
{
	*ppxTimerTaskTCBBuffer = &timer_task_tcb;
	*ppxTimerTaskStackBuffer = timer_task_stack;
	*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
//*************************************

第四步,定义函数入口参数:

void freertos_demo(void)
{
	
	start_task_handler = xTaskCreateStatic( (TaskFunction_t) start_task,//开始任务
                     (char*) "start_task", //任务名称
                     (uint32_t) START_TASK_STACK_SIZE,//任务堆栈大小
                     (void*) NULL,
                     (UBaseType_t)START_TASK_PRIO,//任务优先级
                     (StackType_t *)  start_task_stack,//任务堆栈,我们自己定义
                     (StaticTask_t *) &start_task_tcb );//任务控制块,我们自己定义
							 
	vTaskStartScheduler();//开启任务调度器,我们创建完任务,这个函数必须要开启,不然任务是无法调度的。
	
}

//开始任务函数
void start_task(void *pvParameters)
{
		taskENTER_CRITICAL();//进入临界区
	
	//任务1
			task1_handler = xTaskCreateStatic( (TaskFunction_t) task1,//开始任务
                     (char*) "task1", //任务名称
                     (uint32_t) TASK1_STACK_SIZE,//任务堆栈大小
                     (void*) NULL,
                     (UBaseType_t)TASK1_PRIO,//任务优先级
                     (StackType_t *)  task1_stack,//任务堆栈,我们自己定义
                     (StaticTask_t *) &task_tcb1 );//任务控制块,我们自己定义
										 
	//任务2					 
			task2_handler = xTaskCreateStatic( (TaskFunction_t) task2,//开始任务
                     (char*) "task2", //任务名称
                     (uint32_t) TASK2_STACK_SIZE,//任务堆栈大小
                     (void*) NULL,
                     (UBaseType_t)TASK2_PRIO,//任务优先级
                     (StackType_t *)  task2_stack,//任务堆栈,我们自己定义
                     (StaticTask_t *) &task_tcb2 );//任务控制块,我们自己定义

										 
	//任务3						 
			task3_handler = xTaskCreateStatic( (TaskFunction_t) task3,//开始任务
                     (char*) "task3", //任务名称
                     (uint32_t) TASK3_STACK_SIZE,//任务堆栈大小
                     (void*) NULL,
                     (UBaseType_t)TASK3_PRIO,//任务优先级
                     (StackType_t *)  task3_stack,//任务堆栈,我们自己定义
                     (StaticTask_t *) &task_tcb3 );//任务控制块,我们自己定义
										 
	//删除自身任务
		vTaskDelete(NULL);
	
	taskEXIT_CRITICAL();//退出临界区					 
}

第五步,编写任务函数:

//任务1,实现LED0每500ms翻转一次。
void task1(void *pvParameters)
{
	while(1)
	{
		LED0_turn();
		vTaskDelay(500);//使用FreeRTOS自带的延时函数
	}
}
//任务2,实现LED1每500ms翻转一次。
void task2(void *pvParameters)
{
	while(1)
	{
		LED1_turn();
		vTaskDelay(500);//使用FreeRTOS自带的延时函数
	}
}
//任务3,判断按键KEY0,按下KEY0,删除task1。
void task3(void *pvParameters)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_Scan();
		if(key == 1)
		{
			//删除任务1
			vTaskDelete(task1_handler);
		}
		vTaskDelay(10);//按键延时
	}
}

总结

大家也不难发现,动态任务比静态任务创建起来舒服很多,步骤不会那么繁琐,所以在我们后续的学习中,也都是用动态创建任务了。