任务创建和删除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);//按键延时
}
}
总结
大家也不难发现,动态任务比静态任务创建起来舒服很多,步骤不会那么繁琐,所以在我们后续的学习中,也都是用动态创建任务了。
