在上一篇文章中我们进行了FreeRTOS的移植,创建好了FreeRTOS工程模板,这一篇文章,我们讲解FreeRTOS的创建任务。创建任务的时候,分静态内存创建任务和动态内存创建任务。上一篇文章中,我们使用的是动态任务创建,来验证FreeRTOS的移植是否正确。
1.SRAM静态内存创建任务
使用静态内存创建任务的时候,任务使用的栈和任务控制块都使用静态内存,即预先定义好全局变量,这些预先定义好的全局变量都存放在内部的SRAM中。在静态内存任务创建,需要将FreeRTOS.h中的configSUPPORT_STATIC_ALLOCATION设置为1.
#ifndef configSUPPORT_STATIC_ALLOCATION
/* Defaults to 0 for backward compatibility. */
#define configSUPPORT_STATIC_ALLOCATION 1
#endif
1.1 静态创建任务函数xTaskCreateStatic
#include “FreeRTOS.h”
#include “task.h”
TaskHandle_t xTaskCreateStatic( TaskFunction_t pvTaskCode,
const char * const pcName,
uint32_t ulStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer );
参数名/返回值 | 描述 |
---|---|
pvTaskCode | 任务只是一个永远不会退出的C函数,因此通常是一个循环函数。pvTaskCode只是一个的指向实现任务的函数的指针(实际上就是函数名) |
pcName | 任务的描述性名称,在FreeRTOS中没有使用,仅仅只是用在调试目的。识别一个任务通过一个任务名称比尝试用任务句柄来识别任务简单 |
usStackDepth | 每个任务都有自己唯一的堆栈,由内核分配给创建任务时的任务。usStackDepth值告诉内核堆栈的大小。 |
pvParameters | 任务函数接收一个指向void *类型指针的参数,分配给pvParameters的值是传递给任务的值 |
uxPriority | 定义了任务将要执行的优先级,优先级可以被设置为0到configMAX_PRIORITIES – 1,其中0 是最低优先级 |
puxStackBuffer | 必须指向一个数组的StackType_t变量,至少有ulStackDepth索引,参见上面的ulStackDepth参数 |
pxTaskBuffer | 必须指向StaticTask_t类型的变量。该变量将用于保存已创建任务的数据结构(TCB) |
Returned value | NULL表示任务没有被创建因为puxStackBuffer或者pxTaskBuffer是NULL,如果没有返回空值,表明任务被创建和返回的值是创建任务的句柄值 |
例如:
/* 创建LED_Task任务 */
LED_Task_Handle = xTaskCreateStatic( (TaskFunction_t )LED_Task, //任务函数
(const char* )"LED_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )4, //任务优先级
(StackType_t* )LED_Task_Stack, //任务堆栈
(StaticTask_t* )&LED_Task_TCB); //任务控制块
1.2定义任务函数
void LED_Task()
{
while(1)
{
LED1( ON );
vTaskDelay(1000);
LED1( OFF );
vTaskDelay(1000);
}
}
1.3定义全局变量
目前我们只创建了一个任务,当任务进行延时的时候,没有其他就绪的用户任务,系统会进入空闲任务。空闲任务是FreeRTOS系统自己启动的一个任务,优先级最低。整个系统没有就绪任务的时候,系统必须保证有一个任务运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲任务切换到用户任务。
在FreeRTOS系统中,每一个任务都是独立的,运行时环境都单独保存在相应的栈空间。我们使用的时静态内存创建任务,定义任务的栈大小的时候,需要定义一个独立的全局变量。任务的栈占用的是MCU的RAM,当任务越多的时候,需要使用的栈空间越大,需要的RAM也就越多。
/* LED 任务堆栈 */
static StackType_t LED_Task_Stack[128];
/*LED任务控制块 */
static StaticTask_t LED_Task_TCB;
/* LED 任务句柄 */
static TaskHandle_t LED_Task_Handle;
1.4 启动任务调度
当任务创建好后,是处于任务就绪(Ready) ,在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个任务就是在启动任务调度器中实现, 每个操作系统, 任务调度器只启动一次,之后就不会再次执行了, FreeRTOS 中启动任务调度器的函数vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管理都由FreeRTOS管理,此时才是真正进入实时操作系统中的第一步。
/* 启动任务,开启调度 */
vTaskStartScheduler();
2.SRAM动态内存创建任务
动态内存创建任务和静态内存创建任务相似,少了一些参数。区别在于使用动态内存创建任务的时候,其实使用的是堆内存,也属于SRAM。FreeRTOS在SRAM里面定义了一个大数组,也就是堆内存,供FreeRTOS的动态内存分配函数使用。在第一次使用的时候,与其他操作系统不同,FreeRTOS会自动对定义的堆内存进行初始化,内存初始化的方式在内存管理方案中实现(heap_1.c heap_2.c heap_3.c heap_4.c heap_5.c),一般使用是heap_4.c 。在动态内存任务创建,需要将FreeRTOS.h中的configSUPPORT_STATIC_ALLOCATION设置为1.
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024))
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
2.1 动态创建任务函数xTaskCreate
#include “FreeRTOS.h”
#include “task.h”
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
参数名/返回值 | 描述 |
---|---|
pvTaskCode | 任务只是一个永远不会退出的C函数,因此通常是一个循环函数。pvTaskCode只是一个的指向实现任务的函数的指针(实际上就是函数名) |
pcName | 任务的描述性名称,在FreeRTOS中没有使用,仅仅只是用在调试目的。识别一个任务通过一个任务名称比尝试用任务句柄来识别任务简单 |
usStackDepth | 每个任务都有自己唯一的堆栈,由内核分配给创建任务时的任务。usStackDepth值告诉内核堆栈的大小。 |
pvParameters | 任务函数接收一个指向void *类型指针的参数,分配给pvParameters的值是传递给任务的值 |
uxPriority | 定义了任务将要执行的优先级,优先级可以被设置为0到configMAX_PRIORITIES – 1,其中0 是最低优先级 |
pxCreatedTask | pxCreatedTask 是用来在任务创建的时候分发一个句柄。这个句柄可以在在API调用的时候引用这个任务,举个例子,改变任务的优先级和删除任务。如果应用程序没有使用到这个任务句柄,pxCreatedTask 可以设置为NULL |
Return Values | 返回值有两个。一个是pdPASS,表示任务已经创建成功;一个是errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,表示任务不能被创建因为FreeRTOS无法给任务数据结构体和栈分配足够的内存 |
例如:
/* 创建LED_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
2.2 定义任务函数
void LED_Task()
{
while(1)
{
LED1( ON );
vTaskDelay(1000);
LED1( OFF );
vTaskDelay(1000);
}
}
2.3定义全局变量
和静态创建任务一样,这里需要定义一个任务句柄
/* LED 任务句柄 */
static TaskHandle_t LED_Task_Handle;
2.4 启动任务调度
和静态创建任务一样,在创建任务后,需要开启任务调度。
/* 启动任务,开启调度 */
vTaskStartScheduler();
3.举例创建多任务
在编译的时候,会产生两个错误没有定义vApplicationGetIdleTaskMemory函数和没有定义vApplicationGetTimerTaskMemory函数,这是由于使用静态创建任务的时候,需要人为分配空闲任务和定时器任务的TCB大小、任务栈大小。这里直接在main.c文件中实现。
再次编译,无错误,两个任务来回切换,LED不停闪烁。
例程代码:
#include "stm32f4xx.h"
#include "./led/bsp_led.h"
#include "FreeRTOS.h"
#include "task.h"
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
/* LED1 任务堆栈 */
static StackType_t LED1_Task_Stack[128];
/*LED1任务控制块 */
static StaticTask_t LED1_Task_TCB;
/* LED1 任务句柄 */
static TaskHandle_t LED1_Task_Handle;
/* LED2 任务句柄 */
TaskHandle_t LED2_TaskHandle = NULL;
void LED1_Task()
{
while(1)
{
LED1( ON );
vTaskDelay(1000);
LED1( OFF );
vTaskDelay(1000);
}
}
void LED2_Task()
{
while(1)
{
LED2( ON );
vTaskDelay(500);
LED2( OFF );
vTaskDelay(500);
}
}
int main(void)
{
BaseType_t xReturn = pdPASS;
/* LED 端口初始化 */
LED_GPIO_Config();
//create led1 task
/* 创建LED_Task任务 */
LED1_Task_Handle = xTaskCreateStatic( (TaskFunction_t )LED1_Task,
(const char* )"LED1_Task",
(uint32_t )128,
(void* )NULL,
(UBaseType_t )4,
(StackType_t* )LED1_Task_Stack,
(StaticTask_t* )&LED1_Task_TCB);
//create led2 task
xReturn = xTaskCreate( LED2_Task,
"LED2_Task",
128,
NULL,
4,
&LED2_TaskHandle);
vTaskStartScheduler();
}
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}