FreeRTOS学习—“任务”篇
FreeRTOS学习—“消息队列”篇
FreeRTOS学习—“信号量”篇
FreeRTOS学习—“事件组”篇
FreeRTOS学习—“定时器”篇
书接上文,继续来学习一下FreeRTOS的消息队列。
基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
为啥这句话这么肯定,难道我们用全局变量或者数组,就没办法进行通信吗?这里先埋下一个伏笔。
这次直接上demo程序,优秀的程序员,之所以效率高,都是demo用的熟练,俩字:出活儿。
这次是创建了三个任务,一个读队列,两个写队列。
xQueueHandle xQueue;
static void vSenderTask( void *pvParameters )
{
long lValueToSend;
portBASE_TYPE xStatus;
/* 该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递 – 这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,所以把入口参数强制转换为数据单元要求的类型 */
lValueToSend = ( long ) pvParameters;
/* 和大多数任务一样,本任务也处于一个死循环中 */
for( ;; )
{
/* 往队列发送数据
第一个参数是要写入的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。
第二个参数是被发送数据的地址,本例中即变量lValueToSend的地址。
第三个参数是阻塞超时时间 – 当队列满时,任务转入阻塞状态以等待队列空间有效。本例中没有设定超
时时间,因为此队列决不会保持有超过一个数据单元的机会,所以也决不会满。
*/
xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
if( xStatus != pdPASS )
{
/* 发送操作由于队列满而无法完成 – 这必然存在错误,因为本例中的队列不可能满。 */
printf( "Could not send to the queue.\r\n" );
}
/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */
taskYIELD();
}
}
static void vReceiverTask( void *pvParameters )
{
/* 声明变量,用于保存从队列中接收到的数据。 */
long lReceivedValue;
portBASE_TYPE xStatus;
const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
/* 本任务依然处于死循环中。 */
for( ;; )
{
/* 此调用会发现队列一直为空,因为本任务将立即删除刚写入队列的数据单元。 */
if( uxQueueMessagesWaiting( xQueue ) != 0 )
{
printf( "Queue should have been empty!\r\n" );
}
/* 从队列中接收数据
第一个参数是被读取的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。
第二个参数是保存接收到的数据的缓冲区地址,本例中即变量lReceivedValue的地址。此变量类型与
队列数据单元类型相同,所以有足够的大小来存储接收到的数据。
第三个参数是阻塞超时时间 – 当队列空时,任务转入阻塞状态以等待队列数据有效。本例中常量
portTICK_RATE_MS用来将100毫秒绝对时间转换为以系统心跳为单位的时间值。
*/
xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
if( xStatus == pdPASS )
{
/* 成功读出数据,打印出来。 */
printf( "Received = %ld \r\n", lReceivedValue );
}
else
{
/* 等待100ms也没有收到任何数据。必然存在错误,因为发送任务在不停地往队列中写入数据 */
printf( "Could not receive from the queue.\r\n" );
}
}
}
void app_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
{
/* 队列创建失败*/
printf( "create queue fail.\r\n" );
}
/* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章有讲述更多关于内存管理方面的信息 */
//for( ;; );
}
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize );
参数 | 含义 |
---|---|
uxQueueLength | 队列能够存储的最大单元数目,即队列深度。 |
uxItemSize | 队列中数据单元的长度,以字节为单位。 |
返回值 | NULL 表示没有足够的堆空间分配给队列而导致创建失败。 |
非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。 |
//消息写入队列头,会先被读出
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
//消息写入队列尾
portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
//完全等同xQueueSendToBack
portBASE_TYPE xQueueSend( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
参数 | 含义 |
---|---|
xQueue | 目标队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。 |
pvItemToQueue | 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。 |
xTicksToWait | 阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。如 果 xTicksToWait 设 为 0 ,并且队列已满,则xQueueSendToFront()与 xQueueSendToBack()均会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位。如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。 |
返回值 | 有两个可能的返回值: |
1. pdPASS 返回 pdPASS 只会有一种情况,那就是数据被成功发送到队列中。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效—在超时到来前能够将数据成功写入到队列,函数则会返回 pdPASS。 | |
2. errQUEUE_FULL 如果由于队列已满而无法将数据写入,则将返回errQUEUE_FULL。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效。但直到超时也没有其它任务或是中断服务例程读取队列而腾出空间,函数则会返回 errQUEUE_FULL。 |
切记,在中断服务中不要用这些函数,系统有专门提供在中断中执行的函数。
xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能
//xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。
portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
const void * pvBuffer,
portTickType xTicksToWait );
/*xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。
xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。*/
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
const void * pvBuffer,
portTickType xTicksToWait );
参数 | 含义 |
---|---|
xQueue | 被读队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。 |
pvBuffer | 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。 |
xTicksToWait | 阻塞超时时间。如果在接收时队列为空,则这个时间是任务处于阻塞状态以等待队列数据有效的最长等待时间。如果 xTicksToWait 设为 0,并且队列为空,则 xQueueRecieve()与 xQueuePeek()均会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位。如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。 |
返回值 | 有两个可能的返回值: |
1. pdPASS 只有一种情况会返回 pdPASS,那就是成功地从队列中读到数据。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效—在超时到来前能够从队列中成功读取数据,函数则会返回 pdPASS。 | |
2. errQUEUE_FULL 如果在读取时由于队列已空而没有读到任何数据,则将返回errQUEUE_FULL。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效。但直到超时也没有其它任务或是中断服务例程往队列中写入数据,函数则会返回errQUEUE_FULL。 |
切记不要在中断服务例程中调用 xQueueRceive()和 xQueuePeek()。
中断安全版本的替代 API 函数为 xQueueReceiveFromISR()。
unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );
参数 | 含义 |
---|---|
xQueue | 被查询队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。 |
返回值 | 当前队列中保存的数据单元个数。返回 0 表明队列为空。 |
void vQueueDelete( QueueHandle_t xQueue )
参数 | 含义 |
---|---|
xQueue | 被删除的队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。 |
void xQueueReset( QueueHandle_t xQueue )
参数 | 含义 |
---|---|
xQueue | 被重新清空的队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。 |
FreeRTOS学习—“任务”篇
FreeRTOS学习—“消息队列”篇
FreeRTOS学习—“信号量”篇
FreeRTOS学习—“事件组”篇
FreeRTOS学习—“定时器”篇