1. 程式人生 > 實用技巧 >FreeRTOS --(14)佇列管理之概述

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 AndxQueueSendToFront()

向佇列中插入資料分兩組 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.\r\n" );
        }
 
    }
}

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!\r\n" );
        }
        /* 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.\r\n" );
        }
    } 
}

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.\r\n" );
        }
    }
}

接收端為

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!\r\n" );
        }
        /* 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.\r\n" );
        }
    } 
}

初始化程式碼為:

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\r\n", 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\r\n";
    /* 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\r\n";
    /* 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;
}