当前位置: 首页 > news >正文

FreeRTOS---任务创建与删除

FreeRTOS—任务创建与删除

1 基本概念

  在多任务系统里面,任务有以下三大要素:

  1. 做何事:函数
  2. 栈和TCB
  3. 优先级

  对于每个任务我们得确定它做何事,这个可以通过函数去实现。每个任务需要设置不同的栈,同时还需要设置TCB结构体,设置TCB结构体的目的是当任务发生切换后我们还能继续找到它。最后我们还需要为任务设置优先级,这个不是必须的,但是设置后可以帮助我们处理很多场景。
  对于栈和TCB结构体,我们可以动态分配,也可以静态分配,具体可以参考下面函数说明。

  对于整个单片机程序,我们称之为application,应用程序。使用FreeRTOS时,我们可以在application中创建多个任务(task),有些文档把任务也称为线程(thread)。

在这里插入图片描述
以日常生活为例,比如这个母亲要同时做两件事:

  • 喂饭:这是一个任务
  • 回信息:这是另一个任务 这可以引入很多概念:
  • 任务状态(State):
    • 当前正在喂饭,它是running状态;另一个"回信息"的任务就是"not running"状态
    • "not running"状态还可以细分:
      • ready:就绪,随时可以运行
      • blocked:阻塞,卡住了,母亲在等待同事回信息
      • suspended:挂起,同事废话太多,不管他了
  • 优先级(Priority)
    • 我工作生活兼顾:喂饭、回信息优先级一样,轮流做
    • 我忙里偷闲:还有空闲任务,休息一下
    • 厨房着火了,什么都别说了,先灭火:优先级更高
  • 栈(Stack)
    • 喂小孩时,我要记得上一口喂了米饭,这口要喂青菜了
    • 回信息时,我要记得刚才聊的是啥
    • 做不同的任务,这些细节不一样
    • 对于人来说,当然是记在脑子里
    • 对于程序,是记在栈里
    • 每个任务有自己的栈
  • 事件驱动
    • 孩子吃饭太慢:先休息一会,等他咽下去了、等他提醒我了,再喂下一口

2 任务创建与删除

2.1 什么是任务

在FreeRTOS中,任务就是一个函数,原型如下:

void ATaskFunction( void *pvParameters );

需要注意的是:

  • 这个函数不能返回
  • 同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一个函数
  • 函数内部,尽量使用局部变量:
    • 每个任务都有自己的栈
    • 每个任务运行这个函数时
      • 任务A的局部变量放在任务A的栈里、任务B的局部变量放在任务B的栈里
      • 不同任务的局部变量,有自己的副本
    • 函数使用全局变量、静态变量的话
      • 只有一个副本:多个任务使用的是同一个副本
      • 需要防止冲突

示例:

void ATaskFunction( void *pvParameters )
{/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */int32_t lVariableExample = 0;/* 任务函数通常实现为一个无限循环 */for( ;; ){/* 任务的代码 */}/* 如果程序从循环中退出,一定要使用vTaskDelete删除自己* NULL表示删除的是自己*/vTaskDelete( NULL );/* 程序不会执行到这里, 如果执行到这里就出错了 */
}

2.2 创建任务

创建任务时使用的函数如下:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数const char * const pcName, // 任务的名字const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节void * const pvParameters, // 调用任务函数时传入的参数UBaseType_t uxPriority,    // 优先级TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

参数说明:

参数描述
pvTaskCode函数指针,任务对应的 C 函数。任务应该永远不退出,或者在退出时调用 “vTaskDelete(NULL)”。
pcName任务的名称,仅用于调试目的,FreeRTOS 内部不使用。pcName 的长度为 configMAX_TASK_NAME_LEN。
usStackDepth每个任务都有自己的栈,usStackDepth 指定了栈的大小,单位为 word。例如,如果传入 100,表示栈的大小为 100 word,即 400 字节。最大值为 uint16_t 的最大值。确定栈的大小并不容易,通常是根据估计来设定。精确的办法是查看反汇编代码。
pvParameters调用 pvTaskCode 函数指针时使用的参数:pvTaskCode(pvParameters)。
uxPriority任务的优先级范围为 0~(configMAX_PRIORITIES – 1)。数值越小,优先级越低。如果传入的值过大,xTaskCreate 会将其调整为 (configMAX_PRIORITIES – 1)。
pxCreatedTask用于保存 xTaskCreate 的输出结果,即任务的句柄(task handle)。如果以后需要对该任务进行操作,如修改优先级,则需要使用此句柄。如果不需要使用该句柄,可以传入 NULL。
返回值成功时返回 pdPASS,失败时返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因是内存不足)。请注意,文档中提到的失败返回值是 pdFAIL 是不正确的。pdFAIL 的值为 0,而 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 的值为 -1。

使用静态分配内存的函数如下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,   // 函数指针, 任务函数const char * const pcName,   // 任务的名字const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节void * const pvParameters,   // 调用任务函数时传入的参数UBaseType_t uxPriority,      // 优先级StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个bufferStaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);

相比于使用动态分配内存创建任务的函数,最后2个参数不一样:

参数描述
pvTaskCode函数指针,可以简单地认为任务就是一个C函数。 它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。 长度为:configMAX_TASK_NAME_LEN
usStackDepth每个任务都有自己的栈,这里指定栈大小。 单位是word,比如传入100,表示栈大小为100 word,也就是400字节。 最大值为uint16_t的最大值。 怎么确定栈的大小,并不容易,很多时候是估计。 精确的办法是看反汇编码。
pvParameters调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority优先级范围:0~(configMAX_PRIORITIES – 1) 数值越小优先级越低, 如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
puxStackBuffer静态分配的栈内存,比如可以传入一个数组, 它的大小是usStackDepth*4。
pxTaskBuffer静态分配的StaticTask_t结构体的指针
返回值成功:返回任务句柄; 失败:NULL

2.3 示例1:创建任务

  使用动态、静态分配内存的方式,分别创建多个任务:监测遥控器并在LCD上显示、LED闪烁、全彩LED渐变颜色、使用无源蜂鸣器播放音乐。部分代码截屏如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 估算栈大小

  对于我们上述的示例,我们使用动态分配和静态分配分别创建任务。对于这两个函数,我们都得设置栈的大小,那么我们的栈如何去选取呢?
  那么栈里面保存什么内容呢,它会保存LR寄存器或者其它寄存器,还会保存局部变量,如果任务发生切换还会保存现场,这个现场就是16个寄存器,它会占据64个字节。栈中的局部变量取决于我们的代码,返回地址和其它寄存器取决于函数的调用深度,所以我们如何去评估栈的大小呢,就是得选取我们最复杂得调用关系。
在这里插入图片描述
  对于返回地址和其它寄存器的大小,我们需要参考函数调用深度,以下面例子来讲,函数有5层调用,如果我们每层函数都足够复杂,那么每层都需要保存最大的寄存器个数即9个。那么5层调用的话最大需要保存594个字节数。
在这里插入图片描述
  继续以我们上述示例为例,我们用音乐播放的任务来讲解。
在这里插入图片描述
  以上根据c代码推算出栈的大小,我们分配的栈大小为128*4,足以支撑我们的任务了。

2.5 使用任务参数

  多个任务可以使用同一个函数,那么怎么体现它们的差别?

  • 栈不同
  • 创建任务时可以传入不同的参数

我们创建3个任务,使用同一套函数,但是在LCD上打印不同的信息。

struct TaskPrintInfo{uint8_t x;uint8_t y;char name[16];
};static struct TaskPrintInfo g_Task1Info = {0,0,"Task1"};
static struct TaskPrintInfo g_Task2Info = {0,3,"Task2"};
static struct TaskPrintInfo g_Task3Info = {0,6,"Task3"};
static int g_LCDCanUse = 1;void LcdPrintTask(void *params)
{struct TaskPrintInfo *pInfo = params;uint32_t cnt = 0;int len;LCD_Init();LCD_Clear();while(1){/* 打印信息 */if(g_LCDCanUse){g_LCDCanUse = 0;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(500);}
}

上述代码中的 pInfo 来自参数 params ,params 来自哪里?创建任务时传入的。
代码如下:

  • 使用xTaskCreate创建任务时,第4个参数就是pvParameters
  • 不同的任务,pvParameters不一样
xTaskCreate(LcdPrintTask, "task1", 128, &g_Task1Info, osPriorityAboveNormal, NULL);
xTaskCreate(LcdPrintTask, "task2", 128, &g_Task2Info, osPriorityAboveNormal, NULL);
xTaskCreate(LcdPrintTask, "task3", 128, &g_Task3Info, osPriorityAboveNormal, NULL);

  那么在任务函数中为什么需要延时呢?我们使用全局变量达到了互斥的效果,如果第一个任务正在运行LCD打印,那么第二个任务判断互斥变量后无法运行,我们设置延时的目的就是希望任务调度是在延时处产生,那么互斥变量在这时就是被清除的状态。

2.6 任务的删除

删除任务时使用的函数如下:

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明:

参数描述
pvTaskCode任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。 也可传入NULL,这表示删除自己。

怎么删除任务?举个不好的例子:

  • 自杀:vTaskDelete(NULL)
  • 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
  • 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄

示例:

void StartDefaultTask(void *argument)
{/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */uint8_t dev, data;int len;TaskHandle_t xSoundTaskHandle;BaseType_t ret;LCD_Init();LCD_Clear();IRReceiver_Init();LCD_PrintString(0,0,"waiting control");while (1){/* 读取红外遥控器 */if(0 == IRReceiver_Read(&dev,&data)){if(data == 0xa8) /* Play */{/* 创建播放音乐的任务 */extern void PlayMusic(void *params);if(xSoundTaskHandle == NULL){LCD_ClearLine(0,0);LCD_PrintString(0,0,"Create Task");ret = xTaskCreate(PlayMusic,"SoundTask",128,NULL,osPriorityNormal,&xSoundTaskHandle);}}else if(data == 0xa2) /* Power */{/* 删除播放音乐的任务 */if(xSoundTaskHandle != NULL){LCD_ClearLine(0,0);LCD_PrintString(0,0,"Delete Task");vTaskDelete(xSoundTaskHandle);PassiveBuzzer_Control(0);xSoundTaskHandle = NULL;}}}}/* USER CODE END StartDefaultTask */
}

相关文章:

  • HunyuanPortrait - 一张图生成任意表情和动作的肖像动画 精准操控眼睛和嘴唇动作 支持50系显卡 本地一键整合包下载
  • 一根网线连接两台电脑组建局域网
  • Qt 中的 d-pointer 与 p-pointer小结
  • 【ROS2】Qt Debug日志重定向到ROS2日志管理系统中
  • 1.什么是node.js、npm、vue
  • 基于通义千问的儿童陪伴学习和成长的智能应用架构。
  • 【笔记】suna部署之获取 OpenRouter API key
  • 【Java orm框架对比】十四新增gaarason/database-all框架对比
  • vite导入优化插件vite-plugin-importer
  • odoo与人工智能:可能性与新功能探索
  • JavaScript 循环语句总结
  • 网关Gateway
  • 【25-cv-05935】Keith律所代理(绿色巴士图)版权维权案
  • PostgreSQL如何更新和删除表数据
  • 解决报错error: ‘void_t’ is not a member of ‘std’
  • 软件开发新技术课设-个人博客系统(一)
  • Python中的enumerate函数:优雅地遍历序列索引与元素
  • 大模型强化学习中的 PPO、DPO、KTO、ORPO 全解析
  • 【Linux】(1)—进程概念-③Linux进程概念与PCB
  • 2025推客系统小程序开发:独立部署源码交付,高性价比裂变增长引擎
  • 教做幼儿菜谱菜的网站/大数据培训班需要多少钱
  • 常州网站专业制作/线上拓客渠道有哪些
  • 公司网站开发制作/杭州seo优化
  • 六感程序网站建设/现在百度怎么优化排名
  • 中贤建设集团网站/seo营销方法
  • 达州做淘宝网站/关键词排名怎么查