【FreeRTOS初探】队列管理

文章目录


#队列管理

FreeRTOS中,所有的通讯与同步机制都是基于队列实现的。
##测试代码地址
github.com/CherryXiuHuaWoo/WIN32-MSVC-FreeRTOS-

##队列的特性

###数据存储

队列可以保存有限个具有确定长度数据单元

在队列创建时,需要设定:

  • 队列深度——队列可以保存的最大单元数目
  • 每个单元的大小

特性:

  • FIFO(先进先出)——数据由队列尾部(Tail)写入,由队列头部(Head)读出
  • 往队列写入数据——通过字节拷贝把数据复制存储到队列中。
  • 从队列读出数据——把队列中的数据拷贝删除
  • 队列是具有自己独立权限内核对象,并不属于或赋予任何任务所有任务都可以向同一个队列写入和读出

###读队列时阻塞

  • 某个任务试图读一个队列时,可以指定一个阻塞超时时间
  • 在阻塞超时时间内,若队列为空,该任务保持阻塞态等待数据有效。
  • 当其他任务或中断往队列中写入数据,该任务自动由阻塞态转移为就绪态
  • 对单个队列而言,可能有多个任务处于阻塞态,以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞——这个任务是所有等待任务中优先级最高的任务。如果所有等待任务的优先级相同,则被接触阻塞的任务将是等待最久的任务。

###写队列时阻塞

  • 任务可在写队列时,指定一个阻塞超时时间
  • 阻塞超时时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间
    这里写图片描述

##2、如何创建一个队列?

###创建队列 xQueueCreate()

  1. 函数功能:创建一个队列,并返回一个xQueueHandle句柄,以便对其创建的队列进行引用。
  2. 使用方法:当创建队列,FreeRTOS从堆空间中分配内存空间,用于存储队列数据结构本身以及队列中包含的数据单元。若内存堆没有足够空间来创建队列,xQueueCreate将返回NULL。
  3. 函数原型
xQueueHandle xQueueCreate( 
	unsigned portBASE_TYPE uxQueueLength,
	unsigned portBASE_TYPE uxItemSize );

参数:
uxQueueLength——队列能够存储的最大单元数目,即队列深度
uxItemSize——队列数据单元的长度,以字节为单位

##3、队列如何管理器数据?

##4、如何向队列发送数据?
###xQueueSendToBack
作用:用于将数据发送到队列尾部
函数原型:

portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
								 const void pvItemToQueue,
								 portTickType xTicksToWait);

参数:

  • xQueue——目标队列的句柄,即为调用xQueueCreate()创建改队列时的返回值。
  • pvItemToQueue——发送数据的指针。指向将要复制到目标队列中的数据单元。
  • xTicksToWait——阻塞超时时间。若在发送时队列已满,改时间是任务处于阻塞等待队列空间有效的最长等待时间。若把xTicksToWait设置为portMAX_DELAY,并且在FreeRTOSConfig.h中设定INCLUDE_vTaskSuspend为1,那阻塞等待将没有超时限制。
  • 返回值:pdPASS(成功)errQUEUE_FULL(队列已满无法将数据写入)。

###xQueueSendToFront

  • 作用:用于将数据发送到队列队首
  • 函数原型:
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
								 const void pvItemToQueue,
								 portTickType xTicksToWait);

##5、如何从队列接收数据?
###xQueueReceive()

  1. 作用:从队列中接收数据单元,接收到的单元同时会从队列中删除
  2. 函数原型:
portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
							 const void *pvBuffer,
							 portTickType xTicksToWait);

参数:
xQueue——被读队列的句柄
pvBuffer——接收缓存指针
xTicksToWait——阻塞超时时间
返回值:pdPASS(成功),errQUEUE_FULL(队列已空读不到任何数据)

###xQueuePeek()

  • 作用:从队列中接收数据单元,不会从队列中删除接收到的单元
  • 函数原型:
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
							 const void *pvBuffer,
							 portTickType xTicksToWait);

##6、队列阻塞是什么?
###uxQueueMessagesWaiting
作用:查询队列当前有效数据单元个数
函数原型:

unsigned portBASE_TYPE uxQueueMessageWaiting( xQueueHandle xQueue);

参数:
xQueue——被查询队列的句柄。
返回值:当前队列中保存的数据单元个数,返回0表示队列为空。
##7、往队列发送和从队列接收时,任务优先级有什么影响?
##8、队列读写实例

/*以下为main部分*/
	/* Create the queue. */
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

	if( xQueue != NULL )
	{
		/* Start the two tasks as described in the comments at the top of this
		file. */
		xTaskCreate(vSenderTask, "SenderTask100", 100, (void *) 100, 1, &xTask1Handle);
		xTaskCreate(vSenderTask, "SenderTask200", 100, (void *)200, 1, &xTask1Handle);
		xTaskCreate(vSenderTask, "SenderTask300", 100, (void *)300, 1, &xTask1Handle);
		xTaskCreate(vReciverTask, "ReciverTask", 100, NULL, 2, &xTask1Handle);

		/* Start the tasks and timer running. */
		vTaskStartScheduler();
	}

/*以下为user文件*/
extern QueueHandle_t xQueue;

void vSenderTask(void *pvParameters)
{
	long lValueToSend;
	portBASE_TYPE xStatus;

	lValueToSend = (long)pvParameters;

	while (1)
	{
		xStatus = xQueueSendToBack(xQueue, &lValueToSend, 0);

		if (xStatus != pdPASS)
		{
			printf("Could not send to the queue.\r\n");
		}

		/*Allow other tasks send data.taskYIELD API Fuction: inform Scheduler of joining other task.*/
		taskYIELD();
	}
}

void vReciverTask(void *pvParameters)
{
	long lReceivedValue;
	portBASE_TYPE xStatus;
	const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

	while (1)
	{
		if (uxQueueMessagesWaiting(xQueue) != 0)
		{
			printf("Queue should have been empty!\r\n");
		}

		xStatus = xQueueReceive(xQueue, &lReceivedValue, xTicksToWait);
		if (xStatus == pdPASS)
		{
			printf("Received= %d\r\n", lReceivedValue);
		}
		else
		{
			printf(" Could not receive from the queue.\r\n");
		}
	}
}

这里写图片描述

###taskYIELD()
作用通知调度器立即进行任务切换,而不必等待当前任务的时间片耗尽。当任务调用了taskYIELD()等效于自愿放弃运行态
使用方法:写队列在每次循环中都调用taskYIELD()。

##9、使用队列传递复合数据类型
一个任务从单个队列中接收来自多个发送源的数据是经常的事情。
接收方接收到数据之后,需要知道数据的来源,并根据数据的来源来决定下一步如何处理。
一个简单的方式就是利用队列传递结构体。结构体成员就包含了数据信息和来源信息
这里写图片描述

extern QueueHandle_t xQueue;
extern TaskHandle_t xTask1Handle;
extern TaskHandle_t xTask2Handle;

xData xStructsToSend[2] = 
{
	{ 100, 1},
	{ 200, 2}
};
void vSenderTask(void *pvParameters)
{
	xData *lValueToSend;
	portBASE_TYPE xStatus;

	lValueToSend = (xData *)pvParameters;

	while (1)
	{
		xStatus = xQueueSendToBack(xQueue, lValueToSend, 0);

		if (xStatus != pdPASS)
		{
			printf("Could not send to the queue.\r\n");
		}

		/*Allow other tasks send data.taskYIELD API Fuction: inform Scheduler of joining other task.*/
		taskYIELD();
	}
}

void vReciverTask(void *pvParameters)
{
	xData xReceivedStructure;
	portBASE_TYPE xStatus;
	const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

	while (1)
	{
		if (uxQueueMessagesWaiting(xQueue) != 0)
		{
			printf("Queue should have been empty\r\n");

		}

		xStatus = xQueueReceive(xQueue, &xReceivedStructure, xTicksToWait);
		if (xStatus == pdPASS)
		{
			printf("From Sender %d = %d\r\n", xReceivedStructure.ucSource, xReceivedStructure.ucValue);
		}
		else
		{
			printf(" Could not receive from the queue.\r\n");
		}
	}

}

/*main部分*/
extern xData xStructsToSend[2];

/*** SEE THE COMMENTS AT THE TOP OF THIS FILE ***/
void main_blinky( void )
{
const TickType_t xTimerPeriod = mainTIMER_SEND_FREQUENCY_MS;

	/* Create the queue. */
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof(xData) );

	if( xQueue != NULL )
	{
		/* Start the two tasks as described in the comments at the top of this
		file. */
		xTaskCreate(vSenderTask, "SenderTask100", 100, &(xStructsToSend[0]), 1, NULL);
		xTaskCreate(vSenderTask, "SenderTask200", 100, &(xStructsToSend[1]), 1, NULL);

		xTaskCreate(vReciverTask, "ReciverTask", 100, NULL, 2, NULL);



		/* Start the tasks and timer running. */
		vTaskStartScheduler();
	}

这里写图片描述
###工作于大型数据单元
如果队列存储的数据单元尺寸较大,最好利用队列来传递数据的指针,而不是数据本身在队列上一字节一字节地拷贝进或拷贝出。
注意:

  1. 指针指向的内存空间的所有权必须明确。共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
  2. 指针指向的内存空间必须有效。如果指针指向的内存空间是动态分配,应该有一个任务对其进行内存释放。当这段内存空间被释放后就不应该有任何一个任务再访问这段空间。
  3. 切忌用指针访问任务栈上分配的空间。因为当栈帧发生变化后,栈上的数据将不再有效。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS消息队列是一种用于任务间通信的机制,可以在任务之间传递消息。队列中可以存储有限的、大小固定的数据项目,任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。FreeRTOS中的信号量也是依据队列实现的,所以有必要深入了解FreeRTOS队列。 在FreeRTOS中,可以使用xQueueCreate()函数创建一个队列,该函数需要传入两个参数:队列的长度和每个队列项目的大小。创建队列后,可以使用xQueueSend()函数向队列中发送数据,使用xQueueReceive()函数从队列中接收数据。此外,还可以使用xQueuePeek()函数查看队列中的下一个数据项,而不将其从队列中删除。 下面是一个简单的示例,演示如何在FreeRTOS中使用消息队列: ```c #include "FreeRTOS.h" #include "task.h" #include "queue.h" #define QUEUE_LENGTH 5 #define ITEM_SIZE sizeof(int) void vSenderTask(void *pvParameters) { QueueHandle_t xQueue; int i = 0; // 创建一个长度为QUEUE_LENGTH,每个项目大小为ITEM_SIZE的队列 xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE); while (1) { // 向队列中发送数据 xQueueSend(xQueue, &i, 0); // 延时一段时间 vTaskDelay(pdMS_TO_TICKS(500)); i++; } } void vReceiverTask(void *pvParameters) { QueueHandle_t xQueue; int iReceivedValue; // 获取发送任务创建的队列句柄 xQueue = (QueueHandle_t)pvParameters; while (1) { // 从队列中接收数据 xQueueReceive(xQueue, &iReceivedValue, portMAX_DELAY); // 处理接收到的数据 printf("Received value: %d\n", iReceivedValue); } } int main(void) { QueueHandle_t xQueue; // 创建一个长度为QUEUE_LENGTH,每个项目大小为ITEM_SIZE的队列 xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE); // 创建发送任务 xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 创建接收任务 xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, (void *)xQueue, tskIDLE_PRIORITY + 1, NULL); // 启动调度器 vTaskStartScheduler(); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值