基于HAL库的FREERTOS-----------三.队列

2023-11-11

一.队列简介

在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中
断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之
间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的
长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。
下面是任务间发送消息示意图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二.CUBEMX配置

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

三.API函数介绍

1、函数 xQueueCreate()
此函数本质上是一个宏,用来动态创建队列,内存由freertos中的动态内存管理函数 pvPortMalloc()分配,此宏最终调用的是函数 xQueueGenericCreate()。
在这里插入图片描述

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize)
参数:
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节
返回值:
其他值: 队列创捷成功以后返回的队列句柄!
NULL: 队列创建失败。

cubemx生成的代码中对API做了封装,
在这里插入图片描述
在这里插入图片描述
2、函数 xQueueCreateStatic()
此函数也是用于创建队列的,但是使用的静态方法创建队列,队列所需要的内存由用户自行分配,此函数本质上也是一个宏,此宏最终调用的是函数 xQueueGenericCreateStatic()。

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
const UBaseType_t uxItemSize,
 const uint8_t ucQueueType )
参数:
uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
uxItemSize: 队列中每个项目(消息)的长度,单位为字节。
ucQueueType: 队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号
量的函数最终也是使用此函数的,因此在创建的时候需要指定此队列的用途,
也就是队列类型,一共有六种类型:
queueQUEUE_TYPE_BASE 普通的消息队列
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量
函 数 xQueueCreate() 创建队列的时候此参数默认选择的就是
queueQUEUE_TYPE_BASE。
返回值:
其他值: 队列创捷成功以后的队列句柄!
NULL: 队列创建失败。

3.向队列发送消息
在这里插入图片描述
(1)、函数 xQueueSend()、xQueueSendToBack()和 xQueueSendToFront()
这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏,其中函数 xQueueSend()和 xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。然而!这三个函数最后都是调用的同一个函数:xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾。

BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
 const void* pvItemToQueue,
 TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。如果为 0 的话当队列满的时候就立即返回;当为 portMAX_DELAY 的话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。

(2)、函数 xQueueOverwrite()
此函数也是用于向队列发送数据的,当队列满了以后会覆写掉旧的数据,不管这个旧数据有没有被其他任务或中断取走。这个函数常用于向那些长度为 1 的队列发送消息,此函数也是一个宏,最终调用的也是函数 xQueueGenericSend()

BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void * pvItemToQueue);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
返回值:
pdPASS: 向队列发送消息成功,此函数也只会返回 pdPASS!因为此函数执行过程中不在乎队列满不满,满了的话我就覆写掉旧的数据,总之肯定能成功。

(3)、函数 xQueueSendFromISR()、
xQueueSendToBackFromISR()、
xQueueSendToFrontFromISR()

这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质也是宏,其中函数 xQueueSendFromISR ()和 xQueueSendToBackFromISR ()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数 xQueueSendToFrontFromISR ()是前向入队,即将新消息插入到队列的前面。这三个函数同样调用同一个函数 xQueueGenericSendFromISR ()。

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
 const void * pvItemToQueue,
 BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
 const void * pvItemToQueue,
 BaseType_t * pxHigherPriorityTaskWoken);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdTRUE: 向队列中发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败

这三个函数在中断中执行,没有阻塞值。

(4)、函数 xQueueOverwriteFromISR()
此函数是 xQueueOverwrite()的中断级版本,用在中断服务函数中,在队列满的时候自动覆写掉旧的数据,此函数也是一个宏,实际调用的也是函数 xQueueGenericSendFromISR()。

BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
 const void * pvItemToQueue,
 BaseType_t * pxHigherPriorityTaskWoken);
此函数的参数和返回值同上面三个函数相同。

(5)函数 xQueueGenericSend()和函数xQueueGenericSendFromISR()
这两个都是上面函数的原型

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition)
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间。
xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。
上面讲解的入队 API 函数就是通过此参数来决定采用哪种入队方式的。
返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,const void* pvItemToQueue,BaseType_t* pxHigherPriorityTaskWoken,BaseType_t xCopyPosition);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。
返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。

4.队列上锁和解锁

prvLockQueue( pxQueue );
prvUnlockQueue( Queue_t * const pxQueue );

5.从队列读取消息
在这里插入图片描述
1、函数 xQueueReceive()
此函数用于在任务中从队列中读取一条(请求)消息,读取成功以后就会将队列中的这条数
据删除,此函数的本质是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度。

BaseType_t xQueueReceive(QueueHandle_t xQueue,
 void * pvBuffer,
 TickType_t xTicksToWait);
 
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

2、函数 xQueuePeek()
此函数用于从队列读取一条(请求)消息,只能用在任务中!此函数在读取成功以后不会将
消息删除,此函数是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度。

BaseType_t xQueuePeek(QueueHandle_t xQueue,void * pvBuffer,TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdTRUE: 从队列中读取数据成功。1
pdFALSE: 从队列中读取数据失败。0

3、函数 xQueueReceiveFromISR()
此函数是 xQueueReceive()的中断版本,用于在中断服务函数中从队列中读取(请求)一条消息,读取成功以后就会将队列中的这条数据删除。此函数在读取消息的时候是采用拷贝方式的,所以需要用户提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度.

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
 void* pvBuffer,
 BaseType_t * pxTaskWoken);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
pxTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值是由函数来设置的,
用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

5、函数 xQueuePeekFromISR()
此函数是 xQueuePeek()的中断版本,此函数在读取成功以后不会将消息删除。

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
void * pvBuffer)
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

四.实验验证

1.任务与任务

void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
	uint32_t  data=5;
	BaseType_t state;
	printf("进入任务2");
  /* Infinite loop */
  for(;;)
  {
		state=xQueueSend(myQueue01Handle,&data,0);
		if(state==0)
		{
			printf("队列已经满了,消息发送失败");
		}
		else
			{
				printf("向队列发送消息成功!");
			}
    osDelay(1000);
  }
  /* USER CODE END StartTask02 */
}
void StartTask03(void const * argument)
{
  /* USER CODE BEGIN StartTask03 */
	uint32_t  data;
	printf("进入任务3");
	BaseType_t state;
  /* Infinite loop */
  for(;;)
  {
		state=xQueueReceive(myQueue01Handle,&data,0);
		printf("state %ld",state);
		if(state)
		{
			printf("接收到数据\r\n");
			printf("data %d",data);
			switch(data) 
				 {
					 case 5: //KEY_UP 控制 LED1
					 printf("进入选择");
					 break;
				 }
		}
    osDelay(1000);
  }

在这里插入图片描述
2.中断与中断

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */
	uint32_t  data_rev,buffer,data_send[4]={1,2,3,4};
	BaseType_t state_it_send,state_it_rev;
	BaseType_t xTaskWokenByReceive=pdFALSE;
	BaseType_t xHigherPriorityTaskWoken;
  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM2)
		{
			printf("定时器2中断");
			if(myQueue02Handle!= NULL)
			{
			xQueueSendFromISR(myQueue02Handle,data_send,&xHigherPriorityTaskWoken);
		  }
	}
		__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
  /* USER CODE BEGIN Callback 1 */
		 if (htim->Instance == TIM3)
		 {
			 printf("定时器3中断");
			 if(myQueue02Handle!= NULL)
			 xQueueReceiveFromISR(myQueue02Handle,&data_rev,&xTaskWokenByReceive);
			 printf("data_rev %d  ",data_rev);
			 __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);//清除中断标志位
		 }
  /* USER CODE END Callback 1 */
}

注意,此处必须进行队列初始化,即 if(myQueue02Handle!= NULL),否则中断会卡死,且中断里不应该执行太多的函数
在这里插入图片描述
3.任务与中断

void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
		uint32_t queue_buffer[20],Res[20];
	uint8_t num_queue;
	BaseType_t xTaskWokenByReceive=pdFALSE;
  /* Infinite loop */
  for(;;)
  {
		osDelay(1);
		printf("空闲任务\r\n");
		num_queue=uxQueueMessagesWaiting(myQueue02Handle);
		num_queue=xQueueReceiveFromISR(myQueue02Handle,queue_buffer,&xTaskWokenByReceive);
		printf("queue_buffer %d  %d\r\n",queue_buffer[0],num_queue);
  }
  /* USER CODE END StartDefaultTask */
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */
	uint32_t  data_rev,buffer,data_send[4]={1,2,3,4};
	BaseType_t state_it_send,state_it_rev;
	BaseType_t xTaskWokenByReceive=pdFALSE;
	BaseType_t xHigherPriorityTaskWoken;
  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM2)
		{
			printf("定时器2中断");
			if(myQueue02Handle!= NULL)
			{
			xQueueSendFromISR(myQueue02Handle,data_send,&xHigherPriorityTaskWoken);
		  }
	}
		__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);

在这里插入图片描述
注意:空闲任务的osdelay(1)是不可以去掉的,若去掉,其他任务不执行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于HAL库的FREERTOS-----------三.队列 的相关文章

  • ERP系统设计:库存管理怎么做?

    库存是企业打算出售给客户以获取利润的商品或材料 库存管理是供应链的一个关键要素 涉及到从制造商到仓库 从这些设施到销售点的库存跟踪 库存管理的目标是在适当的时间将适当的产品放置在适当的地点 库存管理的业务问题 在进行库存管理工作时 会出现很

随机推荐

  • 基于Python的大数据分析基础(一)

    关于Pandas Pandas中的数据结构 1 Series 一维数组系列 也称序列 2 DataFrame 二维的表格型数据结构 3 Panel 三维数组 数据类型 1 Logical 逻辑型 2 Numeric 数值型 3 Charac
  • NodeJS简介-node.js是什么?

    Nodejs是个在服务器动可以解析和执行JavaScript代码的运行环境 也可以说是一个运行时平台 仍然使用JavaScript作为开发语言 但是提偶了一些功能性的API 例如文件操作和网络通信API等 Nodejs是由 Ryan Dah
  • ssh key authentication失败,查看日志是selinux禁止了sshd读取authorized_keys文件

    ssh key authentication失败 查看日志是selinux禁止了sshd读取authorized keys文件 May 5 04 24 36 localhost dbus 704 system Activating serv
  • C#的变迁史 - C# 4.0 之线程安全集合篇

    作为多线程和并行计算不得不考虑的问题就是临界资源的访问问题 解决临界资源的访问通常是加锁或者是使用信号量 这个大家应该很熟悉了 而集合作为一种重要的临界资源 通用性更广 为了让大家更安全的使用它们 微软为我们带来了强大的并行集合 Syste
  • 基于堆叠⾃编码器的时间序列预测 深层神经网络

    自适应迭代扩展卡尔曼滤波算法 AIEK 是一种滤波算法 其目的是通过迭代过程来逐渐适应不同的状态和环境 从而优化滤波效果 该算法的基本思路是在每一步迭代过程中 根据所观测的数据和状态方程 对滤波器的参数进行自适应调整 以便更好地拟合实际数据
  • python- NameError name ‘name‘ is not defined

    python NameError name name is not defined 练习写python函数的时候 遇到了NameError name name is not defined 这样的错误 百度了一下 发现name是一个系统变量
  • Selenium+Python系列 - 开发环境搭建

    优质资源分享 学习路线指引 点击解锁 知识定位 人群定位 Python实战微信订餐小程序 进阶级 本课程是python flask 微信小程序的完美结合 从项目搭建到腾讯云部署上线 打造一个全栈订餐系统 Python量化交易实战 入门级 手
  • 设计模式:代理模式

    由于某些原因需要给某对象提供一个代理以控制对该对象的访问 这时 访问对象不适合或者不能直接引用目标对象 代理对象作为访问对象和目标对象之间的中介 这就是代理模式 代理模式的主要优点有 1 代理模式在客户端与目标对象之间起到一个中介作用和保护
  • QT学习:制作树形列表菜单

    一 前言 使用QT制作树形的列表菜单 需要使用QTreeWidget和QTreeWidget两个类 最终效果如图所示 二 代码方式实现 使用代码方式实现树形菜单 首先要包含两个类库 include
  • 安全测试目录内容合集

    基础知识 安全测试基础知识 安全测试 django防御安全策略 HTTP工作原理 靶场DVWA 安全测试网站 DWVA下载安装启动 DVWA Command Injection DVWA 5 File upload 文件上传漏洞 DVWA
  • 【记录】安装Django 创建虚拟环境和新项目

    本文仅记录实际操作情况 本文参考书籍 1 2 1 确保电脑安装Python 2 创建虚拟环境 创建一个新目录test blog 再在终端中切换到这个目录 并执行如下命令创建一个虚拟环境 python m venv ll env 书上原文 这
  • 如何恢复内存卡数据?

    生活中 无论我们使用哪种存储设备 内部空间都是有限的 随着使用时间的增加 里面存储的数据会越来越多 这时如果不能及时处理 将很容易出现数据丢失 如果小伙伴们不小心碰到这样的事 要如何恢复内存卡数据呢 遇到了请不要着急 下面小编就分享可以有效
  • python读取表格画散点图_Note: Python_Matplotlib绘制平滑曲线和散点图

    给出横坐标纵坐标点 即可连线绘图 import matplotlib 调用绘图工具包 给出x y点坐标 x y 1 2 3 4 5 6 5 9 3 4 7 5 绘图 matplotlib pyplot plot x y 这样使用工具包如果程
  • python发邮件附件内容中文乱码_python3发邮件,附件名称为中文时出错

    问题描述 我写了一个发邮件的类 一切进行的很顺利 但是附件名改成中文的时候就出问题了 问题出现的环境背景及自己尝试过哪些方法 用的是python3 和email包 是了MIMEText MIMEApplication MIMEBase都不行
  • java scope_spring中的scope详解

    1 singleton 单一实例 此取值时表明容器中创建时只存在一个实例 所有引用此bean都是单一实例 如同每个国家都有一个总统 国家的所有人共用此总统 而这个国家就是一个spring容器 总统就是spring创建的类的bean 国家中的
  • Flask框架种使用ORM模型对MySQL数据库的管理

    通过flask连接MySQL数据库后 使用ORM模型对数据库管理 ORM模型的优点 使用 ORM 做数据库的开发可以有效的减少重复SQL语句的概率 写出来的模型也更加直观 清晰 支持多个关系数据库引擎 包括流行的 MySQL Postgre
  • mysql使用st_distance_sphere函数报错Incorrect arguments to st_distance_sphere

    最近发现执行mysql st distance sphere报错了 报错的信息是Incorrect arguments to st distance sphere 最开始以为是跟mysql的版本有关系 所以看了下自己本地的mysql版本 执
  • 详解shell输出重定向:>/dev/null 2>&1

    1 输入输出重定向介绍 重定向简单来说就是把本来已经默认的 确定的输入输出给重新定位到你想要的地方 重定向这个概念在C语言中就有 在C语言编程中 标准输出是屏幕 使用printf 函数默认是输出到屏幕显示 但是有时候我们需要将信息输出到文件
  • BOM特效:返回顶部按钮

    BOM特效开发 返回顶部按钮制作 BOM特效开发 返回顶部按钮制作 改变document documentElement scrollTop属性 结合定时器逐步改变此值以动画形式返回顶部 在这里插入代码片
  • 基于HAL库的FREERTOS-----------三.队列

    一 队列简介 在实际的应用中 常常会遇到一个任务或者中断服务需要和另外一个任务进行 沟通交流 这个 沟通交流 的过程其实就是消息传递的过程 在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式 但是如果在使用操作系统的应用中用