一、什么是队列

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制。

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。

在创建队列时要指定队列长度以及队列项目的大小。

二、队列特点

1、数据入队出队方式:队列通常采用”先进先出“(FIFO)先进入消息队列的消息先传给任务,

也可以配置为后进后出(LIFO)的方式。

2、数据传递方式:FreeRTOS消息队列传递的是实际数据即将数据拷贝到队列中进行传递,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址。FreeRTOS中可以通过传递指针地址方式来传递指针。

3、多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送或读取消息。

4、出队/入队阻塞:当任务向一个队列发送消息时可以指定一个阻塞时间。

  • 若阻塞时间为0 :直接返回不会等待。
  • 若为0~port_MAX_DELAY:等待设定的阻塞时间,超时后直接返回。
  • 若为port_MAX_DELAY:一直等到可以入队。

当一个任务试图从一个空队列中读取时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中的数据变得可用,或者阻塞时间过期。

当一个任务试图写入到一个满队列时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中出现可用空间,或者阻塞时间过期。

如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。

如果优先级相同,等待时间最长的任务最先解除阻塞。

三、队列的函数

1、xQueueCreate

 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize );

创建一个新队列并返回 可引用此队列的句柄。

 configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1,或保留未定义状态(此时,它默认 为 1) ,才能使用此 RTOS API 函数。

每个队列需要 RAM 用于保存队列状态和 包含在队列(队列存储区域)中的项目。 如果使用 xQueueCreate() 创建队列,则所需的 RAM 将自动 从 FreeRTOS 堆中分配。 如果使用 xQueueCreateStatic() 创建队列, 则 RAM 由应用程序编写者提供,这会产生更多的参数, 但这样能够在编译时静态分配 RAM 。

参数:

uxQueueLength  队列可同时容纳的最大项目数 。
uxItemSize  存储队列中的每个数据项所需的大小(以字节为单位)。

数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 必须大小相同。

返回:

如果队列创建成功,则返回所创建队列的句柄 。 如果创建队列所需的内存无法 分配 ,则返回 NULL。

2、xQueueSend

BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );

这是一个调用 xQueueGenericSend() 的宏。 

等同于 xQueueSendToBack()。

在队列中发布项目。不得从中断服务程序调用此函数。请参阅 xQueueSendFromISR() 以获取 可用于 ISR 的替代方案。

参数:

xQueue 队列的句柄,数据项将发布到此队列。
pvItemToQueue 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。
xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果队列已满,并且 xTicksToWait 设置为0 ,调用将立即返回。时间在 tick 周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。

如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。

返回:如果成功发布项目,则返回 pdTRUE,否则返回 errQUEUE_FULL。

3、xQueueSendToBack

BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );

这是一个调用 xQueueGenericSend() 的宏。

等同于 xQueueSend()。

从队列尾部入队一个数据项。 数据项通过复制 而非引用入队。 不得从中断服务程序调用此函数。 请参阅 xQueueSendToBackFromISR (),获取可在 ISR 中使用的 替代方案。

参数:

xQueue 队列的句柄,数据项将发布到此队列。
pvItemToQueue 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。
xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。

如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。

返回:

如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL。

4、xQueueSendToFront
 

BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );

此宏用于调用 xQueueGenericSend()。

从队列头部入队一个数据项。 数据项通过复制 而非引用入队。 不得从中断服务程序 调用此函数。 请参阅 xQueueSendToFrontFromISR() 了解 可在 ISR 中使用的替代方法。

参数:

xQueue 队列的句柄,数据项将发布到此队列。
pvItemToQueue 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。
xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。

如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。

返回:

如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL。

5、xQueueReceive

BaseType_t xQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait );

这是一个调用 xQueueGenericReceive() 函数的宏。

从队列中接收项目。该项目通过复制接收,因此必须提供足够大小的缓冲区。创建队列时定义了复制到缓冲区中的字节数。

成功接收后会将数据删除。

中断服务程序中不得使用此函数。请参阅 xQueueReceiveFromISR 了解可以选择的替代方案。

参数:

xQueue 要从中接收项目的队列的句柄。
pvBuffer 指向缓冲区的指针,接收到的项目将被复制到这个缓冲区。
xTicksToWait  如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。 如果队列为空,将 xTicksToWait 设置为 0 将导致函数立即返回。时间在滴答周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。

如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。

返回:

如果从队列成功接收到项目,返回 pdTRUE,否则返回 pdFALSE。

6、xQueuePeek
 

BaseType_t xQueuePeek( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait );

这是一个调用 xQueueGenericReceive() 函数的宏。

从队列中接收项目,而无须从队列中删除该项目。 项目由复制接收,因此必须提供适当大小的缓冲区 。 队列创建时,复制到缓冲区中的字节数已定义 。

成功接收的数据不会删除其仍在队列中。

中断服务例程中不得使用此宏。

参数:

xQueue 要从中接收项目的队列的句柄。
pvBuffer 指针,指向将复制收到的项目的缓冲区。 它必须至少足够大,才能容纳创建队列时定义的队列项的大小。
xTicksToWait 如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。 时间已在滴答周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 来将其转换为实时。

如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。

返回:

如果从队列中成功接收(窥视)项目,则返回 pdTRUE,否则返回 pdFALSE。

四、实验程序

 1、句柄声明和数组声明

QueueHandle_t Key_Hander  = NULL;
QueueHandle_t BigData_Hander  = NULL;
char BigData[50] = {"123zxcv123vbnm"};

2、 创建队列和开始任务

void freertos_demo(void)
{
	 Key_Hander = xQueueCreate(2,sizeof(uint8_t));
	 if(Key_Hander != NULL)
	 {
		 printf("小数据队列创建成功\r\n");
	 }
	 BigData_Hander = xQueueCreate(1,sizeof(char *));
	 if(BigData_Hander != NULL)
	 {
		 printf("大数据队列创建成功\r\n");
	 }
	 
		xTaskCreate( (TaskFunction_t        ) start_task,
								 (char *                ) "start_task",
								 (configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
								 (void *                ) NULL,
								 (UBaseType_t           ) START_TASK_PRIO,
								 (TaskHandle_t *        ) &start_task_handler );
    vTaskStartScheduler();
}

 3、开始任务

void start_task( void * pvParameters )
{
	taskENTER_CRITICAL();/*进入临界区*/
		xTaskCreate( (TaskFunction_t        ) task1,
								 (char *                ) "task1",
								 (configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
								 (void *                ) NULL,
								 (UBaseType_t           ) TASK1_PRIO,
								 (TaskHandle_t *        ) &task1_handler );
								 
		xTaskCreate( (TaskFunction_t        ) task2,
								 (char *                ) "task2",
								 (configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
								 (void *                ) NULL,
								 (UBaseType_t           ) TASK2_PRIO,
								 (TaskHandle_t *        ) &task2_handler );						 
		
		xTaskCreate( (TaskFunction_t        ) task3,
								 (char *                ) "task3",
								 (configSTACK_DEPTH_TYPE) TASK3_STACK_SIZE,
								 (void *                ) NULL,
								 (UBaseType_t           ) TASK3_PRIO,
								 (TaskHandle_t *        ) &task3_handler );			
								 
		vTaskDelete(NULL);
		taskEXIT_CRITICAL(); 		/*退出临界区*/				 
}

 4、任务1、2、3

/* 入队 */
void task1( void * pvParameters )
{
	uint8_t key = 0;
	BaseType_t backValue = 0;
	char *buf;
	buf = &BigData[0];
	while(1)
	{
		key = key_scan(0);
		if(key == KEY0_PRES)
		{
			backValue = xQueueSendToBack(Key_Hander,&key,portMAX_DELAY); 
			if(backValue == pdTRUE)
			{
				printf("小数据入队成功\r\n");
				
			}
			else printf("小数据入队失败\r\n");
		}
		else if(key == KEY1_PRES)
		{
			backValue = xQueueSendToBack(BigData_Hander,&buf,portMAX_DELAY); 
			if(backValue == pdTRUE)
			{
				printf("大数据入队成功\r\n");
				
			}
			else printf("大数据入队失败\r\n");
			
		}
	}
}


/* 出队 */
void task2( void * pvParameters )
{
	uint8_t key = 0;
	BaseType_t backValue = 0;
	while(1)
	{
		backValue = xQueueReceive(Key_Hander,&key,portMAX_DELAY);
		if(backValue == pdTRUE)
			{
				printf("小数据出队成功\r\n");
				
			}
			else printf("小数据出队失败\r\n");
	}
	
	
}

void task3( void * pvParameters )
{
	char *bigData;
	BaseType_t backValue = 0;
	while(1)
	{
		backValue = xQueueReceive(BigData_Hander,&bigData,portMAX_DELAY);
		if(backValue == pdTRUE)
			{
				
				printf("大数据出队成功\r\n%s\r\n",bigData);
						
			}
			else printf("大数据出队失败\r\n");
	}
	
	
}

五、实验现象

1、按下KEY0按键,小数据队列入队出队

数据入队后由于任务2优先级大于任务1,所以任务2会抢占任务1

2、按下KEY1,大数据队列入队出队

六、发送大数据指针分析

memcpy函数原型 

void *memcpy(void *str1, const void *str2, size_t n)

参数: 

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

发送函数xQueueGenericSend会调用prvCopyDataToQueue将数据复制到队列

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
                                      const void * pvItemToQueue,
                                      const BaseType_t xPosition )
{
    BaseType_t xReturn = pdFALSE;
    UBaseType_t uxMessagesWaiting;
 /********省略******************/
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
    {
         /********省略******************/
    }
    else if( xPosition == queueSEND_TO_BACK )
    {
        ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
    }
    else
    {
        ( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
        /********省略******************/
    }
 /********省略******************/
}
/*-----------------------------------------------------------*/

接收函数xQueueReceive 调用prvCopyDataFromQueue将数据从队列中复制

static void prvCopyDataFromQueue( Queue_t * const pxQueue,
                                  void * const pvBuffer )
{
    if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
    {
       /********省略******************/

       
        ( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize ); 
    }
}

由于函数使用memcpy函数,需要传入数据的地址来进行复制,任务1中

char *buf; buf = &BigData[0];

 xQueueSendToBack(BigData_Hander,&buf,portMAX_DELAY); 

buf存放的是数组BigData的地址,&buf传递的是buf的地址相当于传递了数组地址的地址,接收队列复制后的数据就是数组的地址。任务3 bigData就等于&BigData[0];

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部