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

news2024/10/3 2:14:47

前言

(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_LEN
	const 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.coloradmin.cn/o/1402063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LSTM的负荷预测,基于BILSTM的负荷预测,基于GRU的负荷预测,基于BIGRU的负荷预测,基于BP神经网络的负荷预测

目录 背影 摘要 代码和数据下载&#xff1a;基于LSTM的负荷预测&#xff0c;基于BILSTM的负荷预测&#xff0c;基于GRU的负荷预测&#xff0c;基于BIGRU的负荷预测&#xff0c;基于BP神经网络的负荷预测资源-CSDN文库 https://download.csdn.net/download/abc991835105/8876806…

HCIA——22DNS:DNS层次域名空间、域名服务器、域名解析的原理

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

YOLOv8改进 | 主干篇 | 低照度图像增强网络SCINet改进黑暗目标检测(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是低照度图像增强网络SCINet,SCINet(自校正照明网络)是一种专为低光照图像增强设计的框架。它通过级联照明学习过程和权重共享机制来处理图像,优化了照明部分以提升图像质量。我将该网络集成在YOLOv8的主干上针对于图像的输入进行增…

以太坊账户地址与比特B地址生成方法对比

作者 张群&#xff08;赛联区块链教育首席讲师&#xff0c;工信部赛迪特聘资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09;关注张群&#xff0c;为您提供一站式区块链技术和方案咨询。 以太坊和比特B地址在生成方…

关于js的BigInt的使用与注意事项

说明 BigInt是一种内置对象&#xff0c;提供了一种方法来表示大于2^53 - 1 的整数&#xff0c;2^53 - 1 为Number可以表示的最大数字&#xff0c;BigInt可以突破限制&#xff0c;可以用任意精度表示整数&#xff0c;超出Number的安全整数限制&#xff0c;也可以安全地存储和操…

【AI】小白入门笔记

前言 2024年&#xff0c;愿新年胜旧年&#xff01;作为AI世界的小白&#xff0c;今天先来从一些概念讲起&#xff0c;希望路过的朋友们多多指教&#xff01; 正文 AI (人工智能) 提起AI, 大家可能会想起各种机器人&#xff0c;移动手机的“Siri”,"小爱同学", 是语…

GIS项目实战08:JetBrains IntelliJ IDEA 2022 激活

为什么选择 IntelliJ IDEA 使用编码辅助功能更快地编写高质量代码&#xff0c;这些功能可在您键入时搜索可能的错误并提供改进建议&#xff0c;同时无缝地向您介绍编码、新语言功能等方面的社区最佳实践。 IntelliJ IDEA 了解您的代码&#xff0c;并利用这些知识通过在每种上…

[java基础揉碎]位运算符

java中有7个位运算&#xff08;&、|、^、~、>>、<<和>>>&#xff09; 第一组 分别是按位与&、按位或|、按位异或^&#xff0c;按位取反~&#xff0c;它们的运算规则是&#xff1a; 按位与& : 两位全为1&#xff0c;结果为1&#xff0c;否则…

Debian11下编译ADAravis和Motor模块的一条龙过程

Debian11编译EPICS ADAravis记录 一年前整理的上面文&#xff0c;这几天重新走了一遍&#xff0c;有些地方会碰到问题&#xff0c;需要补充些环节&#xff0c;motor模块以前和areaDetector一条龙编译时&#xff0c;总是有问题&#xff0c;当时就没尝试了&#xff0c;这几天尝试…

生成当天递增唯一的流水号的几种方式

说明&#xff1a;当开发中&#xff0c;如交易、文件传输过程中的文件名&#xff0c;可能需要我们使用一串唯一的数字来锁定这一条“交互记录”&#xff0c;即流水号。 本文介绍几种生成6位递增唯一&#xff0c;且每日重置的流水号的方式。 方式一&#xff1a;使用Redis 我们…

OpenCompass 大模型评测

OpenCompass 大模型评测 关于测评的三个问题为什么需要测评&#xff1f;我们需要评测什么&#xff1f;怎么测试大预言模型&#xff1f; 主流大模型评测框架OpenCompass能力框架OpenCompass评测流水线设计 随着人工智能技术的快速发展&#xff0c; 大规模预训练自然语言模型成为…

3.RHCSA脚本配置及通过node2改密码

运行脚本发现node2不成功 脚本破解 选第二个 Ctrl x 换行 破解成功后做node2的改密码题 回到redhat, 发现检测程序检测密码题成功,得了8分.

《WebKit 技术内幕》之八(1):硬件加速机制

《WebKit 技术内幕》之八&#xff08;1&#xff09;&#xff1a;硬件加速机制 1 硬件加速基础 1.1 概念 这里说的硬件加速技术是指使用GPU的硬件能力来帮助渲染网页&#xff0c;因为GPU的作用主要是用来绘制3D图形并且性能特别好&#xff0c;这是它的专长所在&#xff0c;它…

Spring Boot 集成 API 文档 - Swagger、Knife4J、Smart-Doc

文章目录 1.OpenAPI 规范2.Swagger: 接口管理的利器3.Swagger 与 SpringFox&#xff1a;理念与实现4.Swagger 与 Knife4J&#xff1a;增强与创新5.案例&#xff1a;Spring Boot 整合 Swagger35.1 引入 Swagger3 依赖包5.2 优化路径匹配策略兼容 SpringFox5.3 配置 Swagger5.4 S…

快速转换,让图片瞬间变成Excel表格!

摘要&#xff1a;本文将带你探索将图片转换为Excel表格的可行性和方法。通过对一些流行的数据处理软件进行测试&#xff0c;我们发现Photoshop和Excel可以轻松地将图片转换为Excel表格。此外&#xff0c;我们还比较了不同软件之间的差异&#xff0c;并提供了一些建议&#xff0…

docker搭建hbase 全部流程(包含本地API访问)

一、使用docker下载并安装hbase 1、搜索&#xff1a;docker search hbase 2、下载&#xff1a;docker pull harisekhon/hbase&#xff08;一定要下载这个&#xff0c;下面都是围绕此展开的&#xff09; 3、启动容器&#xff1a; docker run -d -p 2181:2181 -p 16000:16000…

做一个简单的倒计时

<div>距离过年还有:<span></span></div><script>let div document.querySelector("div");let span document.querySelector("span");// 获取未来时间戳let future new Date("2024-2-10 00:00:00");// 获取当下…

Midjourney V6有多厉害,看完这27张图你就明白了!

一些结论 当前V6版本仍处于Alpha测试阶段&#xff1a;这意味着产品的某些方面可能会发生变化。 更准确地遵循提示指令&#xff1a;V6在遵循用户的提示指令方面更为准确&#xff0c;特别是对于较长的提示指令。 模型更加连贯&#xff1a;新版本的模型在生成图像时展现出更高的…

SpringBoot+Email发送邮件

引言 邮件通知是现代应用中常见的一种通信方式&#xff0c;特别是在需要及时反馈、告警或重要事件通知的场景下。Spring Boot提供了简单而强大的邮件发送功能&#xff0c;使得实现邮件通知变得轻而易举。本文将研究如何在Spring Boot中使用JavaMailSender实现邮件发送&#xf…

Hypervisor 和Docker 还有Qemu有什么区别与联系?

Hypervisor Hypervisor是一种运行在基础物理服务器和操作系统之间的中间软件层&#xff0c;可以让多个操作系统和应用共享硬件资源&#xff0c;也叫做虚拟机监视器&#xff08;VMM&#xff09;。 Hypervisor有两种类型&#xff1a;Type I和Type II。 Type I 直接运行在硬件上&a…