0. 实验准备
正点原子 STM32407ZG 探索者开发板
 FreeRTOS 例程模板(可以在这一篇文章找到:STM32F407 移植 FreeRTOS)
1. 动态创建任务函数 API
1.1 函数简介
动态创建任务需要使用到BaseType_t xTaskCreate函数,我们可以在 FreeRTOS 官网中查看此函数详细的文档,点击此处跳转

根据上方的描述我们可以得知,此函数将会创建一个新任务,并将其添加到就绪任务列表中。在 FreeRTOSConfig.h 中,configSUPPORT_DYNAMIC_ALLOCATION 必须设置为 1,或者不定义(在这种情况下,它将默认为1),以便此函数可以使用。
 每个任务都需要用于保存任务状态的 RAM,并被任务用作其堆栈。如果使用 xTaskCreate() 创建任务,则会==从 FreeRTOS 堆中自动分配所需的 RAM ==。如果使用 xTaskCreateStatic() (静态创建任务的函数,后续会讲)创建任务,则 RAM 由应用程序编写器提供,因此可以在编译时静态分配。

1.2 入参详解
同样是在官方文档的下面可以查看到下图的文字

2. 动态创建任务步骤
根据上面的描述,我们可以整理出动态创建任务的步骤有三步:
- 将宏 configSUPPORT_DYNAMIC_ALLOCATION配置为 1
- 定义函数入口参数
- 编写任务函数
3. 删除任务函数 API
4. 编程实战
4.1 实验设计
实现如下的功能:
- 设计四个任务:start_task、task1、task2、task3
- start_task:用来创建其他的三个任务
- task1:实现 LED0 每 500ms 闪烁一次
- task2:实现 LED1 每 500ms 闪烁一次
- task3:判断按键 KEY0 是否按下,按下则删掉 task1
4.2 编写代码
首先打开我们的 FreeRTOS 例程模板(在文章顶部的实验准备中可以找到),打开 FREERTOS_config.h ,找到 configSUPPORT_DYNAMIC_ALLOCATION 配置为 1 ,如下图所示

 然后打开 freertos_demo.c ,如下图所示

 此时的freertos_demo.c 是测试 FreeRTOS 是否移植成功的代码,这里全部进行删除,替换为如下的代码,然后从头开始编写创建任务的代码
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/******************************************************************************************************/
/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
    vTaskStartScheduler();                              /* 开启任务调度器 */
}
其中 freertos_demo 会在主函数中进行调用,这里进行保留
4.2.1 start_task 的动态创建任务的编写
我们可以从官网获取到 xTaskCreate 的模板,也可以根据官网的描述在 task. h 中获取到 xTaskCreate 的函数原型。下面是xTaskCreate 的函数原型
BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            configSTACK_DEPTH_TYPE usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );
下面将以 start_task 为例来讲解任务创建的流程:
- TaskFunction_t pvTaskCode是任务函数,这里是需要我们执行的任务函数的代码,所以我们可以在代码开始的部分对任务函数进行声明,如下所示
void start_task(void *pvParameters);        /* 任务函数 */
- const char * const pcName是任务名字,这里和任务函数的名字保持一致即也是- "start_task"
- configSTACK_DEPTH_TYPE usStackDepth是任务堆栈大小,这里我们可以参考官方的方式也定义一个宏定义,后续可以通过修改宏定义的方式来更改堆栈大小,方便维护
#define START_STK_SIZE  128                 /* 任务堆栈大小 */
- void *pvParameters是函数传参,这里暂时没有于是写- NULL
- UBaseType_t uxPriority是任务优先级,数值越大优先级越靠前,这里也使用宏定义的方式,然后将- start_task的优先级设置为 1,即最低优先级
#define START_TASK_PRIO 1                   /* 任务优先级 */
- TaskHandle_t *pxCreatedTask是任务句柄,需要我们在开头定义一个- TaskHandle_t类型的句柄作为参数传入
TaskHandle_t            StartTask_Handler;  /* 任务句柄 */
- 然后在 freertos_demo中调用,完整的代码如下所示
/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO 1                   /* 任务优先级 */
#define START_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            StartTask_Handler;  /* 任务句柄 */
void start_task(void *pvParameters);        /* 任务函数 */
/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
                (const char*    )"start_task",          /* 任务名称 */
                (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
                (void*          )NULL,                  /* 传入给任务函数的参数 */
                (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
                (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */
    vTaskStartScheduler();                              /* 开启任务调度器 */
}
4.2.2 start_task 的任务编写
start_task 任务的使命是创建 task1、task2、task3 这三个任务,而创建 task1、task2、task3 这三个任务和创建 start_task 的方式一模一样。由于我们只希望 start_task 里的代码执行一次,所以希望他执行后销毁掉自己的任务,所以在 start_task 函数的末尾增加下面的代码来删除掉自己。
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
分别设置 task1-3 的任务优先级为 2-4 ,堆栈大小都为 128,有了 start_task 任务的创建经验,如法炮制的在 start_task 函数中编写出以下的代码,
/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO 1                   /* 任务优先级 */
#define START_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            StartTask_Handler;  /* 任务句柄 */
void start_task(void *pvParameters);        /* 任务函数 */
/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO      2                   /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);             /* 任务函数 */
/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO      3                   /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);             /* 任务函数 */
/* TASK3 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK3_PRIO      4                   /* 任务优先级 */
#define TASK3_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task3Task_Handler;  /* 任务句柄 */
void task3(void *pvParameters);             /* 任务函数 */
/******************************************************************************************************/
/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
                (const char*    )"start_task",          /* 任务名称 */
                (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
                (void*          )NULL,                  /* 传入给任务函数的参数 */
                (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
                (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */
    vTaskStartScheduler();                              /* 开启任务调度器 */
}
/**
 * @brief       start_task
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void start_task(void *pvParameters)
{
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    /* 创建任务3 */
    xTaskCreate((TaskFunction_t )task3,
                (const char*    )"task3",
                (uint16_t       )TASK3_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK3_PRIO,
                (TaskHandle_t*  )&Task3Task_Handler);
    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
}
4.2.3 task1、task2、task3 的任务编写
task1/2 的任务为 500ms 翻转一次 LED0/1,需要注意的是,在这里不能调用普通的延时函数,需要使用 FreeRTOS 提供的延时函数 vTaskDelay(后续会讲解) 来实现延时功能。下面是具体的代码(加入了串口打印的部分,后面会用于查看任务执行顺序)
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
    while(1)
    {
        printf("task1正在运行!!!\r\n");
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}
/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
    while(1)
    {
        printf("task2正在运行!!!\r\n");
        LED1_TOGGLE();
        vTaskDelay(500);
    }
}
task3 的任务需要使用到按键,按键的头文件需要在开头导入一下,编写的代码如下所示
#include "./BSP/KEY/key.h"
/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{
    uint8_t key = 0;
    while(1)
    {
        printf("task3正在运行!!!\r\n");
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            if(Task1Task_Handler != NULL)
            {
                printf("删除task1任务\r\n");
                vTaskDelete(Task1Task_Handler);
                Task1Task_Handler = NULL;
            }
        }
        vTaskDelay(10);
    }
}
至此,全部的代码已经编写完毕,完整的 freertos_demo.c 代码如下所示
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO 1                   /* 任务优先级 */
#define START_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            StartTask_Handler;  /* 任务句柄 */
void start_task(void *pvParameters);        /* 任务函数 */
/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO      2                   /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);             /* 任务函数 */
/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO      3                   /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);             /* 任务函数 */
/* TASK3 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK3_PRIO      4                   /* 任务优先级 */
#define TASK3_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task3Task_Handler;  /* 任务句柄 */
void task3(void *pvParameters);             /* 任务函数 */
/******************************************************************************************************/
/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
                (const char*    )"start_task",          /* 任务名称 */
                (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
                (void*          )NULL,                  /* 传入给任务函数的参数 */
                (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
                (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */
    vTaskStartScheduler();                              /* 开启任务调度器 */
}
/**
 * @brief       start_task
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           /* 进入临界区 */
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    /* 创建任务3 */
    xTaskCreate((TaskFunction_t )task3,
                (const char*    )"task3",
                (uint16_t       )TASK3_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK3_PRIO,
                (TaskHandle_t*  )&Task3Task_Handler);
    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
    while(1)
    {
        printf("task1正在运行!!!\r\n");
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}
/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
    while(1)
    {
        printf("task2正在运行!!!\r\n");
        LED1_TOGGLE();
        vTaskDelay(500);
    }
}
/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{
    uint8_t key = 0;
    while(1)
    {
        printf("task3正在运行!!!\r\n");
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            if(Task1Task_Handler != NULL)
            {
                printf("删除task1任务\r\n");
                vTaskDelete(Task1Task_Handler);
                Task1Task_Handler = NULL;
            }
        }
        vTaskDelay(10);
    }
}



















