3 任务管理
3.1 任务的基本形式
任务的基本要素:1.做什么(函数)2.栈 3.优先级
一个任务的形式为:
void ATaskFunc(void *pvParameters)
{
for(;;) //通常是一个死循环
{
/*任务代码*/
}
vTaskDelete(NULL);// 如果程序从死循环退出,需要删除自己
}
freertos.c文件由STM32CubeMX软件生成,会自动创建一个默认任务:
void MX_FREERTOS_Init(void) {
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
}
函数osThreadNew在文件cmsis_os2.c中被定义。
不同操作系统创建任务的函数不一样,为了统一处理,则创建一个统一接口层,即cmsis_os2。
osThreadNew会自动根据不同的操作系统去调用响应的任务创建函数。
3.2 创建任务
3.2.1 动态分配内存
动态分配栈和任务控制块(Task Control Block, TCB)结构体
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 函数指针
const char * const pcName, // 任务名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word
void * const pvParameters, // 传入参数
UBaseType_t uxPriority, // 优先级,越小越优先
TaskHandle_t * const pxCreateTask); // 句柄
3.2.2 静态分配内存
静态分配,需要事先指定栈和TCB结构体
TaskHandel_t xTaskCreateStatic(
TaskFunction_t pxTaskCode, // 函数指针
const char * const pcName, // 任务名字
const uint32_t ulStackDepth, // 栈大小,单位为word
void * const pvParameters, // 传入参数
UBaseType_t uxPriority, // 优先级,越小越优先
StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
StaticTask_t * const pxTaskBuffer);
// 静态分配的任务结构体(TCB)的指针, 用它来操作这个任务
3.2.3 示例1 - 静态创建+动态创建
实现三个任务:1.静态创建任务,调用LED_Test。2.动态创建任务,调用ColorLED_Test。3.在默认任务中调用IRReceiver_Test。
IRReceiver_Test、LED_Test、ColorLED_Test是另外写好的,我们暂时不关心。
在freertos.c模板上进行修改。修改的地方均有中文注释
static StackType_t g_pucStackOfLightTask[128]; // 给光任务静态分配的栈
static StaticTask_t g_TCBofLightTask; // 给光任务静态分配的TCB结构体
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void);
void MX_FREERTOS_Init(void) {
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
// 为光任务创建静态任务
xTaskCreateStatic(Led_Test, "LightTask", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);
// 动态创建任务
xTaskCreate(ColorLED_Test, "ColorTask", 128, NULL, osPriorityNormal, NULL);
}
void StartDefaultTask(void *argument)
{
LCD_Init();
LCD_Clear();
for(;;)
{
IRReceiver_Test();
}
}
想要创建任务,在void MX_FREERTOS_Init(void){}初始化函数中创建任务即可。
在静态创建任务时,使用了
static StackType_t g_pucStackOfLightTask[128]
来分配栈,那么如何粗略估算要分给他多大的空间呢?
函数要用到的栈空间考虑以下因素:
- 函数调用深度:有几级调用n,每次调用,要用到R4~R11共8个寄存器+一个LR,每个占4字节。因此栈的大小计算为n94
- 局部变量大小:有的函数中可能会用到很大的局部变量,此时需要将其考虑在内。
- 现场需要16*4=64字节
3.2.4 示例2 - 使用同一个函数,创建不同的任务(类似多态)
思路很简单,首先创建一个任务函数
void LcdTask(void * params)
{
struct TaskPrintInfo * pInfo = params;
uint32_t cnt = 0;
while(1)
{ if (g_LcdCanUse)
{
g_LcdCanUse = 0;//很不可靠的保护方法
int len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
len += LCD_PrintString(len, pInfo->y, ":");
LCD_PrintSignedVal(len, pInfo->y, cnt++);
g_LcdCanUse = 1;
}
mdelay(1);
}
}
让其在指定位置显示指定字符,传入的参数创建如下:
struct TaskPrintInfo
{
uint8_t x;
uint8_t y; // 位置
char name[16]; // 字符
};
然后在MX_FREERTOS_Init中使用函数创建三个任务即可。但由于LCD_PrintString耗时非常长,RTOS很有可能在期间跳出执行另一个任务,导致IIC传输中断。为了避免,添加一个全局变量来保护。
但这种保护很不可靠,更可靠的保护方法后面会介绍。
以下是完整代码:
//添加打印的信息格式
struct TaskPrintInfo
{
uint8_t x;
uint8_t y; // 位置
char name[16]; // 字符
};
//创建三个任务所需要的参数
static struct TaskPrintInfo g_Task1Info = {0,0,"T1"};
static struct TaskPrintInfo g_Task2Info = {0,3,"T2"};
static struct TaskPrintInfo g_Task3Info = {0,6,"T3"};
static int g_LcdCanUse = 1; // lcd能否被使用,这是为了保护IIC不被打断。更高级的保护机制后面会讲
//添加一个任务函数
void LcdTask(void * params)
{
struct TaskPrintInfo * pInfo = params;
uint32_t cnt = 0;
while(1)
{ if (g_LcdCanUse)
{
g_LcdCanUse = 0;//很不可靠的保护方法
int len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
len += LCD_PrintString(len, pInfo->y, ":");
LCD_PrintSignedVal(len, pInfo->y, cnt++);
g_LcdCanUse = 1;
}
mdelay(1);
}
}
void MX_FREERTOS_Init(void)
{
LCD_Init();
LCD_Clear();
//使用同一个函数创建不同的任务
xTaskCreate(LcdTask, "LCDPrint", 128, &g_Task1Info, osPriorityNormal, NULL);
xTaskCreate(LcdTask, "LCDPrint", 128, &g_Task2Info, osPriorityNormal, NULL);
xTaskCreate(LcdTask, "LCDPrint", 128, &g_Task3Info, osPriorityNormal, NULL);
}
3.3 删除任务
删除任务需要知道任务句柄。删除操作的函数原型为:
void vTaskDelete( TaskHandle_t xTaskToDelete); 需要传入一个任务句柄
有两种删除方式:
自杀:vTaskDelete(NULL);
他杀:vTaskDelete(pvTaskCode)
3.3.1 示例3 用遥控控制灯亮灭
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
}
void StartDefaultTask(void *argument)
{
ColorLED_Set(0)
LCD_Init();
LCD_Clear();
IRReceiver_Init();
TaskHandle_t xColorLedHandle=NULL;
LCD_PrintString(0,0,"waiting control");
for(;;)
{
uint8_t dev,data;
//读取红外遥控器
if (!IRReceiver_Read(&dev, &data)){
if(data == 0xa8)
{
if(xColorLedHandle == NULL){
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Police!Freeze!");
xTaskCreate(ColorLED_Test, "cled", 128, NULL, osPriorityNormal, &xColorLedHandle);
}
}
else if(data == 0xa2)
{
if(xColorLedHandle != NULL){
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "You can go now");
vTaskDelete(xColorLedHandle);
ColorLED_Set(0);
xColorLedHandle = NULL;
}
}
}
}