FreeRTOS---任务创建与删除

article/2025/8/22 18:21:34

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 */
}

http://www.hkcw.cn/article/isbJxtTACX.shtml

相关文章

梅花鹿遭野狗追逐坠落死亡 流浪狗围猎问题频发

梅花鹿遭野狗追逐坠落死亡 流浪狗围猎问题频发!近日,有网友称近一周来,辽宁大连有数只梅花鹿遭流浪狗撕咬死亡,还有梅花鹿在被狗群追逐时从高处坠落死亡。5月27日凌晨,一只母鹿遭狗群追逐从土墙上坠落摔伤,全身多处撕咬伤,后经抢救无效死亡。据大连野境自然保护中心介绍…

这场峰会捅了美国痛点 东盟联手应对关税挑战

这场峰会捅了美国痛点 东盟联手应对关税挑战!东盟、中国与海合会再开启“新篇章”。5月27日下午,首届东盟-中国-海合会峰会在马来西亚吉隆坡召开。从东亚合作扩展到跨区域合作,三方在峰会上首次正式会面,标志着区域合作进入全新阶段,具有重要的里程碑意义。东盟和中国作…

【HW系列】—HTTP协议详解

文章目录 HTTP协议介绍HTTP协议拆分统一资源定位符(URL)HTTP请求的四个部分1. 请求行(Request Line)2. 请求头(Headers)3. 空行(CRLF)4. 请求体(Body) HTTP服…

缺兵少将!许利民:全运会的比赛不可控因素很大 要把每场比赛打好 克服伤病迎战全运

28日,北京队在首钢篮球中心举行备战第十五届全国运动会男篮成年组公开训练。球队主教练许利民表示,要克服伤病困难,打好每场比赛。此次征战全运会的北京队主要由中国男子篮球职业联赛(CBA)北京首钢队和北控队队员组成。许利民提到,队员们刚结束联赛比赛,部分队员有伤病情…

华为OD机试真题——报文回路(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…

CPP中CAS std::chrono 信号量与Any类的手动实现

前言 CAS(Compare and Swap) 是一种用于多线程同步的原子指令。它通过比较和交换操作来确保数据的一致性和线程安全性。CAS操作涉及三个操作数:内存位置V、预期值E和新值U。当且仅当内存位置V的值与预期值E相等时,CAS才会将内存位…

20250529-C#知识:索引器

C#知识:索引器 索引器给对象添加了索引访问的功能,实际访问的是对象的成员,感觉不太常用。 1、主要内容及代码示例 索引器中类似属性,也包含get和set方法索引器能够使像访问数组一样访问对象一般当类中有数组类型的成员变量时&am…

芭莎明星怼脸照,卡粉眼袋眼角纹真实却美丽,看完再也没有容貌焦虑 女星状态更胜一筹

今年的时尚芭莎盛典异常热闹。原本以为这又是一次明星们展示美貌的机会,但这次芭莎玩了个新花样,用“镜头签”将明星的真实皮肤状态暴露在公众面前。于是,“没去芭莎的很幸运了”这个词条冲上了热搜。这次的生图简直成了“照妖镜”。男星的表现可以说不尽如人意。张云龙依旧…

订单已排至2029年!我国造船产业订单量领跑全球

订单已排至2029年!我国造船产业新接订单量领跑全球在当前复杂的全球贸易形势下,我国造船产业依旧表现出强劲的市场韧性与竞争力,走出了产业加速度,今年1-4月,我国造船产业新接订单量占世界市场份额继续保持全球第一。眼下,很多造船企业的订单饱满,生产任务也排至了几年之…

Maven-生命周期

目录 1.项目对象模型 2.依赖管理模型 3.仓库:用于存储资源,管理各种jar包 4.本地仓库路径 1.项目对象模型 2.依赖管理模型 3.仓库:用于存储资源,管理各种jar包 4.本地仓库路径

Nacos

注册发现各种第三方组件的比较介绍: CAP C:一致性 A:可用性 P:分区容错性 启动nacos Linux环境:找到startup.sh,编辑文件将启动模式从集群cluster模式修改为单机模式standalone,如下图 然…

苹果公司计划按年份来重命名重大的软件,将升级iOS 18软件至iOS 26

苹果公司计划从今年开始,所有苹果操作系统将统一采用年份标识,而非此前混乱的版本号体系。苹果将在6月9日的全球开发者大会上正式宣布这一变革。周三截至发稿,苹果股价震荡微涨0.46%,重回3万亿美元市值。 苹果公司正在筹划其操作…

How to Initiate Back-to-Back Write Transactions from Master

Q: How to Initiate Back-to-Back Write Transactions from Master A: following are the modification required at master end to achieve back-to-back transaction driving the VIP: constraint all the master relevant delays to ‘0’ during transaction randomizatio…

纵览网丨新视角下的黑洞探索:传统奇点理论的挑战与未来观测的可能性

纵览网(www.zonglan.com)在宇宙的浩瀚无垠中,黑洞一直以其神秘莫测的特性吸引着人类的目光。从爱因斯坦的广义相对论到现代天文学的观测成果,黑洞的研究不断取得突破,但同时也伴随着无数未解之谜。其中,传统…

冯彬实现女子铁饼亚锦赛三连冠 中国田径再添辉煌

韩国当地时间5月29日晚,2025年亚洲田径锦标赛结束了第三个比赛日的争夺。女子铁饼决赛中,中国选手冯彬凭借最后一投的61米90顺利夺得金牌,并实现了个人的亚锦赛三连冠。这也是中国田径队连续12届摘得女子铁饼项目的亚锦赛金牌。31岁的冯彬此前曾获得2022年俄勒冈世锦赛金牌和…

4.1.4 基于数据帧做SQL查询

在本节实战中,我们探讨了如何基于Spark DataFrame执行SQL查询。首先,我们学习了如何通过createOrReplaceTempView方法将DataFrame注册为一个临时视图,以便在SQL查询中使用。接着,我们使用spark.sql方法执行了各种SQL查询&#xff…

DUBBO介绍

1.1 DUBBO简介 Dubbo是Alibaba开源的分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合&…

这个西部城市登顶全国消费第一城 重庆连续超越上海

这个西部城市登顶全国消费第一城 重庆连续超越上海!重庆的消费数据再次超过了上海。根据重庆市统计局的数据,1-4月,重庆社会消费品零售总额达到5385.43亿元,同比增长4.4%。同期,上海的社会消费品零售总额为5355.46亿元,同比下降0.3%。这意味着重庆成为当前中国消费总额最…

solidworks报错-只有合并特征才能被阵列。如果恰当,请选择实体的阵列

当我想要阵列这个特征的时候出现了如下报错,报错提示我使用实体的阵列,但这明显不合适,因为我在创建特征的时候已经合并了特征所以只有一个实体,有一个不算聪明的解决方法 重新退回特征创建阶段,取消合并结果 这样设计…

【加密社】私钥碰撞工具 最新版

最近有很多朋友问我能不能做一款针对指定地址进行爆破的工具 【指定地址进行碰撞】 当然可以做。 这里要说明的是,私钥碰撞工具的概率是非常非常非常非常小的,几乎无限趋近于0的几率,除非你是天选之子。 (但是这里我还是做了一…