已经移植好 FreeRTOS 工程模板,本实验就在该模板基础上增加功能代码,
可将工程模板复制一份重新命名为“任务创建--静态方法” 本章所要实现的功能是:使用静态内存方法创建 2 个任务,分别控制 2 个 LED 闪烁
1定义任务函数
任务实际上就是一个无限循环且不带返回值的 C 函数。我们创建 2 个这样 的任务,让开发板上面的 LED 灯以 1S 的频率闪烁,具体实现代码如下。
//任务 1 函数
void task1_task(void *pvParameters)
{
while(1) //(1)
{
LED1=0;
vTaskDelay(200); //(2)
LED1=1;
vTaskDelay(800);
}
}
//任务 2 函数
void task2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}
代码(1):任务必须是一个死循环,否则任务将通过 LR 返回,如果 LR 指 向了非法的内存就会产生 HardFault_Handler,而 FreeRTOS 指向一个死循环, 那么任务返回之后就在死循环中执行,这样子的任务是不安全的,所以为避免这 种情况,任务一般都是死循环并且无返回值的。
代码(2):任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数, 并不能使用我们裸机编程中的那种延时。这两种的延时的区别是 FreeRTOS 里面 的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调 度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那 种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的, 那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无 法实现多任务。但 FreeRTOS 中的延时最小值只能是 ms,对于 us 级别的延时怎 么办呢,这个在前面移植操作系统时我们介绍过,使用 SysTick.c 文件中的延时 函数亦可,包括 ms 和 us 级延时,这两个延时函数与裸机实验中是不一样的,所 以不要搞混
2 空闲任务与定时器任务堆栈函数
当我们使用了静态创建任务的时候,configSUPPORT_STATIC_ALLOCATION 这 个宏定义必须为 1(在 FreeRTOSConfig.h 文件中),并且我们需要实现两个函 数:vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任 务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分 配,具体代码如下
/* 空闲任务任务堆栈 */
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;
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS 提供了接口函数 vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t
**ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
98
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;
*ppxIdleTaskStackBuffer=Idle_Task_Stack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer : 任务控制块内存
//ppxTimerTaskStackBuffer: 任务堆栈内存
//pulTimerTaskStackSize : 任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t
**ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小
*/
}
3 定义任务栈和任务控制块
目前我们只创建了 2 个任务,当任务进入延时的时候,因为没有另外就绪的 用户任务,那么系统就会进入空闲任务,空闲任务是 FreeRTOS 系统自己启动的 一个任务,优先级最低。当整个系统都没有就绪任务的时候,系统必须保证有一 个任务在运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲 任务切换回用户任务。 在 FreeRTOS 系统中,每一个任务都是独立的,他们的运行环境都单独的保 存在他们的栈空间当中。那么在定义好任务函数之后,我们还要为任务定义一个 栈,目前我们使用的是静态内存,所以任务栈是一个独立的全局变量,具体见代 码。任务的栈占用的是 MCU 内部的 RAM,当任务越多的时候,需要使用的栈空 间就越大,即需要使用的 RAM 空间就越多。一个 MCU 能够支持多少任务,就得 看芯片的 RAM 空间有多少。
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中是以 8 字节大小 对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定义的一个宏,其值为 8,就是配置为按 8 字节对齐,当然 用户可以选择按 1、2、4、8、16、32 等字节对齐,目前默认为 8。 定义好任务函数和任务栈之后,我们还需要为任务定义一个任务控制块,通 常我们称这个任务控制块为任务的身份证。在 C 代码上,任务控制块就是一个 结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息
//任务控制块
StaticTask_t Task1TaskTCB;
//任务控制块
StaticTask_t Task2TaskTCB
4 静态创建任务函数
一个任务的三要素是任务主体函数,任务栈,任务控制块,那么怎么样把这 三个要素联合在一起?FreeRTOS 里面有一个叫静态任务创建函数 xTaskCreateStatic(),它就可以实现。它将任务主体函数,任务栈(静态的) 和任务控制块(静态的)这三者联系在一起,让任务可以随时被系统启动,具体 如下。
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, //任务函数 (1)
(const char* )"start_task",//任务名称 (2)
(uint32_t )START_STK_SIZE,//任务堆栈大小(3)
(void* )NULL,//传递给任务函数的参数 (4)
(UBaseType_t )START_TASK_PRIO, //任务优先级 (5)
(StackType_t* )StartTaskStack,//任务堆栈 (6)
(StaticTask_t* )&StartTaskTCB);//任务控制块 (7
代码(1):任务入口函数,即任务函数的名称,需要我们自己定义并且实现。 代码(2):任务名字,字符串形式,最大长度由 FreeRTOSConfig.h 中定义 的 configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字 最好要与任务函数入口名字一致,方便进行调试。 代码(3):任务堆栈大小,单位为字,在 32 位的处理器下(STM32),一个 字等于 4 个字节,那么任务大小就为 128 * 4 字节。 代码(4):任务入口函数形参,不用的时候配置为 0 或者 NULL 即可。 代码(5):任务的优先级。优先级范围根据 FreeRTOSConfig.h 中的宏 configMAX_PRIORITIES 决定,如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个 优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大 可用优先级数目。在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级。 代码(6):任务栈起始地址,只有在使用静态内存的时候才需要提供,在使 用动态内存的时候会根据提供的任务栈大小自动创建。 代码(7):任务控制块指针,在使用静态内存的时候,需要给任务初始化函 数 xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存 的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务 控制块是 xTaskCreate()函数里面动态分配的一块内存。
整体代码
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
/* 空闲任务任务堆栈 */
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;
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;
*ppxIdleTaskStackBuffer=Idle_Task_Stack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer : 任务控制块内存
//ppxTimerTaskStackBuffer: 任务堆栈内存
//pulTimerTaskStackSize : 任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
USART1_Init(115200);
//创建开始任务
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(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint32_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(StackType_t* )Task1TaskStack,
(StaticTask_t* )&Task1TaskTCB);
//创建TASK2任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//任务1函数
void task1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//任务2函数
void task2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}