xQueueReceive() 用于从队列中接收 ( 读取)数据单元。接收到的单元同时会从队列
中删除。
xQueuePeek() 也是从从队列中接收数据单元,不同的是并不从队列中删出接收到
的单元。
uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。
写队列任务的代码实现:
这个任务被创建了两个实例,一个不
停地往队列中写数值 100 ,而另一个实例不停地往队列中写入数值 200 。任务的入口参
数被用来为每个实例传递各自的写入值。
static void vSenderTask( void *pvParameters )
{ long lValueToSend; portBASE_TYPE xStatus; /* 该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递 – 这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,所以把入口参数强制转换为数据单元要求的类型 */ lValueToSend = ( long ) pvParameters; /* 和大多数任务一样,本任务也处于一个死循环中 */ for( ;; ) { /* 往队列发送数据第一个参数是要写入的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是被发送数据的地址,本例中即变量lValueToSend的地址。第三个参数是阻塞超时时间 – 当队列满时,任务转入阻塞状态以等待队列空间有效。本例中没有设定超时时间,因为此队列决不会保持有超过一个数据单元的机会,所以也决不会满。*/ xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 ); if( xStatus != pdPASS ) { /* 发送操作由于队列满而无法完成 – 这必然存在错误,因为本例中的队列不可能满。 */ vPrintString( "Could not send to the queue.\r\n" ); } /* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */ taskYIELD(); }
}
读队列任务的代码实现:
读队列任务设定了 100 毫秒的阻塞超时时
间,所以会进入阻塞态以等待队列数据有效。一旦队列中数据单元有效,或者即使队列
数据无效但等待时间超过 100 毫秒,此任务将会解除阻塞。在本例中,将永远不会出
现 100 毫秒超时,因为有两个任务在不停地往队列中写数据。
static void vReceiverTask( void *pvParameters )
{ /* 声明变量,用于保存从队列中接收到的数据。 */ long lReceivedValue; portBASE_TYPE xStatus; const portTickType xTicksToWait = 100 / portTICK_RATE_MS; /* 本任务依然处于死循环中。 */ for( ;; ) { /* 此调用会发现队列一直为空,因为本任务将立即删除刚写入队列的数据单元。 */ if( uxQueueMessagesWaiting( xQueue ) != 0 ) { vPrintString( "Queue should have been empty!\r\n" ); } /* 从队列中接收数据第一个参数是被读取的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是保存接收到的数据的缓冲区地址,本例中即变量lReceivedValue的地址。此变量类型与队列数据单元类型相同,所以有足够的大小来存储接收到的数据。第三个参数是阻塞超时时间 – 当队列空时,任务转入阻塞状态以等待队列数据有效。本例中常量portTICK_RATE_MS用来将100毫秒绝对时间转换为以系统心跳为单位的时间值。*/ xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait ); if( xStatus == pdPASS ) { /* 成功读出数据,打印出来。 */ vPrintStringAndNumber( "Received = ", lReceivedValue ); } else { /* 等待100ms也没有收到任何数据。必然存在错误,因为发送任务在不停地往队列中写入数据 */ vPrintString( "Could not receive from the queue.\r\n" ); } }
}
main()函数的实现:
其在启动调度器之前创建了一个队列和三
个任务。尽管对任务的优先级的设计使得队列实际上在任何时候都不可能多于一个数据
单元,本例代码还是创建了一个可以保存最多 5 个 long 型值的队列
/* 声明一个类型为 xQueueHandle 的变量. 其用于保存队列句柄,以便三个任务都可以引用此队列 */
xQueueHandle xQueue;
int main( void )
{ /* 创建的队列用于保存最多5个值,每个数据单元都有足够的空间来存储一个long型变量 */ xQueue = xQueueCreate( 5, sizeof( long ) ); if( xQueue != NULL ) { /* 创建两个写队列任务实例,任务入口参数用于传递发送到队列的值。所以一个实例不停地往队列发送100,而另一个任务实例不停地往队列发送200。两个任务的优先级都设为1。 */ xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL ); xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL ); /* 创建一个读队列任务实例。其优先级设为2,高于写任务优先级 */ xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL ); /* 启动调度器,任务开始执行 */ vTaskStartScheduler(); } else { /* 队列创建失败*/ } /* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章有讲述更多关于内存管理方面的信息 */ for( ;; );
}
写队列任务在每次循环中都调用 taskYIELD() 。 taskYIELD() 通知调度器立即进行任 务切换,而不必等到当前任务的时间片耗尽。某个任务调用 taskYIELD() 等效于其自愿 放弃运行态。由于本例中两个写队列任务具有相同的任务优先级,所以一旦其中一个任 务调用了 taskYIELD() ,另一个任务将会得到执行 — 调用 taskYIELD()的任务转移到 就绪态,同时另一个任务进入运行态。这样就可以使得这两个任务轮翻地往队列发送数据。
使用队列传递复合数据类型
通常接收方收到数 据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式 就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。
写队列任务具有最高优先级,所以队列正常情况下一直
是处于满状态。这是因为一旦读队列任务从队列中读走一个数据单元,某个写队列任务
就会立即抢占读队列任务,把刚刚读走的位置重新写入,之后便又转入阻塞态以等待队
列空间有效。
/* 定义队列传递的结构类型。 */
typedef struct
{ unsigned char ucValue; unsigned char ucSource;
} xData;
/* 声明两个xData类型的变量,通过队列进行传递。 */
static const xData xStructsToSend[ 2 ] =
{ { 100, mainSENDER_1 }, /* Used by Sender1. */ { 200, mainSENDER_2 } /* Used by Sender2. */
};static void vReceiverTask( void *pvParameters )
{ /* 声明结构体变量以保存从队列中读出的数据单元 */ xData xReceivedStructure; portBASE_TYPE xStatus; /* This task is also defined within an infinite loop. */ for( ;; ) { /* 读队列任务的优先级最低,所以其只可能在写队列任务阻塞时得到执行。而写队列任务只会在队列写满时才会进入阻塞态,所以读队列任务执行时队列肯定已满。所以队列中数据单元的个数应当等于队列的深度 – 本例中队列深度为3 */ if( uxQueueMessagesWaiting( xQueue ) != 3 ) { vPrintString( "Queue should have been full!\r\n" ); } /* Receive from the queue. 第二个参数是存放接收数据的缓存空间。本例简单地采用一个具有足够空间大小的变量的地址。第三个参数是阻塞超时时间 – 本例不需要指定超时时间,因为读队列任会只会在队列满时才会得到执行,故而不会因队列空而阻塞 */ xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 ); if( xStatus == pdPASS ) { /* 数据成功读出,打印输出数值及数据来源。 */ if( xReceivedStructure.ucSource == mainSENDER_1 ) { vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue ); } else { vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue ); } } else { /* 没有读到任何数据。这一定是发生了错误,因为此任务只支在队列满时才会得到执行 */ vPrintString( "Could not receive from the queue.\r\n" ); } }
}int main( void )
{ /* 创建队列用于保存最多3个xData类型的数据单元。 */ xQueue = xQueueCreate( 3, sizeof( xData ) ); if( xQueue != NULL ) { /* 为写队列任务创建2个实例。 The 任务入口参数用于传递发送到队列中的数据。因此其中一个任务往队列中一直写入xStructsToSend[0],而另一个则往队列中一直写入xStructsToSend[1]。这两个任务的优先级都设为2,高于读队列任务的优先级 */ xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL ); xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL ); /* 创建读队列任务。读队列任务优先级设为1,低于写队列任务的优先级。 */ xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL ); /* 启动调度器,创建的任务得到执行。 */ vTaskStartScheduler(); } else { /* 创建队列失败。 */ } /* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章将提供更多关于内存管理方面的信息 */ for( ;; );
}
如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对
数据本身在队列上一字节一字节地拷贝进或拷贝出。
但是,当你利用队列传递指针时,一定要十分小心地做到以
下两点:
1、指针指向的内存空间的所有权必须明确
原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;
共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
2.、指针指向的内存空间必须有效
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再
有效