利用STM32CubeMX和keil模拟器,3天入门FreeRTOS(1.1) —— 创建多个静态任务实操和简单讲解

前言

(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:gitee仓库

实战

(1)将上一篇博客最终的代码复制一份。

在这里插入图片描述

开启静态创建任务宏定义

(1)使用FreeRTOS的静态创建任务的时候,需要在FreeRTOSConfig.h打开configSUPPORT_STATIC_ALLOCATION这个宏定义。一般是默认打开了的。如果你不需要使用静态创建任务,个人建议将这个宏关闭,这样生成的代码段会少一些。

在这里插入图片描述

任务创建

FreeRTOS静态任务创建

(1)对于STM32CubeMX而言,静态创建任务和动态创建任务只有如下部分不同,整体使用上都一样。反正ST做了RTOS层抽象,最终对外接口都是osThreadNew()函数。
(2)因为上一篇博客是使用Keil端手动传入参数,咱们已经会了,那么我现在就教一下大家如何在STM32CubeMX中传入参数。
(3)这里需要注意一点,你使用STM32CubeMX让任务传入参数,这个参数需要在keil端创建。(按Ctrl+F搜索Private variables即可找到如下部分)

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */          
static char *CubemxTask_argument = "StartCubemxTask\r\n";
/* USER CODE END Variables */

(4)之后你就需要在任务里面调用这个参数,按照如下方法使用。(按Ctrl+F搜索StartCubemxTask即可找到任务函数)

/* USER CODE END Header_StartCubemxTask */
void StartCubemxTask(void *argument)
{/* USER CODE BEGIN StartCubemxTask */char *CubemxTaskPrintf = (char *)argument;/* Infinite loop */for(;;){printf(CubemxTaskPrintf);		}/* USER CODE END StartCubemxTask */
}

在这里插入图片描述

keil端手动创建静态任务

(1)静态创建的任务,我们需要自己创建三个参数。一个是为静态任务准备的栈空间,一个是TCB控制块,一个是任务句柄。(按Ctrl+F搜索Private variables即可找到如下部分,并进行补充)


/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
static StackType_t g_pucStackKeilTaskBuff[128];  // 为静态任务准备的栈空间
static StaticTask_t g_TCBKeilTask;               // 静态任务的TCB控制块
TaskHandle_t keilTaskHandle;                     // 静态任务的句柄static char *CubemxTask_argument = "StartCubemxTask\r\n";
/* USER CODE END Variables */	

(2)上一篇博客我们已经介绍了如何使用keil端手动传入参数的方法,于是当前这一篇就不传入参数了。(按Ctrl+F搜索add threads 即可找到如下部分,并进行补充)

/* USER CODE BEGIN RTOS_THREADS *//* add threads, ... */keilTaskHandle = xTaskCreateStatic(StartKeilTask,"KeilTask", 128, NULL, osPriorityLow1, g_pucStackKeilTaskBuff,&g_TCBKeilTask);if(keilTaskHandle == NULL){printf("KeilTask creation failed\r\n");}

(3)然后再补充任务函数内容。(按Ctrl+F搜索BEGIN Application 即可找到如下部分,并进行补充)

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
int fputc(int ch, FILE *f)
{unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;
}
void StartKeilTask(void *argument)
{while(1){HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);HAL_Delay(100);}
}
/* USER CODE END Application */

测试结果

(1)和上文方法一致,不过因为我们修改了STM32CubeMX的配置信息重新生成keil工程的时候,原来的配置信息都将会被清空,因此还需要重新配置。

keil调试配置

(1)打开微库。

在这里插入图片描述

(2)配置模拟器
DARMSTM.DLL
pSTM32F103C8

在这里插入图片描述

配置虚拟示波器

(1)打开调试界面

在这里插入图片描述

(2)选择逻辑分析仪,检测PC13引脚。为什么下面输入的是PORTC.13,原因很简单,格式为PORTx.y,x表示端口,y表示具有引脚数值,注意’.'必须是英文的!

在这里插入图片描述
在这里插入图片描述

配置虚拟串口

(1)如下图

在这里插入图片描述

实测

(1)我们会发现结果和动态创建任务的结果是一致的。这也很好的证明了,静态创建任务和动态创建任务,在使用上是没有本质上的区别的。只有在创建任务和删除任务的时候略微不同。
(2)那么我们又为什么需要弄一个动态创建任务和一个静态创建任务呢?这就需要大家对有一定的认识了。具体内容请看理论部分,如果不理解,我建议无脑使用动态创建任务。

在这里插入图片描述

理论

(1)再次强调,以下内容对堆栈的知识要有一定的认知!如果新手小白看不懂,建议无脑使用动态创建任务!

xTaskCreateStatic()函数介绍

(1)静态创建任务函数和动态创建任务函数就最后部分不一样。
<1>如果是动态创建任务函数,那么最后只需要传入一个任务句柄即可。
<2>如果是静态创建任务函数,那么最后传入的任务句柄修改为栈空间的首地址TCB控制块
(2)栈空间:可能有同学不太能理解,我也不想讲一大堆术语,说白了,就是一个数组。这个涉嫌到汇编的内容,下面部分能听懂就听,听不懂略过。
<1>当函数A调用函数B的时候,一些参数信息需要保存进入栈。(也就是你这里传入的数组)
<2>函数中的局部变量会存放在栈空间里面。(也就是你这里传入的数组)
<3>RTOS进行任务调度切换的时候,需要保护现场。所谓的保护现场,就算把当前的下面这16个寄存器里面的值都存入到栈空间。(也就是你这里传入的数组)
(3)TCB控制块:这个就是句柄最终指向的区域。具体细节请看:
句柄到底是什么?TCB又是什么?C代码实例讲解

在这里插入图片描述

BaseType_t xTaskCreateStatic(TaskFunction_t pxTaskCode,                 // 指向任务函数的函数指针const char * const pcName,                 // 任务的名字,最大长度configMAX_TASK_NAME_LENconst configSTACK_DEPTH_TYPE usStackDepth, // 任务栈大小,单位为word,10表示40字节void * const pvParameters,                 // 调用任务函数时传入的参数UBaseType_t uxPriority,                    // 优先级,范围:0 ~ (configMAX_PRIORITIES-1)。数值越大,优先级越大StackType_t * const puxStackBuffer,        // 栈空间首地址指针StaticTask_t * const pxTaskBuffer          // 任务的TCB控制块
); 

如何估计栈的大小

uxTaskGetStackHighWaterMark()函数介绍

(1)
<1>对于栈的大小估计,FreeRTOS中提供了uxTaskGetStackHighWaterMark()函数来查看任务使用的栈空间历史使用剩余值的最小值,单位是world也就是4字节。很多C站博主都说是单位是1字节。我不知道他们从哪里得出的结论,我是直接看的FreeRTOS源码介绍)。
<2>当这个值越小说明任务堆栈溢出的可能性就越大。就要尝试适当的增大栈空间分配。
<3>如果这个值你发现非常的大。那么就可以适当的减小创建时候分配的栈空间。

/*** @brief   查看任务使用的栈空间大小** @param   xTask 任务句柄** @return 任务堆栈可用的最小值,单位word(4字节)*/
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );/* === 使用方法 === */
void StartCubemxTask(void *argument)
{/* USER CODE BEGIN StartCubemxTask */char *CubemxTaskPrintf = (char *)argument;UBaseType_t Cubemx_Stack;/* Infinite loop */for(;;){printf(CubemxTaskPrintf);Cubemx_Stack = uxTaskGetStackHighWaterMark(keilTaskHandle);printf("CubemxTask is %ld\r\n",Cubemx_Stack);		}/* USER CODE END StartCubemxTask */
}

在这里插入图片描述

(2)使用这个函数之前,需要注意,要在STM32CubeMX中使能INCLUDE_uxTaskGetStackHighWaterMark

在这里插入图片描述

(3)或者在FreeRTOSConfig.h文件中让INCLUDE_uxTaskGetStackHighWaterMark 这个宏定义为1。

在这里插入图片描述

测试结果

(1)开启仿真调试,我们即可知道最终的还剩下的栈空间大小。

在这里插入图片描述

静态创建的任务和动态创建

两者区别

(1)在FreeRTOS中,任务可以通过静态创建和动态创建两种方式来实现。他们只有在任务创建的初期能否释放栈有区别,最终使用是一模一样的。

  • 静态创建的任务,栈是存放在数组里面的,也就是bss段。因此静态创建任务的栈无法释放,在编译初期就定好了。
  • 动态创建的任务,栈是通过类似malloc的函数实现的,也就是堆区域。堆是可以通过类似free函数释放的。

(3)以下是它们之间的主要区别以及各自的优缺点:
<1>静态创建任务:
优点:

  • 内存管理: 不需要动态分配内存,所有的资源在编译时就已经分配好了。
  • 实时性: 由于任务的资源在编译时就已经分配,因此任务创建的实时性较好。注意,这里只有在任务创建的时候实时性不同,任务创建完成之后,使用是一模一样的!!!
  • 可靠性: 如果你的应用程序在内存方面受到限制,静态创建任务可以帮助你在编译时就分配任务所需的内存,而不是在运行时动态分配。这样可以避免在运行时发生内存分配失败碎片化问题。
  • 容易调试: 静态创建任务在编译时就分配了任务的资源,这使得在调试阶段更容易检测和解决问题,因为你可以直接查看任务的内存布局和大小。

缺点:

  • 灵活性: 静态创建任务需要在编译时确定任务的数量和占用的资源,因此不够灵活,无法动态地根据运行时的条件调整任务数量任务栈大小,在程序烧录进入MCU之后就是定死的。
  • 浪费资源: 如果分配的资源过大,可能会浪费内存。因为静态创建的任务栈无法被释放。但是如果你这个任务就是要一直运行,不需要删除,这个问题不需要考虑。
  • 复杂性: 涉及多个任务和更复杂的系统结构时,静态创建任务可能会增加系统配置的复杂性。任务的数量、优先级和资源需求都需要在编译时确定,这可能使得系统的调整变得更加繁琐。

<2>动态创建任务:
优点:

  • 灵活性: 可以根据应用程序的需求动态创建和删除任务。也就是说他的任务数量任务栈大小,是可以在MCU运行过程中进行调整。
  • 资源利用: 可以更灵活地分配内存,减少资源的浪费。因为他的任务栈是通过类似malloc函数申请的,也可也通过类似free函数释放。

缺点:

  • 内存管理: 动态分配内存需要考虑内存的释放,否则可能导致内存泄漏。
  • 实时性: 由于涉及到动态内存分配,任务的创建可能不如静态创建及时。
  • 可靠性: 动态创建的任务可能会因为堆不足够,导致任务创建失败的问题。

两种创建方式如何抉择

(1)选择静态创建还是动态创建取决于应用的具体需求。如果你的任务具备以下特征,就推荐使用静态创建任务:

  • 任务的数量和属性在编译时就确定,不需要动态创建或删除任务。
  • 任务的栈空间需求可以预先估计,不需要动态调整。
  • 堆空间有限,或者想要节省堆空间。
  • 对任务创建的速度和可靠性有较高的要求。

(2)当你的任务具备以下特征,就推荐使用动态创建任务:

  • 任务的数量和属性在运行时才确定,需要动态创建或删除任务。
  • 任务的栈空间需求难以预先估计,需要动态调整。
  • 堆空间充足,或者不在乎堆空间的占用。
  • 对任务创建的速度和可靠性没有较高的要求。

(3)关于动态分配任务还是静态分配任务到底如何抉择,这个要具体问题具体分析。但是对于新手小白来说,哪个容易使用,就用哪个,因此我个人推荐新手小白无脑使用动态分配任务的方式。

FreeRTOS的堆管理机制

(1)上面我说动态创建任务的任务栈是通过类似malloc函数实现的,为什么用类似两字呢?因为标准C库存在如下问题,所以自己写了一个堆分配算法。

  • malloc()free() 函数在嵌入式系统上并不总是可用。
  • 占用了宝贵的代码空间
  • 不是线程安全的
  • 执行函数所需时间将因调用而异

(2)一个嵌入式/实时系统的 RAM 和定时要求可能与另一个非常不同,所以单一的 RAM 分配算法 将永远只适用于一个应用程序子集。因此FreeRTOS 提供了几种堆管理方案, 其复杂性和功能各不相同。
<1>heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。(也就是静态创建任务函数xTaskCreateStatic()
<2>heap_2 现在被视为旧版,因为较新的 heap_4 实现是首选。
<3>heap_1 占用code段最小,heap_5 占用空间最多。

文件优点缺点
heap_1.c分配简单,时间确定只分配、不回收
heap_2.c动态分配、最佳匹配碎片、时间不定
heap_3.c调用标准库函数速度慢、时间不定
heap_4.c相邻空闲内存可合并可解决碎片问题、时间不定
heap_5.c在 heap_4 基础上支持分隔的内存块可解决碎片问题、时间不定

参考

(1)freeRTOS使用uxTaskGetStackHighWaterMark函数查看任务堆栈空间的使用情况
(2)CubeMX FreeRTOS uxTaskGetStackHighWaterMark()的使用
(3)FreeRTOS官方文档:静态内存分配 vs 动态内存分配
(4)FreeRTOS官方文档:内存管理
(5)FreeRTOS官方文档:任务堆栈应该多大?
(6)韦东山:FreeRTOS入门与工程实践课程——[4-2]内存管理部分

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/421364.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ranger概述及安装配置

一、前序 希望拥有一个框架,可以管理大多数框架的授权,包括: hdfs的目录读写权限各种大数据框架中的标的权限,列级(字段)权限,甚至行级权限,函数权限(UDF)等相关资源的权限是否能帮忙做书库脱敏Ranger框架应运而生。 二、Ranger 2.1、什么是ranger Apache Ranger…

re:从0开始的HTML学习之路 11. 音视频标签

1. 音视频标签 向页面中引入音频/视频。二者使用方式一样 2. 二者常用属性 controls&#xff1a;是否允许用户控制播放&#xff0c;不加则不允许 autoplay&#xff1a;控制是否自动播放 loop&#xff1a;控制是否循环播放 注意&#xff1a; HTML5中若属性名与属性值相同&…

mini-Spring-BeanDefinition和BeanDefinitionRegistry(二)

Bean工厂 首先我们需要定义 BeanFactory 这样一个 Bean 工厂&#xff0c;提供 Bean 的获取方法 getBean(String name)&#xff0c;之后这个 Bean 工厂接口由抽象类 AbstractBeanFactory 实现&#xff0c;可以统一模板。继承抽象类 AbstractBeanFactory 后的 AbstractAutowireCa…

Kotlin协程的JVM实现源码分析(下)

协程 根据 是否保存切换 调用栈 &#xff0c;分为&#xff1a; 有栈协程&#xff08;stackful coroutine&#xff09;无栈协程&#xff08;stackless coroutine&#xff09; 在代码上的区别是&#xff1a;是否可在普通函数里调用&#xff0c;并暂停其执行。 Kotlin协程&…

66K star!想画出高颜值的流程图,试试这个手绘风开源白板

工作中总是少不了要画画图&#xff0c;不管是开发中绘制流程图&#xff0c;还是设计系统时画出架构图&#xff0c;一款趁手的工具总是少不了。今天我们就来聊聊画图的白板工具。 今天我们推荐的推荐的项目帮你画出手绘风的高颜值图表&#xff0c;目前在GitHub已超过66K Star&a…

redis优化系列(六)

本期分享redis内存过期策略&#xff1a;过期key的处理 Redis之所以性能强&#xff0c;最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大&#xff0c;会影响持久化或主从同步性能。 可以通过修改配置文件来设置Redis的最大内存&#xff1a; maxmemory 1gb …

DophineScheduler通俗版

1.DophineScheduler的架构 ZooKeeper&#xff1a; AlertServer&#xff1a; UI&#xff1a; ApiServer&#xff1a; 一个租户下可以有多个用户&#xff1b;一个用户可以有多个项目一个项目可以有多个工作流定义&#xff0c;每个工作流定义只属于一个项目&#xff1b;一个租户可…

时空预测网络ST-Resnet 代码复现

ST-ResNet&#xff08;Spatio-Temporal Residual Network&#xff09;是一种用于处理时空数据的深度学习模型&#xff0c;特别适用于视频、时间序列等具有时空结构的数据。下面是一个简单的使用PyTorch搭建ST-ResNet的示例代码。请注意&#xff0c;这只是一个基本的示例&#x…

大模型微调实战笔记

大模型三要素 1.算法&#xff1a;模型结构&#xff0c;训练方法 2.数据&#xff1a;数据和模型效果之间的关系&#xff0c;token分词方法 3.算力&#xff1a;英伟达GPU&#xff0c;模型量化 基于大模型对话的系统架构 基于Lora的模型训练最好用&#xff0c;成本低好上手 提…

【项目日记(三)】内存池的整体框架设计

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你做项目   &#x1f51d;&#x1f51d; 开发环境: Visual Studio 2022 项目日…

(酒驾检测、人脸检测、疲劳检测、模拟口罩数据集制作、防酒驾)-常用的论文所用的python代码总结

汇总&#xff1a; 学习感悟&#xff1a;如果小白建议一行一行的Debug,看够一定量的代码&#xff0c;自己就自然顺手写代码了。汇总上传的东西用在自己电脑上一般都需要适当修改的。 20&#xff0c;水平翻转图片 19&#xff0c;颜色空间装换HSV对比演示 18&#xff0c;光照补…

永赢基金引入微签电子签章系统实现审批签章电子化

永赢基金管理有限公司采用微签电子签章系统&#xff0c;作为进一步推动办公自动化转型的解决方案。微签在审批签署方面的显著优势&#xff0c;帮助永赢基金有效解决了原有OA系统无法满足电子文件全程电子化、签章不落地的问题&#xff0c;实现办公效率翻倍。 公司概况 永赢基金…