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

一.队列简介

在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。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)是不可以去掉的,若去掉,其他任务不执行。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

地球先生_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值