0. 实验准备
正点原子 STM32407ZG 探索者开发板
FreeRTOS 例程模板(可以在这一篇文章找到:STM32F407 移植 FreeRTOS)
1. 静态创建任务函数 API
1.1 函数简介
动态创建任务需要使用到BaseType_t xTaskCreate
函数,我们可以在 FreeRTOS 官网中查看此函数详细的文档,点击此处跳转
根据上方的描述我们可以得知,此函数将会创建一个新任务,并将其添加到就绪任务列表中。在 FreeRTOSConfig.h
中,configSUPPORT_STATIC_ALLOCATION
必须设置为 1,或者不定义(在这种情况下,它将默认为 1 ),以便此函数可以使用。
每个任务都需要用于保存任务状态的 RAM ,并被任务用作其堆栈。如果使用 xTaskCreate()
创建任务,则从 FreeRTOS 堆中自动分配所需的 RAM 。如果使用 xTaskCreateStatic()
创建任务,则 RAM 由应用程序编写者提供,因此可以在编译时静态分配。
1.2 入参详解
同样是在官方文档的下面可以查看到下图的文字
- pxTaskCode:
- pcName:
- ulStackDepth:
- pvParameters:
- uxPriority:
- puxStackBuffer:
- pxTaskBuffer:
1.3 返回值
如果 puxStackBuffer
或 pxTaskBuffer
都不是 NULL,那么任务将被创建,并返回任务句柄。如果 puxStackBuffer
或 pxTaskBuffer
为NULL,则任务将不会被创建并返回 NULL。
2. 静态创建任务步骤
根据上面的描述,结合实际的代码需要,使用动态创建任务的步骤有以下几步:
- 将宏
configSUPPORT_STATIC_ALLOCATION
配置为 1 - 定义空闲任务&定时器任务的任务堆栈及 TCB
- 实现两个接口函数:
vApplicationGetIdleTaskMemory( )
和vApplicationGetTimerTaskMemory ( )
——因为我们配置了configSUPPORT_STATIC_ALLOCATION
为1,系统不再使用动态方式管理内存,因此需要用户提供用于提供空闲任务和软件定时器服务任务(如果启用了软件定时器)内存的两个回调函数 - 定义函数入口参数
- 编写任务函数
3. 删除任务函数 API
3.1 函数简介
根据上方的描述我们可以得知,INCLUDE_vTaskDelete
必须定义为1才能使用此函数。此函数可以从 RTOS 内核管理中删除一个任务。被删除的任务将从所有就绪、阻塞、挂起和事件列表中删除。
注意:空闲任务负责从已删除的任务中释放 RTOS 内核分配的内存。因此,如果您的应用程序调用 vTaskDelete()
,那么空闲任务需要处理器的时间是很重要的。由任务代码分配的内存不会自动释放,应该在删除任务之前释放。
3.2 入参详解
- xTask:要删除的任务句柄。传递NULL将导致调用任务(自身)被删除。
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_STATIC_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.1.2 两个回调函数的编写
我们在 freertos_demo.c
文件中编写这两个回调函数,具体代码如下所示
/* 空闲任务任务堆栈 */
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
/* 空闲任务控制块 */
static StaticTask_t IdleTaskTCB;
/* 定时器服务任务堆栈 */
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
/* 定时器服务任务控制块 */
static StaticTask_t TimerTaskTCB;
/**
* @brief 获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的静态内存,因此空闲任务的任务堆栈和任务控制块的内存就由用户提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()实现此函数即可。
* @param ppxIdleTaskTCBBuffer:任务控制块内存
ppxIdleTaskStackBuffer:任务堆栈内存
pulIdleTaskStackSize:任务堆栈大小
* @retval 无
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &IdleTaskTCB;
*ppxIdleTaskStackBuffer = IdleTaskStack;
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/**
* @brief 获取定时器服务任务的任务堆栈和任务控制块内存
* @param ppxTimerTaskTCBBuffer:任务控制块内存
ppxTimerTaskStackBuffer:任务堆栈内存
pulTimerTaskStackSize:任务堆栈大小
* @retval 无
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer = &TimerTaskTCB;
*ppxTimerTaskStackBuffer = TimerTaskStack;
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
4.2.2 start_task 的静态创建任务的编写
我们可以从官网获取到 xTaskCreateStatic
的模板,也可以根据官网的描述在 task. h
中获取到 xTaskCreateStatic
的函数原型。下面是xTaskCreateStatic
的函数原型
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 pvTaskCode
是任务函数,这里是需要我们执行的任务函数的代码,所以我们可以在代码开始的部分对任务函数进行声明,如下所示
void start_task(void *pvParameters); /* 任务函数 */
const char * const pcName
是任务名字,这里和任务函数的名字保持一致即也是"start_task"
const uint32_t ulStackDepth
是任务堆栈大小,这里我们可以参考官方的方式也定义一个宏定义,后续可以通过修改宏定义的方式来更改堆栈大小,方便维护
#define START_STK_SIZE 128 /* 任务堆栈大小 */
void *pvParameters
是函数传参,这里暂时没有于是写NULL
UBaseType_t uxPriority
是任务优先级,数值越大优先级越靠前,这里也使用宏定义的方式,然后将start_task
的优先级设置为 1,即最低优先级
#define START_TASK_PRIO 1 /* 任务优先级 */
StackType_t * const puxStackBuffer
是用于指定该任务的堆栈地址StaticTask_t * const pxTaskBuffer
是任务控制块,需要我们在开头定义一个TaskHandle_t
类型的控制块作为参数传入
TaskHandle_t StartTaskTCB; /* 任务控制块 */
- 然后在
freertos_demo
中调用,完整的代码如下所示
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t StartTaskStack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t StartTask_Handler; /* 任务句柄 */
StaticTask_t StartTaskTCB; /* 任务控制块 */
void start_task(void *pvParameters); /* 任务函数 */
void freertos_demo(void)
{
StartTask_Handler = xTaskCreateStatic((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint32_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(StackType_t* )StartTaskStack, /* 任务堆栈 */
(StaticTask_t* )&StartTaskTCB); /* 任务控制块 */
vTaskStartScheduler(); /* 开启任务调度器 */
}
4.2.3 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
函数中编写出以下的代码,
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task1Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
StaticTask_t Task1TCB; /* 任务控制块 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task2Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
StaticTask_t Task2TCB; /* 任务控制块 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task3Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
StaticTask_t Task3TCB; /* 任务控制块 */
void task3(void *pvParameters); /* 任务函数 */
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
Task1Task_Handler = xTaskCreateStatic((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(StackType_t* )Task1Stack,
(StaticTask_t* )&Task1TCB);
/* 创建任务2 */
Task2Task_Handler = xTaskCreateStatic((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(StackType_t* )Task2Stack,
(StaticTask_t* )&Task2TCB);
/* 创建任务3 */
Task3Task_Handler = xTaskCreateStatic((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(StackType_t* )Task3Stack,
(StaticTask_t* )&Task3TCB);
vTaskDelete(StartTask_Handler); /* 删除开始任务*/
taskEXIT_CRITICAL(); /* 退出临界区 */
}
4.2.4 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 的任务需要使用到按键,按键的头文件需要在开头导入一下,由于按下按键我们希望删除 task1 ,但是如果当 task1 已经被删除了,再次按下按键就会出现不可预估的后果,所以在删除 task1 前,我们先对他的句柄进行一个非空判断,在删除 task1 后,将 task1 的句柄设置为空。编写的代码如下所示
#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 /* 任务堆栈大小 */
static StackType_t StartTaskStack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t StartTask_Handler; /* 任务句柄 */
StaticTask_t StartTaskTCB; /* 任务控制块 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task1Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
StaticTask_t Task1TCB; /* 任务控制块 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task2Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
StaticTask_t Task2TCB; /* 任务控制块 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
static StackType_t Task3Stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
StaticTask_t Task3TCB; /* 任务控制块 */
void task3(void *pvParameters); /* 任务函数 */
/* 空闲任务任务堆栈 */
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
/* 空闲任务控制块 */
static StaticTask_t IdleTaskTCB;
/* 定时器服务任务堆栈 */
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
/* 定时器服务任务控制块 */
static StaticTask_t TimerTaskTCB;
/**
* @brief 获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的静态内存,因此空闲任务的任务堆栈和任务控制块的内存就由用户提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()实现此函数即可。
* @param ppxIdleTaskTCBBuffer:任务控制块内存
ppxIdleTaskStackBuffer:任务堆栈内存
pulIdleTaskStackSize:任务堆栈大小
* @retval 无
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &IdleTaskTCB;
*ppxIdleTaskStackBuffer = IdleTaskStack;
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/**
* @brief 获取定时器服务任务的任务堆栈和任务控制块内存
* @param ppxTimerTaskTCBBuffer:任务控制块内存
ppxTimerTaskStackBuffer:任务堆栈内存
pulTimerTaskStackSize:任务堆栈大小
* @retval 无
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer = &TimerTaskTCB;
*ppxTimerTaskStackBuffer = TimerTaskStack;
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
StartTask_Handler = xTaskCreateStatic((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint32_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(StackType_t* )StartTaskStack, /* 任务堆栈 */
(StaticTask_t* )&StartTaskTCB); /* 任务控制块 */
vTaskStartScheduler(); /* 开启任务调度器 */
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
/* 创建任务1 */
Task1Task_Handler = xTaskCreateStatic((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(StackType_t* )Task1Stack,
(StaticTask_t* )&Task1TCB);
/* 创建任务2 */
Task2Task_Handler = xTaskCreateStatic((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(StackType_t* )Task2Stack,
(StaticTask_t* )&Task2TCB);
/* 创建任务3 */
Task3Task_Handler = xTaskCreateStatic((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(StackType_t* )Task3Stack,
(StaticTask_t* )&Task3TCB);
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);
}
}
4.2.4 验证
下载编译完成后的代码到开发板上,可以看到 LED0 和 1 以同样的频率进行闪烁,按下 KEY0 后 LED0 停止闪烁,和预计效果一致。将开发板的串口连接到电脑上,连接到串口,重新开机查看串口打印的信息,注意任务的执行顺序,如下图所示
可以看到先执行了 task1 先执行了,然后是 task2 和 task3,这与我们设置的优先级的顺序并不一致,这是因为在下图的函数中,打开了任务调度器,任务调度器会在就绪列表中选择任务执行,而在 start_task
函数中,我们依次创建了 task1、2、3 ,在刚创建完成 task1 时,task1 就被放入到了就绪列表(这里在上面的动态创建函数地方有讲解到),立马就被调度了,所以 task1 虽然优先级最低但是最先被执行了
如果我们想要在创建任务的时候不被打断(这里是希望不要被调度),可以在创建任务之前添加如下的代码
taskENTER_CRITICAL(); /* 进入临界区 */
在创建任务之后添加如下的代码
taskEXIT_CRITICAL(); /* 退出临界区 */
上面的两行代码的作用可以理解为,在这两行代码之间的操作不希望被打断,是可以被连续执行的,添加完成这两行代码后,代码执行的逻辑就应该为:
进入临界区,创建 task1 进入就绪队列,创建 task2 进入就绪队列,创建 task3 进入就绪队列,退出临界区,开始任务调度—选择优先级最高的任务也就是 task3 进入运行
4.2.5 修改代码
根据上面一小节的逻辑,我们砸 start_task
函数中进行修改。修改完成后的代码如下所示
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
Task1Task_Handler = xTaskCreateStatic((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(StackType_t* )Task1Stack,
(StaticTask_t* )&Task1TCB);
/* 创建任务2 */
Task2Task_Handler = xTaskCreateStatic((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(StackType_t* )Task2Stack,
(StaticTask_t* )&Task2TCB);
/* 创建任务3 */
Task3Task_Handler = xTaskCreateStatic((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(StackType_t* )Task3Stack,
(StaticTask_t* )&Task3TCB);
vTaskDelete(StartTask_Handler); /* 删除开始任务*/
taskEXIT_CRITICAL(); /* 退出临界区 */
}
4.2.6 再次验证
把修改后的代码再次烧录进开发板,然后查看任务执行顺序,发现按照我们设定的优先级顺序进行执行了