This program is very similar to the previous one, but this time we are passing complete structures to the queue instead of simple integers. In the receiving task, we create a structure of the same type to back up the incoming data.

code example:

#include "bsp.h"

#define SENDER_1   1    /* ID of sender to identify on the Queue message */
#define SENDER_2   2    /* ID of sender to identify on the Queue message */

typedef struct                              /* Creation of struct to send as a message */
{
    unsigned char ucValue;                  /* Varible to store the message of the sender */
    unsigned char ucSource;                 /* Variable to identify the Sender */
}_xData;

_xData xDataToSend1 = {101, SENDER_1};      /* Creation of variable struct to fill variables ( used as a message data ) */
_xData xDataToSend2 = {202, SENDER_2};      /* Creation of variable struct to fill variables ( used as a message data ) */

void vSenderTask( void *pvParameters );     /* Declare the sender task function */
void vReceiveTask( void *pvParameters );    /* Declare the receiver task function */

QueueHandle_t xQueue;                       /* Queue handler */

int main( void )
{
    HAL_Init();
 
    /*enable RTT and system view*/
    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );
 
    xQueue = xQueueCreate( 3, sizeof( _xData ) );                           /* Creation of the Queue */
 
    xTaskCreate(vSenderTask, "task1", 240, (void*)&xDataToSend1, 2, NULL);  /* Resister sender task1 with a priority of 2 */
    xTaskCreate(vSenderTask, "task2", 240, (void*)&xDataToSend2, 2, NULL);  /* Resister sender task2 with a priority of 2 */
    
    xTaskCreate(vReceiveTask, "task3", 240, NULL, 1, NULL);                 /* Register the receiver task with priority of 1 */
    
    vTaskStartScheduler();                                                  /* Execution of Kernel */
}

void vSenderTask( void *pvParameters )
{
    const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;   /* variable to store Ticks to wait in ms */

    for(;;)
    {
        xQueueSend( xQueue, pvParameters, xTicksToWait );       /* Send the parameter as a message to Queue */
        taskYIELD();                                            /* Change the context to other task task with the same priority */
    }
}

void vReceiveTask( void *pvParameters )
{
    _xData xReceivedStruct;                                      /* variale to store struct message received */
    const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;    /* variable to store Ticks to wait in ms */

    for(;;)
    {
        xQueueReceive( xQueue, &xReceivedStruct, xTicksToWait ); /* Wait to receive data */
 
        if( xReceivedStruct.ucSource == SENDER_1 )               /* Check in struct parameter if the sender is the SENDER_1 */
        {
            /* Execute only if message is from task sender 1 */   
            SEGGER_SYSVIEW_PrintfHost("Tarea Sender 1 = %d", xReceivedStruct.ucValue);   /* Print the message on Terminal */    
        }
        else                                                    
        {
            /* Execute only if message is from task sender 2 */
            SEGGER_SYSVIEW_PrintfHost("Tarea Sender 2 = %d", xReceivedStruct.ucValue);   /* Print the message on Terminal */
        }
    }
}

Another thing you can observe is that the priorities are inverted compared to the sending tasks and the receiving task. The "vTaskSender" tasks have higher priority and are executed first. They continuously load data into the queue until it is full. Once it's full, they will block for 100 ms (value in xTicksToWait). At this point, the receiving task runs, continuously reading the data from the queue until it's empty.

💡
Before proceeding: What would happen if the third parameter of the xQueueReceive function were zero, and how could we control that valid data has been read from the queue?

SystemView Output:

The terminal shows that the receiver task processes the messages and identifies which sender task sent the message.

Document

Timeline:

Document

Observe that at the init, Task sender 1 and Task sender 2 ( Task1 and Task2 ) were executed, Task1 sent a message to Queue, and after Task2 sent another message.
After, receiver task executes, reading a message from the Queue.

The table of Events shows in detail the behavior of the tasks previously explained.

Next, we can observe normal behavior with the task sender and task receiver.
This image shows that Task1 sends a message to the queue to be read by the task receiver (Task3), which reads a previous message from Task2 at the init of execution.

Now Task2 sends a message to the Queue, and Task3 Reads the message received by the last execution of Task1, saving the new message to the next reading.