FreeRTOS --(14)队列管理之概述

转载自 https://blog.csdn.net/zhoutaopower/article/details/107221175

在任何的 OS 中,都需要支持任务与任务,中断与任务之间的数据传输机制,在 FreeRTOS 中,这种数据传输的方式被称之为队列(Queue);

队列是一个 FIFO 模型,在创建一个队列用于数据传递的时候,需要指定队列的长度,创建完队列,便可以使用它进行数据传递;一个简单的例子:

有两个任务 A 和 B,任务 A 将数据传递进队列,任务 B 作为接收端,从队列中获取数据:

1、下面是创建了一个长度为 5 的队列:

2、此刻任务 A 写一个数据 10 到 Queue:

3、任务 A 在写一个数据 20 到 Queue;

4、此刻任务 B 从 Queue 中读取,先读取第一个数据 10;

5、此刻 10 已经被读走,20 成为下一个即将被读取的数据:

1、Basic Feature

1.1、Access by Multiple Tasks

同一个队列可以被多个任务(或者在 ISR 中)访问;可以有多个任务对同一个 Queue 写入,也可以多个任务进行读;实际中,多个任务同时写一个 Queue 是比较常见的,多个读者是比较少见的;

1.2、Blocking on Queue Reads

当一个任务企图去读一个 Queue 的时候,可以选择指定阻塞时间;这个是什么意思呢?这个时间指的是,当这个 Queue 为空的时候,这个任务保持阻塞在读这个 Queue 的时间;因为一个任务执行的时候,很多情况是,获得到来自另一个任务(或者中断)的数据后,才得以执行,在这之前,最好是保持在阻塞状态,这样可以免得调度器在调度没用的任务;

一个任务阻塞在读 Queue 上,等待数据,当另外的任务或者 ISR 给这个 Queue 喂了数据后,这个被阻塞的任务将会从阻塞链表中移除,被加入到 Ready 任务链表;或者当指定的阻塞时间到,要读的 Queue 还是为空,那么也会解除阻塞,进入 Ready 链表;

Queue 支持多个读者,所以,如果多个任务都在读一个已经空了的 Queue,那么他们都将进入阻塞状态,

出现这种情况的时候,如果当 Queue 有数据的时候,只有一个最高优先级的任务可以解除阻塞,进入 Ready;如果这几个任务的优先级一样,那么等待数据并处于阻塞的时间最长的那个任务将被解除阻塞;

1.3、Blocking on Queue Writes

对于写来说,和读一样的,当一个任务企图去写一个 Queue 的时候,可以选择指定阻塞时间;这个时间指的是,当这个 Queue 为满的时候,这个任务保持阻塞在写这个 Queue 的时间;

一个任务阻塞在写 Queue 上,等待数据,当另外的任务或者 ISR 读走了 Queue 数据导致这个 Queue 不在是满的状态,这个被阻塞的任务将会从阻塞链表中移除,被加入到 Ready 任务链表;或者当指定的阻塞时间到,要写的 Queue 还是为满,那么也会解除阻塞,进入 Ready 链表;

Queue 支持多个写者,所以,如果多个任务都在写一个已经满了的 Queue,那么他们都将进入阻塞状态,出现这种情况的时候,一旦 Queue 有数据被读走,阻塞在写上的几个任务只有一个最高优先级的任务可以解除阻塞,进入 Ready;如果这几个任务的优先级一样,那么等待数据并处于阻塞的时间最长的那个任务将被解除阻塞;

2、Usage And APIs

2.1、xQueueCreate()

xQueueCreate() 接口用于创建一个 Queue,函数的原型为:

QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

它有两个入参和一个返回值:

uxQueueLength:Queue 中能够存储的最大的 Items 的个数;

uxItemSize:一个 Item 的大小,单位为 Bytes;

Return:如果返回 NULL,说明创建 Queue 失败;否则将返回这个 Queue 的 Handle 句柄,后面操作这个 Queue 就靠这个句柄;

2.2、xQueueSendToBack()/xQueueSend And xQueueSendToFront()

向队列中插入数据分两组 API,一组是往队列的尾部插入,使用 xQueueSendToBack()/xQueueSend() ;另一组是往队列的头部插入:xQueueSendToFront()

注意:不要在 ISR 中使用 xQueueSendToBack、xQueueSendToFront,应该使用对应的 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR ;

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

它有3个入参和一个返回值:

xQueue:队列创建时候的句柄,用于表示向哪个队列写入数据;

pvItemToQueue:放入队列的 Item 的指针;

xTicksToWait:如果队列为满,则阻塞的最大时间;当被设置为 0 的时候,不阻塞,如果队列为满,则直接返回;如果设置为 portMAX_DELAY 的话,意味着如果队列为满,则会挂起这个任务;

Return:当成功放入队列,返回 pdTRUE;否则返回 errQUEUE_FULL;

2.3、xQueueReceive()

xQueueReceive() 用于从队列中读数据;读出数据并将数据从 Queue 中删除

注意:不要在 ISR 中使用 xQueueReceive,应该使用对应的 xQueueReceiveFromISR;

它的函数原型为:

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

它有3个入参和一个返回值:

xQueue:队列创建时候的句柄,用于表示向哪个队列写入数据;

pvBuffer:从队列读出来的数据指针;

xTicksToWait:如果队列为空,则为阻塞的最大时间;当被设置为 0 的时候,不阻塞,如果队列为空,则直接返回;如果设置为 portMAX_DELAY 的话,意味着如果队列为空,则会挂起这个任务;

Return:当成功读出队列的数据,返回 pdTRUE;否则返回 errQUEUE_EMPTY;

2.4、uxQueueMessagesWaiting()

该函数用于查询当前指定的队列中有多少个 Item;

它的函数原型是:

UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );

注意:不要在 ISR 中使用 uxQueueMessagesWaiting,应该使用对应的 uxQueueMessagesWaitingFromISR;

它有1个入参和1个返回值:

xQueue:队列创建时候的句柄,用于表示查看哪个队列还有几个 Item;

Return:当前队列中还有几个 Item 元素,如果返回 0,说明队列为空;

2.5、Example:Blocking when receiving from a queue

下面这个例子是:

1、创建一个 Queue;

2、多个任务往 Queue 写数据,一个任务读数据;

static void vSenderTask( void *pvParameters )
{
    int32_t lValueToSend;
    BaseType_t xStatus;
    /* Two instances of this task are created so the value that is sent to the
    queue is passed in via the task parameter - this way each instance can use 
    a different value. The queue was created to hold values of type int32_t, 
    so cast the parameter to the required type. */
    lValueToSend = ( int32_t ) pvParameters;
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* Send the value to the queue.
        The first parameter is the queue to which data is being sent. The 
        queue was created before the scheduler was started, so before this task
        started to execute.
        The second parameter is the address of the data to be sent, in this case
        the address of lValueToSend.
        The third parameter is the Block time – the time the task should be kept
        in the Blocked state to wait for space to become available on the queue
        should the queue already be full. In this case a block time is not 
        specified because the queue should never contain more than one item, and
        therefore never be full. */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
        if( xStatus != pdPASS )
        {
            /* The send operation could not complete because the queue was full -
            this must be an error as the queue should never contain more than 
            one item! */
            vPrintString( "Could not send to the queue.
" );
        }
 
    }
}

4、创建 1 个接收数据的任务

static void vReceiverTask( void *pvParameters )
{
    /* Declare the variable that will hold the values received from the queue. */
    int32_t lReceivedValue;
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
    /* This task is also defined within an infinite loop. */
    for( ;; )
    {
        /* This call should always find the queue empty because this task will
        immediately remove any data that is written to the queue. */
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!
" );
        }
        /* Receive data from the queue.
        The first parameter is the queue from which data is to be received. The
        queue is created before the scheduler is started, and therefore before this
        task runs for the first time.
        The second parameter is the buffer into which the received data will be
        placed. In this case the buffer is simply the address of a variable that
        has the required size to hold the received data.
        The last parameter is the block time – the maximum amount of time that the
        task will remain in the Blocked state to wait for data to be available 
        should the queue already be empty. */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
            value. */
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            /* Data was not received from the queue even after waiting for 100ms.
            This must be an error as the sending tasks are free running and will be
            continuously writing to the queue. */
            vPrintString( "Could not receive from the queue.
" );
        }
    } 
}

5、初始化如下:

/* Declare a variable of type QueueHandle_t. This is used to store the handle
to the queue that is accessed by all three tasks. */
QueueHandle_t xQueue;
int main( void )
{
    /* The queue is created to hold a maximum of 5 values, each of which is
    large enough to hold a variable of type int32_t. */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    if( xQueue != NULL )
    {
        /* Create two instances of the task that will send to the queue. The task
        parameter is used to pass the value that the task will write to the queue,
        so one task will continuously write 100 to the queue while the other task 
        will continuously write 200 to the queue. Both tasks are created at
        priority 1. */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        /* Create the task that will read from the queue. The task is created with
        priority 2, so above the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }
    
    /* If all is well then main() will never reach here as the scheduler will 
    now be running the tasks. If main() does reach here then it is likely that 
    there was insufficient FreeRTOS heap memory available for the idle task to be 
    created. Chapter 2 provides more information on heap memory management. */
    for( ;; );
}

首先创建了深度为 5 ,每个 Item 为 int32_t 的数据的 Queue;

创建了 2 个发送线程,优先级都为 1,Sender 1 往 Queue 中发送 100,Sender 2 往 Queue 中发送 200;阻塞时间为 0;

创建了 1 个接收线程,优先级为 2,带阻塞的读 Queue 中的数据;

在时序上看到的情况如下:

由于 Receiver 的优先级最高,并且带阻塞的读,也就是他会抢占低优先级的 Sender1 和 Sender2;也就是说,这个队列只可能有一个 Item,因为读 Queue 的任务优先级高于了写 Queue,一旦有数据,立马会被读走;

2.6、Receiving Data From Multiple Sources

在 FreeRTOS 中一个比较常见的场景是,接收来自不同 Source 的数据;接收端需要知道接收到的数据是从哪端发过来的,这样才知道怎么处理数据,有一种简单的设计方案可以来搞定这种场景,比如,可以定义一个结构体:

typedef struct {
    uint8_t ucValue;
    DataSource_t eDataSourceID;
} Data_t;

我们通过定义并传递 Data 的 ID 来得知

typedef enum
{
    eCanBusSender,
    eHMISender
} DataSource_t;

如图所示,有很多个数据的来源,都往同一个 Queue 中放置 Data_t 数据,放数据的时候,通过 eDataID 来标记数据的来源;在数据的读取那端,根据读到的 Data_t 中的 eDataID 就能够区分数据的来源;

Example:

两个发送者,一个接受者;

两个发送者的优先级一样,都为 2,接受者优先级为 1;

发送者调用 xQueueSendToBack,并参数带上阻塞时间 100ms;

接受者调用 xQueueReceive ,阻塞时间为 0(因为优先级低,所以当发送者将 Queue 塞满后,进入了阻塞,就轮到接受者执行,一旦接受者读出一个数据,那么此刻高优先级的发送者又将可以运行);

/* Define an enumerated type used to identify the source of the data. */
typedef enum
{
    eSender1,
    eSender2
} DataSource_t;
/* Define the structure type that will be passed on the queue. */
typedef struct
{
    uint8_t ucValue;
    DataSource_t eDataSource;
} Data_t;
/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{
    { 100, eSender1 }, /* Used by Sender1. */
    { 200, eSender2 } /* Used by Sender2. */
};

两个发送者的代码为

static void vSenderTask( void *pvParameters )
{
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* Send to the queue.
        The second parameter is the address of the structure being sent. The
        address is passed in as the task parameter so pvParameters is used 
        directly.
        The third parameter is the Block time - the time the task should be kept
        in the Blocked state to wait for space to become available on the queue
        if the queue is already full. A block time is specified because the
        sending tasks have a higher priority than the receiving task so the queue
        is expected to become full. The receiving task will remove items from 
        the queue when both sending tasks are in the Blocked state. */
        xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
        if( xStatus != pdPASS )
        {
            /* The send operation could not complete, even after waiting for 100ms.
            This must be an error as the receiving task should make space in the 
            queue as soon as both sending tasks are in the Blocked state. */
            vPrintString( "Could not send to the queue.
" );
        }
    }
}

接收端为

static void vReceiverTask( void *pvParameters )
{
    /* Declare the structure that will hold the values received from the queue. */
    Data_t xReceivedStructure;
    BaseType_t xStatus;
    /* This task is also defined within an infinite loop. */
    for( ;; )
    {
        /* Because it has the lowest priority this task will only run when the
        sending tasks are in the Blocked state. The sending tasks will only enter
        the Blocked state when the queue is full so this task always expects the
        number of items in the queue to be equal to the queue length, which is 3 in
        this case. */
        if( uxQueueMessagesWaiting( xQueue ) != 3 )
        {
            vPrintString( "Queue should have been full!
" );
        }
        /* Receive from the queue.
        The second parameter is the buffer into which the received data will be
        placed. In this case the buffer is simply the address of a variable that
        has the required size to hold the received structure. 
        The last parameter is the block time - the maximum amount of time that the
        task will remain in the Blocked state to wait for data to be available 
        if the queue is already empty. In this case a block time is not necessary 
        because this task will only run when the queue is full. */
        xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
            value and the source of the value. */
            if( xReceivedStructure.eDataSource == eSender1 )
            {
                vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
            }
            else
            {
                vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
            }
        }
        else
        {
            /* Nothing was received from the queue. This must be an error as this 
            task should only run when the queue is full. */
            vPrintString( "Could not receive from the queue.
" );
        }
    } 
}

初始化代码为:

int main( void )
{
    /* The queue is created to hold a maximum of 3 structures of type Data_t. */
    xQueue = xQueueCreate( 3, sizeof( Data_t ) );
    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue. The
        parameter is used to pass the structure that the task will write to the 
        queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
        while the other task will continuously send xStructsToSend[ 1 ]. Both 
        tasks are created at priority 2, which is above the priority of the receiver. */
        xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );
        /* Create the task that will read from the queue. The task is created with
        priority 1, so below the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
    /* The queue could not be created. */
    }
 
    /* If all is well then main() will never reach here as the scheduler will 
    now be running the tasks. If main() does reach here then it is likely that 
    there was insufficient heap memory available for the idle task to be created. 
    Chapter 2 provides more information on heap memory management. */
    for( ;; );
}

运行的结果为:

时序上的表现是:

最开始 t1 时刻,Sender 1 往 Queue 中放了 3 个数据(因为它优先级高)后,进入 Blocked;

t2 时刻,Sender 2 希望往 Queue 放数据,结果已经满了,直接进入 Blocked;

t3 时刻,此刻优先级最低的 Receiver 得以执行,读出一个 Item;

t4 时刻,由于 Sender 1 和 Sender 2 都阻塞在写 Queue,此刻 Queue 不为满,所以 Sender 1 得以执行,并往 Queue 中写入一个 Item;

接着就是 Receiver---Sender 2---Receiver---Sender 1---.......

2.7、Working with Large or Variable Sized Data

上面说的使用 Queue 传输,适用于传输小额数据,如果要传输大数据或者变长数据,官方推荐使用另一种方式:传递数据的指针,避免直接传数据进行 Copy;

如果使用这种方式来弄,那么需要注意一下两点:

1、当通过指针在任务之间共享内存时,必须确保,任务不会同时修改内存内容或执行任何其他操作,否则可能导致内存内容无效或不一致。正常情况下,在读共享内存的任务读走有效数据之前,只有发送者才允许取访问该共享内存;

2、如果指针指向的内存是动态分配的,或者从已经准备好的内存池中获取的,那么需要有一个任务去释放他们;被释放后,不允许在访问;

注意:不要使用任务堆栈的指针。堆栈帧更改后,数据将无效。

Example:

创建一个 Queue,可以保存 5 个指针;

发送方:分配内存,并填充,然后发送指针到 Queue

接收放:获取指针,获得指针指向的数据

/* Declare a variable of type QueueHandle_t to hold the handle of the queue being created. */
QueueHandle_t xPointerQueue;
/* Create a queue that can hold a maximum of 5 pointers, in this case character pointers. */
xPointerQueue = xQueueCreate( 5, sizeof( char * ) );

Sender 分配内存,发送字符串指针到 Queue;

/* A task that obtains a buffer, writes a string to the buffer, then sends the address of the 
buffer to the queue created in Listing 52. */
void vStringSendingTask( void *pvParameters )
{
    char *pcStringToSend;
    const size_t xMaxStringLength = 50;
    BaseType_t xStringNumber = 0;
    for( ;; )
    {
        /* Obtain a buffer that is at least xMaxStringLength characters big. The implementation 
        of prvGetBuffer() is not shown – it might obtain the buffer from a pool of pre-allocated 
        buffers, or just allocate the buffer dynamically. */
        pcStringToSend = ( char * ) prvGetBuffer( xMaxStringLength );
        /* Write a string into the buffer. */
        snprintf( pcStringToSend, xMaxStringLength, "String number %d
", xStringNumber );
        /* Increment the counter so the string is different on each iteration of this task. */
        xStringNumber++;
        /* Send the address of the buffer to the queue that was created in Listing 52. The
        address of the buffer is stored in the pcStringToSend variable.*/
        xQueueSend( xPointerQueue, /* The handle of the queue. */
                    &pcStringToSend, /* The address of the pointer that points to the buffer. */
                    portMAX_DELAY );
    }
}

接收端接收拿到数据指针,打印,并释放内存;

void vStringReceivingTask( void *pvParameters )
{
    char *pcReceivedString;
    for( ;; )
    {
        /* Receive the address of a buffer. */
        xQueueReceive( xPointerQueue, /* The handle of the queue. */
        &pcReceivedString, /* Store the buffer’s address in pcReceivedString. */
        portMAX_DELAY );
        /* The buffer holds a string, print it out. */
        vPrintString( pcReceivedString );
        /* The buffer is not required any more - release it so it can be freed, or re-used. */
        prvReleaseBuffer( pcReceivedString );
    }
}

2.7.1、Send Different Types and Lengths of Data

前面展示了使用同一个 Queue,不同 Source 发送到 Queue,以及变长的数据发送(通过指针);

如果将这两者结合起来,便可以做到使用一个 Queue 来接收来自不同 Source 的任何数据(变长数据)的目的(FreeRTOS+TCP TCP/IP 协议栈中有用这种方式);

下面看代码:

1、首先定义一些基本的结构,数据的 Source(eIPEvent_t)以及发送给 Queue 的 Item:

/* A subset of the enumerated types used in the TCP/IP stack to identify events. */
typedef enum
{
    eNetworkDownEvent = 0, /* The network interface has been lost, or needs (re)connecting. */
    eNetworkRxEvent, /* A packet has been received from the network. */
    eTCPAcceptEvent, /* FreeRTOS_accept() called to accept or wait for a new client. */
    /* Other event types appear here but are not shown in this listing. */
} eIPEvent_t;
/* The structure that describes events, and is sent on a queue to the TCP/IP task. */
typedef struct IP_TASK_COMMANDS
{
    /* An enumerated type that identifies the event. See the eIPEvent_t definition above. */
    eIPEvent_t eEventType;
    /* A generic pointer that can hold a value, or point to a buffer. */
    void *pvData;
} IPStackEvent_t;

2、网络接收的数据通过 Queue 传递给 TCP/IP 协议栈,eEventType 标记为 eNetworkRxEvent:

void vSendRxDataToTheTCPTask( NetworkBufferDescriptor_t *pxRxedData ) {
    IPStackEvent_t xEventStruct;
    
    /* Complete the IPStackEvent_t structure. The received data is stored in 
    pxRxedData. */
    xEventStruct.eEventType = eNetworkRxEvent;
    xEventStruct.pvData = ( void * ) pxRxedData;
    /* Send the IPStackEvent_t structure to the TCP/IP task. */
    xSendEventStructToIPTask( &xEventStruct );
}

3、Accept 事件也会给 TCP/IP 任务发送数据:

void vSendAcceptRequestToTheTCPTask( Socket_t xSocket ) {
    IPStackEvent_t xEventStruct;
    
    /* Complete the IPStackEvent_t structure. */
    xEventStruct.eEventType = eTCPAcceptEvent;
    xEventStruct.pvData = ( void * ) xSocket;
    /* Send the IPStackEvent_t structure to the TCP/IP task. */
    xSendEventStructToIPTask( &xEventStruct );
}

4、网络 Down 掉的事件也会发送给 TCP/IP 任务处理:

void vSendNetworkDownEventToTheTCPTask( Socket_t xSocket ) {
    IPStackEvent_t xEventStruct;
    
    /* Complete the IPStackEvent_t structure. */
    xEventStruct.eEventType = eNetworkDownEvent;
    xEventStruct.pvData = NULL; /* Not used, but set to NULL for completeness. */
    /* Send the IPStackEvent_t structure to the TCP/IP task. */
    xSendEventStructToIPTask( &xEventStruct );
}

5、在 TCP/IP 协议栈受到这些消息后,根据消息的来源,获取数据(均以指针传递),并处理:

IPStackEvent_t xReceivedEvent;
/* Block on the network event queue until either an event is received, or xNextIPSleep ticks 
pass without an event being received. eEventType is set to eNoEvent in case the call to 
xQueueReceive() returns because it timed out, rather than because an event was received. */
xReceivedEvent.eEventType = eNoEvent;
xQueueReceive( xNetworkEventQueue, &xReceivedEvent, xNextIPSleep );
/* Which event was received, if any? */
switch( xReceivedEvent.eEventType )
{
    case eNetworkDownEvent :
        /* Attempt to (re)establish a connection. This event is not associated with any 
        data. */
        prvProcessNetworkDownEvent();
    break;
    case eNetworkRxEvent:
        /* The network interface has received a new packet. A pointer to the received data 
        is stored in the pvData member of the received IPStackEvent_t structure. Process 
        the received data. */
        prvHandleEthernetPacket( ( NetworkBufferDescriptor_t * )( xReceivedEvent.pvData ) );
    break;
    case eTCPAcceptEvent:
        /* The FreeRTOS_accept() API function was called. The handle of the socket that is 
        accepting a connection is stored in the pvData member of the received IPStackEvent_t 
        structure. */
        xSocket = ( FreeRTOS_Socket_t * ) ( xReceivedEvent.pvData );
        xTCPCheckNewClient( pxSocket );
    break;
    /* Other event types are processed in the same way, but are not shown here. */
}

2.8、Receiving From Multiple Queues

前面的部分,全部都说的是,多个任务使用一个 Queue,其实 FreeRTOS 对于这种场景,还有一种实现方式,就是使用 Queue Set,就是队列集合;

Queue Set 允许用户定义对个 Queue,并且调用 FreeRTOS 的 xQueueCreateSet 定义一个队列集合,将多个 Queue 通过 xQueueAddToSet 的方式添加到 Queue Set;每个任务只操作自己的 Queue,在接收端使用 xQueueSelectFromSet 配合 xQueueReceive 来接收数据;

Example:

创建两个 Queue,加入到同一个 Queue Set

创建两个发送任务和一个接受任务;

/* Declare two variables of type QueueHandle_t. Both queues are added to the same 
queue set. */
static QueueHandle_t xQueue1 = NULL, xQueue2 = NULL;
/* Declare a variable of type QueueSetHandle_t. This is the queue set to which the 
two queues are added. */
static QueueSetHandle_t xQueueSet = NULL;
int main( void )
{
    /* Create the two queues, both of which send character pointers. The priority 
    of the receiving task is above the priority of the sending tasks, so the queues 
    will never have more than one item in them at any one time*/
    xQueue1 = xQueueCreate( 1, sizeof( char * ) );
    xQueue2 = xQueueCreate( 1, sizeof( char * ) );
    /* Create the queue set. Two queues will be added to the set, each of which can 
    contain 1 item, so the maximum number of queue handles the queue set will ever 
    have to hold at one time is 2 (2 queues multiplied by 1 item per queue). */
    xQueueSet = xQueueCreateSet( 1 * 2 );
    /* Add the two queues to the set. */
    xQueueAddToSet( xQueue1, xQueueSet );
    xQueueAddToSet( xQueue2, xQueueSet );
    /* Create the tasks that send to the queues. */
    xTaskCreate( vSenderTask1, "Sender1", 1000, NULL, 1, NULL );
    xTaskCreate( vSenderTask2, "Sender2", 1000, NULL, 1, NULL );
    /* Create the task that reads from the queue set to determine which of the two 
    queues contain data. */
    xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
    /* Start the scheduler so the created tasks start executing. */
    vTaskStartScheduler();
    /* As normal, vTaskStartScheduler() should not return, so the following lines 
    Will never execute. */
    for( ;; );
    return 0;
}

两个发送任务的实现,分别往两个不同的 Queue 发送数据,传递数据指针,如下所示:

void vSenderTask1( void *pvParameters )
{
    const TickType_t xBlockTime = pdMS_TO_TICKS( 100 );
    const char * const pcMessage = "Message from vSenderTask1
";
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* Block for 100ms. */
        vTaskDelay( xBlockTime );
        /* Send this task's string to xQueue1. It is not necessary to use a block 
        time, even though the queue can only hold one item. This is because the 
        priority of the task that reads from the queue is higher than the priority of 
        this task; as soon as this task writes to the queue it will be pre-empted by 
        the task that reads from the queue, so the queue will already be empty again 
        by the time the call to xQueueSend() returns. The block time is set to 0. */
        xQueueSend( xQueue1, &pcMessage, 0 );
    }
}
/*-----------------------------------------------------------*/
void vSenderTask2( void *pvParameters )
{
 const TickType_t xBlockTime = pdMS_TO_TICKS( 200 );
 const char * const pcMessage = "Message from vSenderTask2
";
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* Block for 200ms. */
        vTaskDelay( xBlockTime );
        /* Send this task's string to xQueue2. It is not necessary to use a block 
        time, even though the queue can only hold one item. This is because the 
        priority of the task that reads from the queue is higher than the priority of 
        this task; as soon as this task writes to the queue it will be pre-empted by 
        the task that reads from the queue, so the queue will already be empty again 
        by the time the call to xQueueSend() returns. The block time is set to 0. */
        xQueueSend( xQueue2, &pcMessage, 0 );
    }
}

接收任务通过调用 xQueueSelectFromSet 来获得这个 Queue Set 中,是哪个 Queue Handle 来了数据,并通过 xQueueReceive 来获取数据,如下所示:

void vReceiverTask( void *pvParameters )
{
    QueueHandle_t xQueueThatContainsData;
    char *pcReceivedString;
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* Block on the queue set to wait for one of the queues in the set to contain data.
        Cast the QueueSetMemberHandle_t value returned from xQueueSelectFromSet() to a 
        QueueHandle_t, as it is known all the members of the set are queues (the queue set 
        does not contain any semaphores). */
        xQueueThatContainsData = ( QueueHandle_t ) xQueueSelectFromSet( xQueueSet,
                                                                    portMAX_DELAY );
        /* An indefinite block time was used when reading from the queue set, so
        xQueueSelectFromSet() will not have returned unless one of the queues in the set 
        contained data, and xQueueThatContainsData cannot be NULL. Read from the queue. It 
        is not necessary to specify a block time because it is known the queue contains
        data. The block time is set to 0. */
        xQueueReceive( xQueueThatContainsData, &pcReceivedString, 0 );
        /* Print the string received from the queue. */
        vPrintString( pcReceivedString );
    } 
}

2.9、Queue to Create a Mailbox

Queue 也可以用作 MailBox 的实现;在 FreeRTOS 中,mailbox 的实现是靠一个长度的 Queue 来做的;

Mailbox 用来为保持数据,以供其他的任务或者 ISR 来读取;在 mailbox 中的数据不会被冲掉,只能被重写;

重写一个 Queue 的数据,使用接口 xQueueOverwrite:

BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );

注意:ISR 中不准使用 xQueueOverwrite,要使用 xQueueOverwriteFromISR

两个入参,一个返回值:

xQueue:Queue 的句柄;

pvItemToQueue:要更新的数据指针;

Return:返回 pdPASS;

访问 mailbox 的数据使用接口 xQueuePeek

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

注意:ISR 中不准使用 xQueuePeek,要使用 xQueuePeekFromISR

3 个参数:

xQueue:Queue 的句柄;

pvBuffer:获取到的数据指针;

xTicksToWait:如果 mailbox 没有数据,阻塞的时间;

典型用法:

初始化一个 mailbox,就是 1 个 Example_t 大小的 Queue:

typedef struct xExampleStructure
{
    TickType_t xTimeStamp;
    uint32_t ulValue;
} Example_t;
/* A mailbox is a queue, so its handle is stored in a variable of type
QueueHandle_t. */
QueueHandle_t xMailbox;
 
void vAFunction( void )
{
    /* Create the queue that is going to be used as a mailbox. The queue has a 
    length of 1 to allow it to be used with the xQueueOverwrite() API function, which 
    is described below. */
    xMailbox = xQueueCreate( 1, sizeof( Example_t ) );
}

更新 mailbox 内容:

void vUpdateMailbox( uint32_t ulNewValue )
{
    /* Example_t was defined in Listing 67. */
    Example_t xData;
    /* Write the new data into the Example_t structure.*/
    xData.ulValue = ulNewValue;
    /* Use the RTOS tick count as the time stamp stored in the Example_t structure. */
    xData.xTimeStamp = xTaskGetTickCount();
    /* Send the structure to the mailbox - overwriting any data that is already in the 
    mailbox. */
    xQueueOverwrite( xMailbox, &xData );
}

读取 mailbox 内容:

BaseType_t vReadMailbox( Example_t *pxData ) 
{
    TickType_t xPreviousTimeStamp;
    BaseType_t xDataUpdated;
    
    /* This function updates an Example_t structure with the latest value received 
    from the mailbox. Record the time stamp already contained in *pxData before it 
    gets overwritten by the new data. */
    xPreviousTimeStamp = pxData->xTimeStamp;
    /* Update the Example_t structure pointed to by pxData with the data contained in
    the mailbox. If xQueueReceive() was used here then the mailbox would be left 
    empty, and the data could not then be read by any other tasks. Using 
    xQueuePeek() instead of xQueueReceive() ensures the data remains in the mailbox.
    A block time is specified, so the calling task will be placed in the Blocked 
    state to wait for the mailbox to contain data should the mailbox be empty. An
    infinite block time is used, so it is not necessary to check the value returned 
    from xQueuePeek(), as xQueuePeek() will only return when data is available. */
    xQueuePeek( xMailbox, pxData, portMAX_DELAY );
    /* Return pdTRUE if the value read from the mailbox has been updated since this 
    function was last called. Otherwise return pdFALSE. */
    if( pxData->xTimeStamp > xPreviousTimeStamp )
    {
        xDataUpdated = pdTRUE;
    }
    else
    {
        xDataUpdated = pdFALSE;
    }
 
    return xDataUpdated;
}
原文地址:https://www.cnblogs.com/FZLGYZ/p/13785656.html