FreeRTOS learning message queue

Posted by liamjw on Sat, 22 Jan 2022 20:49:29 +0100

Message queue

FreeRTOS learning warehouse address: https://gitee.com/killerp/free-rtos_-study

Message queue is the basic data structure of RTOS, which is used for data transmission between tasks and between tasks and interrupts.

When message queuing is not used, if you want to transfer data between two tasks, you must transfer it through global variables. In multi task systems, accessing global variables often requires users to protect resources, which makes programming troublesome.

Message queue encapsulates the access protection of shared data, and also adds the blocking waiting mechanism. So that users do not have to consider complex concurrent access when programming.

1, Queue structure

The message queue structure is defined as follows:

  • Message queue can be understood as a ring queue, connecting the first place of the queue through pcHead and pcTail.

  • pcWriteTo and pcReadFrom are the head and tail of the queue respectively (if the default FIFO is used)

  • The size of each message in the queue is fixed. A queue can hold several messages. The memory occupied by the queue is determined by the size and number of messages

  • The queue has two linked lists, which are used to store the blocked tasks due to sending / receiving messages.

  • The queue supports locking. When the queue is locked, the interrupt function cannot modify the above linked list to protect the data.

/*
 * Message queue queuing uses memory replication
 */
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    int8_t * pcHead;           /*< Points to the starting address of the queue memory */
    int8_t * pcWriteTo;        /*< Points to the next free address in the queue */

    union
    {
        QueuePointers_t xQueue;    //TODO when the structure is a queue  
        SemaphoreData_t xSemaphore; //When used as a semaphore
    } u;

    List_t xTasksWaitingToSend;             //Linked list: save tasks that are blocked due to sending semaphores (sorted by priority)
    List_t xTasksWaitingToReceive;          //Linked list: save tasks that are blocked due to received semaphores (sorted by priority)

    volatile UBaseType_t uxMessagesWaiting; //Number of messages in the queue
    UBaseType_t uxLength;                   //Maximum number of messages queued
    UBaseType_t uxItemSize;                 //Size of a message

    //Queue lock
    volatile int8_t cRxLock;                //The number of messages the task receives from the queue when the queue is locked
    volatile int8_t cTxLock;                //The number of messages the task sends to the queue when the queue is locked

    uint8_t ucStaticallyAllocated;      //Mark the memory allocation method of the queue


} xQUEUE;

The message queue can be simply abstracted into the following pictures:

At the same time, only one task or interrupt can modify the linked list of the queue. Yes, queue locks do not prevent interruptions in copying data to the queue.

Insert picture description here

2, Create queue

The process of creating a queue is similar to that of creating a task. You need to allocate memory for the queue structure and queue storage, and initialize the member variables of the queue structure.

Take dynamic memory allocation as an example: allocate memory first

    QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue;   //Point to the new queue structure
        size_t xQueueSizeInBytes;   //Total queue size (bytes)
        uint8_t * pucQueueStorage;  //Point to the starting address of the queue storage area

        //The length of the queue must be at least 1
        configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

        //Calculate the memory size used by the queue
        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
        /* Check for multiplication overflow */
        configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );

        /* Check for spillage */
        configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) >  xQueueSizeInBytes );

        //todo requests memory, pvPortMalloc byte alignment problem
        pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); 

        if( pxNewQueue != NULL )
        {
            //The queue structure and the actual memory area of the queue are in the same continuous memory, so pucqueueuestorage skips the Queue_t
            pucQueueStorage = ( uint8_t * ) pxNewQueue;
            pucQueueStorage += sizeof( Queue_t ); 

            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    pxNewQueue->ucStaticallyAllocated = pdFALSE;
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
			//Initialize queue structure
            prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
        }
        else
        {
            traceQUEUE_CREATE_FAILED( ucQueueType );
            mtCOVERAGE_TEST_MARKER();
        }

        return pxNewQueue;
    }

Initialize the member variables of the queue structure. The memory after initialization is about:

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,
                                   const UBaseType_t uxItemSize,
                                   uint8_t * pucQueueStorage,
                                   const uint8_t ucQueueType,
                                   Queue_t * pxNewQueue )
{
    //Remove compiler warning
    ( void ) ucQueueType;

    //Set the members of the queue structure

    //When the queue is used as a semaphore, uxItemSize is 0
    if( uxItemSize == ( UBaseType_t ) 0 )
    {

        //pcHead cannot be null. Because pcHead is null, it indicates a mutually exclusive semaphore, so it is set to a certain value
        pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
    }
    else
    {
        //As a queue, pcHead points to the storage area
        pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
    }
    //Set queue length
    pxNewQueue->uxLength = uxQueueLength;
    pxNewQueue->uxItemSize = uxItemSize;

    //Reset queue 
    ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

}

BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
                               BaseType_t xNewQueue )
{
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );
    
    taskENTER_CRITICAL();
    {
        //Reset the pointer to the queue
        pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); 
        pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
        pxQueue->pcWriteTo = pxQueue->pcHead;
        
        pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); 
        pxQueue->cRxLock = queueUNLOCKED;
        pxQueue->cTxLock = queueUNLOCKED;

        
        if( xNewQueue == pdFALSE )
        {
            //Not a new queue
            /* xTasksWaitingToSend Tasks can send messages, while xtasks waitingtoreceive continues to wait */
            if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
            {
                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                {
                    queueYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            //Initialize the linked list used by the queue
            vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
            vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
        }
    }
    taskEXIT_CRITICAL();

    return pdPASS;
}

3, Send message to queue

There are many functions for sending messages in RTOS, but they can't change. As long as you master the following function, other sending functions are variants of this function.

Send message in task

The task calls the send message function, in which the task will enter an endless loop. If the task can successfully send messages, it will exit the loop. Otherwise, the task needs to enter the block and wait for an idle position in the queue.

At the same time, the exclusive access of the current task to the queue is guaranteed by entering the critical area. The specific code logic is as follows:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    //xEntryTimeSet marks whether the blocking time has been set for the task
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut; //Blocking time structure
    Queue_t * const pxQueue = xQueue;

    //Check for some incorrect usage
    configASSERT( pxQueue );
    configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
    configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    //loop
    for( ; ; )
    {
        //Enter the critical area to operate the linked list
        taskENTER_CRITICAL();
        {

            //When the queue is not full or overwritten, messages can be written to the queue
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
            {
                traceQUEUE_SEND( pxQueue );
                
                    {
                        //Copies the queue item to the specified location on the queue
                        xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                        //If a task is waiting for a message
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            //Remove the task from the event linked list
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                //The removed tasks have higher priority and need task scheduling
                                queueYIELD_IF_USING_PREEMPTION();
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else if( xYieldRequired != pdFALSE )    //No task is waiting for a message, but task scheduling is required
                        {
                            queueYIELD_IF_USING_PREEMPTION();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }

                taskEXIT_CRITICAL();    //Message sending is completed and exit the critical area
                return pdPASS;
            }
            else    //The queue is full and cannot be joined. You need to delay or exit
            {
                //The task does not require delay to exit directly
                if( xTicksToWait == ( TickType_t ) 0 )
                {

                    taskEXIT_CRITICAL();

                    traceQUEUE_SEND_FAILED( pxQueue );
                    return errQUEUE_FULL;
                }
                else if( xEntryTimeSet == pdFALSE ) //Blocking not initialized
                {
                    
                    //Initialize the blocking structure to assist in calculating the blocking time
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                 
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        //At this point, the task failed to send a message successfully. The message needs to be blocked and waited

        //After exiting the critical area, other tasks or interrupts may preempt the current task (and then get messages from the queue, then the current task may send messages)
        taskEXIT_CRITICAL();
        
        //The xTasksWaitingToSend linked list that suspends the scheduler and prevents other tasks from accessing the queue
        vTaskSuspendAll();
        //Lock the queue to prevent interruption and modify the xTasksWaitingToSend linked list
        prvLockQueue( pxQueue );

        //Check whether the blocking time of the current task has arrived
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            //If the blocking time is not up and the queue is full, insert the current task into the WaitingToSend of the queue
            if( prvIsQueueFull( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_SEND( pxQueue );

                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );

                //Unlock queue (allow interrupt to modify linked list)
                prvUnlockQueue( pxQueue );

                //Restore the scheduler and generate task scheduling. The current task enters the blocking. It is awakened when the blocking time arrives or other tasks read the message, and then re-enter the current cycle
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
            }
            else
            {
                //The queue has a position and re enters the current cycle
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else
        {
            //Timeout expired and failed to send message to exit
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();

            traceQUEUE_SEND_FAILED( pxQueue );
            return errQUEUE_FULL;
        }
    } /*lint -restore */
}

FreeRTOS messages are implemented through memory replication. The process is as shown in the figure: first write the message from the write pointer, and then move the write pointer to the next free position.

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
                                      const void * pvItemToQueue,
                                      const BaseType_t xPosition )
{
    BaseType_t xReturn = pdFALSE;
    UBaseType_t uxMessagesWaiting;

    /* The current function must be called in a critical area */

    uxMessagesWaiting = pxQueue->uxMessagesWaiting;
    //As a semaphore
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
    {
        
            {
                //If it is a mutually exclusive semaphore, it means that the semaphore is released and the priority inversion needs to be cancelled
                if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                {
                    
                    xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
                    pxQueue->u.xSemaphore.xMutexHolder = NULL;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
       
    }
    //Send to the end of the queue as a message queue (writeto)
    else if( xPosition == queueSEND_TO_BACK )
    {
        //Memory copy
        ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
        //pcWriteTo add
        pxQueue->pcWriteTo += pxQueue->uxItemSize;                                                       
        //If pcWriteTo reaches the end of memory, it returns to the beginning of memory
        if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail )                                            
        {
            pxQueue->pcWriteTo = pxQueue->pcHead;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else  
    {
        //Copy to team leader (pcReadFrom)
        ( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
        pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;

        if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) 
        {
            pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        //If the write is overwritten, a message is overwritten, uxMessagesWaiting-1
        if( xPosition == queueOVERWRITE )
        {
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {

                --uxMessagesWaiting;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    //Team success
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

    return xReturn;
}

Send message in interrupt

Since blocking cannot be entered in an interrupt, a function is needed to send messages in an interrupt. The difference from the task is that the critical area protection and the function will exit directly when the transmission conditions are not met, and the delay waiting will not be entered.

At the same time, if the queue is locked during interruption, the linked list cannot be modified. This is because when the interrupt is triggered, the task may be accessing the queue linked list.

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
                                     const void * const pvItemToQueue,
                                     BaseType_t * const pxHigherPriorityTaskWoken,
                                     const BaseType_t xCopyPosition )
{
    BaseType_t xReturn;     //Return value: indicates whether task scheduling is required
    UBaseType_t uxSavedInterruptStatus; //Save interrupt status
    Queue_t * const pxQueue = xQueue;

    //Check queue
    configASSERT( pxQueue );
    configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
    configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );

    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    //Enter critical zone
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        //When the queue is not full or overwritten, messages can be written to the queue
        if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
        {
            const int8_t cTxLock = pxQueue->cTxLock;    //Get send lock 
            const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;

            traceQUEUE_SEND_FROM_ISR( pxQueue );

            /* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a
             *  semaphore or mutex.  That means prvCopyDataToQueue() cannot result
             *  in a task disinheriting a priority and prvCopyDataToQueue() can be
             *  called here even though the disinherit function does not check if
             *  the scheduler is suspended before accessing the ready lists. */
            //Copy data to queue
            ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

            //If the queue is not locked
            if( cTxLock == queueUNLOCKED )
            {
                    {
                        //If a task is waiting for data
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            //Remove task from waiting list
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                //Tasks marked with higher priority need task scheduling after unlocking and exiting interrupt
                                if( pxHigherPriorityTaskWoken != NULL )
                                {
                                    *pxHigherPriorityTaskWoken = pdTRUE;
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        ( void ) uxPreviousMessagesWaiting;
                    }

            }
            else
            {
                //The queue is locked. Increase the value of cTxLock to make the task of unlocking the queue understand how many messages failed to enter the queue successfully
                configASSERT( cTxLock != queueINT8_MAX );

                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }

            xReturn = pdPASS;
        }
        else    //The queue is full and cannot be blocked to exit directly
        {
            traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
            xReturn = errQUEUE_FULL;
        }
    }
    //Exit critical zone
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

4, Receive message

Receive message in task

The implementation of receiving a message is similar to sending a message. It also needs to exit in the loop if the message in the queue is successfully obtained, otherwise it will enter the block and wait for the message to arrive.

The code notes are as follows:

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

    BaseType_t xEntryTimeSet = pdFALSE;     //Mark whether to initialize the blocking time
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    configASSERT( ( pxQueue ) );

    configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );


    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif


    //loop
    for( ; ; )
    {
        taskENTER_CRITICAL();
        {
            //Gets the number of messages in the queue
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

            //If there is data in the queue
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {
                //Copy data to buffer, number of messages - 1
                prvCopyDataFromQueue( pxQueue, pvBuffer );
                traceQUEUE_RECEIVE( pxQueue );
                pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;

                //After the message leaves the queue, if there is a task waiting to send a message, resume the task to send it
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    //Remove tasks waiting to be sent
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        //The removed tasks have higher priority and preemption occurs
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else    //There are no tasks waiting to be sent
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                taskEXIT_CRITICAL();
                return pdPASS;
            }
            else    //Queue is empty
            {
                //Quit immediately without waiting
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE ) //If blocking time is not initialized
                {

                    //Set blocking time
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }

        //At this point, the current task cannot successfully get the message. It needs to enter the blocking wait

        
        taskEXIT_CRITICAL();
        //After exiting the critical area, other tasks or interrupts may preempt the current task (and then send a message to the queue, then the current task can successfully obtain the message)

        //Suspend the scheduler and lock the queue to prevent tasks from interrupting and modifying the linked list of the queue
        vTaskSuspendAll();
        prvLockQueue( pxQueue );


        //The task blocking time has not expired
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {

            //The queue is still empty and needs to wait
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                //Put the task on the waiting list 
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
                prvUnlockQueue( pxQueue );  //Unlock

                //Restore the scheduler and generate task scheduling. The current task enters the blocking. When the blocking time arrives or other tasks send messages, it will be awakened, and then re-enter the current cycle
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                //There are messages in the queue. Re-enter the current cycle
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else    //Blocking time arrival
        {

            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();
            //Check whether there are messages in the queue again. If there are messages, enter the current cycle, and if not, exit the wait
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    } /*lint -restore */
}

Copy messages to the queue. It should be noted here that when the message size of the queue is 0, the queue is a semaphore and does not need memory replication. This part will explain the semaphore in the next chapter.

When reading a message, first move the read pointer and then read it.

static void prvCopyDataFromQueue( Queue_t * const pxQueue,
                                  void * const pvBuffer )
{
    if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
    {
        //pcReadFrom moves forward one cell
        pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize;          

        //Whether it overflows and needs to return to the queue header
        if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) 
        {
            pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        //Memory copy
        ( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize ); 
    }
}


Receive message in interrupt

Similarly, when the message is read in the interrupt, when there is no message in the queue, it cannot enter the waiting and must exit immediately.

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void * const pvBuffer,
                                 BaseType_t * const pxHigherPriorityTaskWoken )
{
    BaseType_t xReturn;
    UBaseType_t uxSavedInterruptStatus;
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );
    configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );

    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    //Enter the critical area and save the interrupt state
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        //Get the number of messages
        const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

        //Check for messages
        if( uxMessagesWaiting > ( UBaseType_t ) 0 )
        {
            const int8_t cRxLock = pxQueue->cRxLock;    //Acquire receive lock

            traceQUEUE_RECEIVE_FROM_ISR( pxQueue );
            //The data is valid. Copy the data to the buffer
            prvCopyDataFromQueue( pxQueue, pvBuffer );
            pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;

            //The queue is unlocked. You can modify the queue linked list
            if( cRxLock == queueUNLOCKED )
            {
                //After the message leaves the queue, the queue has an idle position. If there is a task waiting to send a message, you need to restore the task
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    //Remove tasks waiting to be sent
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {

                        //The priority of the removed task is higher, and the task scheduling needs to be started after exiting the interrupt
                        if( pxHigherPriorityTaskWoken != NULL )
                        {
                            //Set return parameters
                            *pxHigherPriorityTaskWoken = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    else    
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else    //The queue is locked and the linked list cannot be operated
            {

                configASSERT( cRxLock != queueINT8_MAX );
                //Set cRxLock-1 so that the queue knows that a message is read when it is locked
                pxQueue->cRxLock = ( int8_t ) ( cRxLock + 1 );
            }
            //Successfully get message
            xReturn = pdPASS;
        }
        else    //If the queue is empty, return immediately
        {
            xReturn = pdFAIL;
            traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue );
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

Topics: queue source code message queue FreeRTOS