FreeRTOS: Queues

foreword

When writing project applications, it is often encountered that one task "communicates" with another task. When there is no operating system, global variables can solve this problem, but if all variables are used in the application using the operating system to Transferring information will involve the issue of "resource management", and global variables are not easy to maintain, and often in programs with complex logic, it is impossible to track who uses or changes global variables. FreeRTOS provides a mechanism called "queue" for this.

1. Introduction to Queue

Queues are prepared for communication between tasks and between tasks and interrupts. Messages can be transmitted between tasks and between tasks and between tasks and interrupts. Limited data items of fixed size can be stored in the queue. The data to be exchanged between tasks and tasks, tasks and interrupts is stored in queues, called queue items. The maximum number of data items that a queue can hold is called the length of the queue. When creating a queue, the size of the data items and the length of the queue are specified. Because the queue is used to transmit messages, it is also called a message queue. The semaphore in FreeRTOS is also implemented according to the queue!

1.1 Data storage

Usually, the queue adopts a first-in-first-out (FIFO) storage buffer mechanism, that is, when data is sent to the queue (also called enqueue), it is always sent to the end of the queue. , and when extracting data from the queue (also called dequeue) is extracted from the head of the queue. But last-in-first-out (LIFO) storage buffering can also be used, and the queue in FreeRTOS also provides a LIFO storage buffering mechanism.

After the data is sent to the queue, it will be stored in the queue, which means that the original value of the data stored in the queue is not a reference to the original data (that is, a pointer to the data). This transfer process is also called value transfer. Different from UCOS, the message queue of UCOS adopts reference transfer, and what is passed is the message pointer. But the disadvantage of this is that the flower message passed by reference must remain visible, that is, the content of the message must be valid. In this case, for example, the local variables of the function will be deleted at any time, but the advantage of passing by reference is also great. Obviously, the time to transfer data to the queue can be greatly reduced.
Another advantage of value passing is that after the data is sent to the queue, the buffer that originally stored the data can be deleted or overwritten, so that the buffer can be reused forever. However, in the case of network information transmission, it is often necessary to transmit a large amount of information, so there will inevitably be a lot of time spent in the process of message queue transmission. In this case, you can pass the pointer address of the group of data, then as long as you get the buffer After the address of the area, the data group headed by the address can be used.

1.2 Multitasking access

Any task can send messages to the queue, or extract messages from the queue.

1.3 Dequeue blocking

When a task tries to read a message from a queue, a blocking time can be specified. This blocking time is the time when the task is blocked when the message read from the queue is invalid. Dequeue is to read messages from the queue, and dequeue blocking is for the task of reading messages from the queue. For example, task A is used to process the data received by the serial port. After the serial port receives the data, it will put it into the queue Q, and task A reads the data from the queue Q. But if the queue Q is empty at this time, it means that there is no data yet. If task A comes to read at this time, it must not get anything, so what should I do? Task A now has three choices,
one: end the reading process directly without reading the data;
two: wait for a period of time, which is the so-called blocking time, and the data read in the queue will end during this period, otherwise, then After waiting for the blocking time to arrive, enter the ready list from the delay list;
3: Set the waiting time to the maximum value portMAX_DELAY, that is, if no data is read, it will enter the blocking state and wait until the data is received.
Which one to choose is determined by the blocking time, and the blocking time unit is the number of clock ticks.

1.4 Enqueue blocking

Joining the queue means sending a message to the queue and adding the message to the queue. Like dequeue blocking, when a task sends a message to the queue, the blocking time can also be set. For example, task B sends a message to the message queue Q, but the queue Q is full at this time, then the sending must fail. At this time, task B will encounter the same problem as task A above. The processing process of these two cases is similar, except that one is to send a message to queue Q, and the other is to read a message from queue Q.

1.5 Queue operation process diagram

The following pictures briefly demonstrate the process of entering and exiting the queue.

1.5.1 Create a queue

insert image description here
In the figure above, task A wants to send a message to task B, and this message is the value of the x variable. First create a queue, and specify the length of the queue and the length of each message. Here we create a queue with a length of 4, because the value of x is to be passed, and x is a variable of type int, so the length of each message is the length of type int, which is 4 bytes in STM32, that is, each Messages are 4 bytes.

1.5.2 Send the first message to the queue

insert image description here
In the figure above, the variable x value of task A is 10, and this value is sent to the message queue. At this time, the remaining length of the queue is 3. As mentioned earlier, sending messages to the queue is done by copying, so once the message is sent, the variable x can be used again and assigned other values.

1.5.3 Send a second message to the queue

insert image description here
In the figure above, task A sends another message to the queue, which is the new value of x, which is 20 here. At this time, the remaining length of the queue is 2.

1.5.4 Read messages from the queue

insert image description here
In the figure above, task B reads messages from the queue, and assigns the value of the read messages to y, so that y is equal to 10. After task B reads the message from the queue, it can choose to clear the message or not. When you choose to clear this message, other tasks or interrupts will not be able to get this message, and the remaining size of the queue will be increased by one to become 3. If it is not cleared, other tasks or interrupts can also get this message, and the remaining size of the queue is still 2.

2. Queue structure

There is a structure used to describe the queue, called Queue_t, this structure is in the file, this structure is in the file queue.c

typedef struct QueueDefinition 
{
    
    
    //指向队列存储区开始地址
    int8_t * pcHead;           
    //指向存储区中下一个空闲区域
    int8_t * pcWriteTo;        
    union
    {
    
    
        QueuePointers_t xQueue;     
        SemaphoreData_t xSemaphore; 
    } u;
    //等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上。
    List_t xTasksWaitingToSend;             
    //等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上。
    List_t xTasksWaitingToReceive;          

    //队列中当前队列项数量,也就是消息数
    volatile UBaseType_t uxMessagesWaiting;
    //创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量
    UBaseType_t uxLength;                   
    //创建队列时指定的每个队列项(消息)最大长度,单位:字节
    UBaseType_t uxItemSize;                 

    //当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
    volatile int8_t cRxLock;               
    //当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
    volatile int8_t cTxLock;                

    //如果使用静态存储的话此字段设置为 pdTURE。
    #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; 
    #endif

    //队列集相关宏
    #if ( configUSE_QUEUE_SETS == 1 )
        struct QueueDefinition * pxQueueSetContainer;
    #endif

    //跟踪调试相关宏
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif
} xQUEUE;

3. Queue creation

3.1 Create function

This function is also used to create a queue, but the static method to create a queue requires the user to allocate the required memory. This function is essentially a macro line allocation. This macro finally calls the function xQueueGenericCreateStatic(). The function prototype is as follows:

xQueueCreateStatic()
xQueueCreate()

These two functions are essentially macros, and the functions that actually complete the queue creation are xQueueGenericCreate() and xQueueGenericCreateStatic().

3.2 Function xQueueCreateStatic()

This function uses a static method to create a queue, and the memory required by the queue is allocated by the user.

QueueHandle_t xQueueCreateStatic(
								 UBaseType_t       uxQueueLength,
                                 UBaseType_t       uxItemSiz,
                                 uint8_t*          pucQueueStorageBuffer,
                                 StaticQueue_t*    pxQueueBuffer
							     )

insert image description here
insert image description here

3.3 Function xQueueCreate()

This function is essentially a macro, which is used to dynamically create a queue. This function is essentially a macro, which is used to dynamically create a queue. This macro finally calls the function xQueueGenericCreate(). The function prototype is as follows:

QueueHandle_t xQueueCreate(UBaseType_t       uxQueueLength,
                           UBaseType_t       uxItemSiz,)

insert image description here

3.4 Function xQueueGenericCreateStatic()

QueueHandle_t xQueueGenericCreateStatic( 
										const UBaseType_t uxQueueLength, 
										const UBaseType_t uxItemSize, 
										uint8_t *pucQueueStorage, 
										StaticQueue_t *pxStaticQueue, 
										const uint8_t ucQueueType 
										)
	

insert image description here

3.5 Function xQueueGenericCreate()

QueueHandle_t xQueueGenericCreate( 
									const UBaseType_t uxQueueLength, 
									const UBaseType_t uxItemSize, 
									const uint8_t ucQueueType 
								)

insert image description here

4. Send a message to the queue

4.1 Function Prototype

After the queue is created, you can send messages to the queue. FreeRTOS provides 8 API functions for sending messages to the queue, as shown in the following table:
insert image description here

4.2 Functions xQueueSend(), xQueueSendToBack() and xQueueSendToFront()

These three functions are used to send messages to the queue. These three functions are essentially macros. The functions xQueueSend() and xQueueSendToBack() are the same, and they all enter the queue backwards, inserting new messages into the back of the queue. The function xQueueSendToToFront() enqueues forward, that is, inserts new messages at the front of the queue. However! These three functions end up calling the same function: xQueueGenericSend(). These three functions can only be used in task functions and cannot be used in interrupt service functions. Interrupt service functions have dedicated functions, and they end with "FromISR". The prototypes of these three functions are as follows:

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

Parameters:
xQueue: Queue handle, indicating which queue to send data to. After the queue is successfully created, the
queue handle of this queue will be returned.
**pvItemToQueue:** points to the message to be sent, and the message will be copied to the queue when sending.
xTicksToWait: Blocking time, this parameter indicates the maximum time for the task to enter the blocking state and wait for the queue to be free when the queue is full. If it is 0, it will return immediately when the queue is full; if it is portMAX_DELAY,
it will wait until there is an idle queue item in the queue, that is, dead wait, but the macro INCLUDE_vTaskSuspend must be 1.

Return value:
pdPASS: Send message to queue successfully!
errQUEUE_FULL: The queue is full and the message cannot be sent.

4.2 Function xQueueOverwrite()

This function is also used to send data to the queue. When the queue is full, the old data will be overwritten, regardless of whether the old data has been taken away by other tasks or interrupts. This function is often used to send messages to those queues with a length of 1. This function is also a macro, and the function xQueueGenericSend() is finally called. The function prototype is as follows:

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

Parameters:
xQueue: Queue handle, indicating which queue to send data to. After the queue is successfully created, the queue handle of this queue will be returned.
**pvItemToQueue:** points to the message to be sent, which will be copied to the queue when sending.

Return value:
pdPASS: If the message is sent to the queue successfully, this function will only return pdPASS! Because this function does not care whether the queue is full or not during the execution of this function, if it is full, I will overwrite the old data, and it will definitely succeed.

4.3 Function xQueueGenericSend()

This function is the real work. All the task-level enqueue functions mentioned above are ultimately called by this function. This function is also what we will focus on later. Let’s take a look at the function prototype first:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
							  const void * const pvItemToQueue, 
							  TickType_t xTicksToWait, 
							  const BaseType_t xCopyPosition )

Parameters:
xQueue: Queue handle, indicating which queue to send data to. After the queue is successfully created, the queue handle of this queue will be returned.
**pvItemToQueue:** points to the message to be sent, which will be copied to the queue during the sending process.
xTicksToWait: Blocking time.
xCopyPosition: There are three ways to join the queue:
queueSEND_TO_BACK: queue backward
queueSEND_TO_FRONT: queue forward
queueOVERWRITE: overwrite the queue.

The enqueue API function explained above uses this parameter to determine which enqueue method to use.

Return value:
pdTRUE: Send message to queue successfully!
errQUEUE_FULL: The queue is full and the message failed to be sent

4.4 Functions xQueueSendFromISR(), xQueueSendToBackFromISR(), xQueueSendToFrontFromISR()

These three functions also send messages to the queue, and these three functions are used in the interrupt service function. The essence of these three functions is also a macro, among which the functions xQueueSendFromISR () and xQueueSendToBackFromISR () are the same, they all enter the queue backwards, that is, insert new messages to the back of the queue. The function xQueueSendToFrontFromISR () enqueues forward, that is, inserts new messages to the front of the queue. These three functions also call the same function xQueueGenericSendFromISR (). The prototypes of these three functions are as follows:

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
							 const void * pvItemToQueue,
							 BaseType_t * pxHigherPriorityTaskWoken);
							 
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
								   const void * pvItemToQueue,
								   BaseType_t * pxHigherPriorityTaskWoken);
								   
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
								 	const void * pvItemToQueue,
								 	BaseType_t * pxHigherPriorityTaskWoken);

Parameters:
xQueue: Queue handle, indicating which queue to send data to. After the queue is successfully created, the queue handle of this queue will be returned.
**pvItemToQueue:** points to the message to be sent, which will be copied to the queue when sending.
pxHigherPriorityTaskWoken: Mark whether to switch tasks after exiting this function. The value of this variable is set by these three functions. The user does not need to set it. The user only needs to provide a variable to save this value. When this value is pdTRUE, a task switch must be performed before exiting the interrupt service function.

Return value:
pdTRUE: Send message to queue successfully!
errQUEUE_FULL: The queue is full and the message cannot be sent.

4.5 Function xQueueOverwriteFromISR()

This function is the interrupt-level version of xQueueOverwrite(). It is used in the interrupt service function to automatically overwrite the old data when the queue is full. This function is also a macro, and the function xQueueGenericSendFromISR() is actually called. The prototype of this function is as follows :

BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
								  const void * pvItemToQueue,
								  BaseType_t * pxHigherPriorityTaskWoken);

The parameters and return value of this function are the same as the above three functions.

4.6 Function xQueueGenericSendFromISR()

As mentioned above, the four interrupt-level enqueuing functions are finally called the function xQueueGenericSendFromISR(). This is the real master of work, and it is also a function that we will explain in detail below. First, let’s take a look at the prototype of this function, as follows:

BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,
									const void* pvItemToQueue,
									BaseType_t* pxHigherPriorityTaskWoken,
									BaseType_t xCopyPosition);

Parameters:
xQueue: Queue handle, indicating which queue to send data to. After the queue is successfully created, the queue handle of this queue will be returned.
**pvItemToQueue:** points to the message to be sent, which will be copied to the queue during the sending process.
pxHigherPriorityTaskWoken: Mark whether to switch tasks after exiting this function. The value of this variable is set by these three functions. The user does not need to set it. The user only needs to provide a variable to save this value. When this value is pdTRUE, a task switch must be performed before exiting the interrupt service function.

xCopyPosition: There are three ways to join the queue:
queueSEND_TO_BACK: queue backward
queueSEND_TO_FRONT: queue forward
queueOVERWRITE: overwrite the queue.

Return value:
pdTRUE: Send message to queue successfully!
errQUEUE_FULL: The queue is full and the message cannot be sent.

4.7 Task-level general enqueue function

Regardless of whether it is enqueueing backwards, enqueueing forward, or overriding the enqueue, the final enqueue function xQueueGenericSend() is called. This function is defined in the file queue.c. The reduced function code is as follows:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
							  const void * const pvItemToQueue, 
							  TickType_t xTicksToWait, 
							  const BaseType_t xCopyPosition )
{
    
    
	BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
	TimeOut_t xTimeOut;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	for( ;; )
	{
    
    
		taskENTER_CRITICAL(); //进入临界区
		{
    
    
			//查询队列现在是否还有剩余存储空间,如果采用覆写方式入队的话那就不用在
			//乎队列是不是满的啦。
			if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
				( xCopyPosition == queueOVERWRITE ) )
			{
    
    
				traceQUEUE_SEND( pxQueue );
				xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue,\ (2)
				 xCopyPosition );
				/**************************************************************************/
				/**************************省略掉与队列集相关代码**************************/
				/**************************************************************************/
				{
    
    
				//检查是否有任务由于等待消息而进入阻塞态
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ==\(3)
				pdFALSE )
				{
    
    
					if( xTaskRemoveFromEventList( &( pxQueue->\ (4)
						xTasksWaitingToReceive ) ) != pdFALSE )
					{
    
    
						//解除阻塞态的任务优先级最高,因此要进行一次任务切换
						queueYIELD_IF_USING_PREEMPTION(); (5)
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else if( xYieldRequired != pdFALSE )
				{
    
    
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL();
			return pdPASS; (6)
		}
		else
		{
    
    
			if( xTicksToWait == ( TickType_t ) 0 ) (7)
			{
    
    
				//队列是满的,并且没有设置阻塞时间的话就直接返回
				taskEXIT_CRITICAL();
				traceQUEUE_SEND_FAILED( pxQueue );
				return errQUEUE_FULL; (8)
			}
			else if( xEntryTimeSet == pdFALSE ) (9)
			{
    
    
				//队列是满的并且指定了任务阻塞时间的话就初始化时间结构体
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}
			else
			{
    
    
				//时间结构体已经初始化过了,
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
	taskEXIT_CRITICAL(); //退出临界区
	vTaskSuspendAll(); (10)
	prvLockQueue( pxQueue ); (11)
	//更新时间壮态,检查是否有超时产生
	if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (12)
	{
    
    
		if( prvIsQueueFull( pxQueue ) != pdFALSE ) (13)
		{
    
    
			traceBLOCKING_ON_QUEUE_SEND( pxQueue );
			vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), \ (14)
			xTicksToWait );
			prvUnlockQueue( pxQueue ); (15)
			if( xTaskResumeAll() == pdFALSE ) (16)
			{
    
    
				portYIELD_WITHIN_API();
			}
		}
		else
		{
    
    
			//重试一次
			prvUnlockQueue( pxQueue ); (17)
			( void ) xTaskResumeAll();
		}
	}
	else
	{
    
    
		//超时产生
		prvUnlockQueue( pxQueue ); (18)
		( void ) xTaskResumeAll();
		traceQUEUE_SEND_FAILED( pxQueue );
		return errQUEUE_FULL; (19)
	}
	}
}

(1) To send data to the queue, you must first check whether the queue is full. If it is full, it must not be sent. When the queue is not full or overwritten into the queue, the message can be enqueued.

(2) Call the function prvCopyDataToQueue() to copy the message to the queue. As mentioned earlier, enqueueing is divided into backward enqueue, forward enqueue, and overwrite enqueue. Their specific implementation is completed in the function prvCopyDataToQueue(). If queueSEND_TO_BACK is selected, the message will be copied to the queue item pointed to by the queue structure member pcWriteTo. After the copy is successful, pcWriteTo will increase uxItemSize bytes to point to the next queue item. When selecting queueSEND_TO_FRONT or queueOVERWRITE, the message is copied to the queue item pointed to by u.pcReadFrom, and the position of u.pcReadFrom needs to be adjusted as well. When a message is written to the queue, the member uxMessagesWaiting that counts the current number of messages in the queue will be increased by one, but if you choose to overwrite the queue OVERWRITE, uxMessagesWaiting will be reduced by one, so that one minus and one plus is equivalent to the current number of messages in the queue has not changed .

(3) Check whether any task is blocked due to the request queue message, and the blocked task will be hung on the
xTasksWaitingToReceive list of the queue.

(4) A task is blocked due to a request message, because a message has been sent to the queue in (2), so call the function xTaskRemoveFromEventList() to remove the blocked task from the list xTasksWaitingToReceive, and remove this task Added to the ready list, if the scheduler is locked, these tasks will be hung on the list xPendingReadyList. If the priority of the unblocked task is higher than the priority of the currently running task, a task switch is required. When the return value of the function xTaskRemoveFromEventList() is pdTRUE, it is necessary to switch tasks.

(5) Perform task switching.

(6) Return pdPASS, marking the success of joining the team.

(7), (2) to (6) are all very ideal effects, that is, the message queue is not full, and there is no obstacle to entering the queue. But what about when the queue is full? First judge whether the set blocking time is 0, if it is 0, it means there is no blocking time.

(8) From (7), it is known that the blocking time is 0, then directly return errQUEUE_FULL, and mark that the queue is full.

(9) If the blocking time is not 0 and the time structure has not been initialized, initialize the timeout structure variable once, and call the function vTaskSetTimeOutState() to complete the initialization of the timeout structure variable xTimeOut. In fact, it is to record the value xTickCount of the current system clock tick counter and the number of overflows xNumOfOverflows.

(10), the task scheduler is locked, and the code execution here shows that the current situation is that the queue is full, and a non-zero blocking time is set. Then the next step is to take corresponding measures for the task, such as adding the task to the
xTasksWaitingToSend list of the queue.

(11) Calling the function prvLockQueue() to lock the queue is actually setting the member variables cRxLock and
cTxLock in the queue to queueLOCKED_UNMODIFIED.

(12) Call the function xTaskCheckForTimeOut() to update the timeout structure variable xTimeOut, and check whether the blocking time is up.

(13), the blocking time has not yet arrived, then check whether the queue is still full.

(14), after (12) and (13), it is found that the blocking time has not arrived, and the queue is still full, then call the function vTaskPlaceOnEventList() to add the task to the xTasksWaitingToSend list and the delay list of the queue, and set The task is removed from the ready list. Notice! If the blocking time is portMAX_DELAY and the macro INCLUDE_vTaskSuspend is 1, the function vTaskPlaceOnEventList() will add the task to the list xSuspendedTaskList.

(15), the operation is completed, call the function prvUnlockQueue () to unlock the queue.

(16) Call the function xTaskResumeAll() to restore the task scheduler

(17), the blocking time has not yet arrived, but the queue now has idle queue items, so just try again.

(18), compared to step (12), the blocking time is up! Then tasks don't need to be added to those lists, so unlock the queue and restore the task scheduler.

(19), return errQUEUE_FULL, indicating that the queue is full.

4.8 Interrupt-level general enqueue function

After talking about the task-level enqueue function, let's take a look at the interrupt-level enqueue function xQueueGenericSendFromISR(). Other interrupt-level enqueue functions are implemented by this function. The interrupt-level enqueue function is similar to the task-level enqueue function, and the function code is as follows:

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, 
									 const void * const pvItemToQueue, 
									 BaseType_t * const pxHigherPriorityTaskWoken, 
									 const BaseType_t xCopyPosition )
{
    
    
	BaseType_t xReturn;
	UBaseType_t uxSavedInterruptStatus;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
    
    
		if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
	 		( xCopyPosition == queueOVERWRITE ) )
		{
    
    
			const int8_t cTxLock = pxQueue->cTxLock; (2)
			traceQUEUE_SEND_FROM_ISR( pxQueue );
			( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (3)			
			{
    
    
				//队列上锁的时候就不能操作事件列表,队列解锁的时候会补上这些操作的。
				if( cTxLock == queueUNLOCKED ) (4)
				{
    
    
					/**************************************************************************/
					/**************************省略掉与队列集相关代码**************************/
					/**************************************************************************/
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (5)
						pdFALSE )
					{
    
    
						if( xTaskRemoveFromEventList( &( pxQueue->\ (6)
							xTasksWaitingToReceive ) ) != pdFALSE )
						{
    
    
							//刚刚从事件列表中移除的任务对应的任务优先级更高,所以
							标记要进行任务切换
							if( pxHigherPriorityTaskWoken != NULL )
							{
    
    
								*pxHigherPriorityTaskWoken = pdTRUE; (7)
							}
							else
							{
    
    
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
    
    
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
				}
			}
			else
			{
    
    
				//cTxLock 加一,这样就知道在队列上锁期间向队列中发送了数据
				pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); (8)
			}
		xReturn = pdPASS; (9)
		}
		else
		{
    
    
			traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
			xReturn = errQUEUE_FULL; (10)
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
	return xReturn;
}

(1) The queue is not full or the overriding method of enqueueing is adopted, which is the most ideal state.

(2) Read the member variable xTxLock of the queue to determine whether the queue is locked.

(3) Copy the data to the queue.

(4) The queue is locked. For example, the task-level enqueue function will lock the queue when operating the list in the queue.

(5) Determine whether the queue list xTasksWaitingToReceive is empty, if it is not empty, it means that a task is blocked when requesting a message.

(6). Remove the corresponding task from the list xTasksWaitingToReceive. The process is the same as the task-level enqueue function.

(7) If the priority of the task just removed from the list xTasksWaitingToReceive is higher than the priority of the current task, then mark pxHigherPriorityTaskWoken as pdTRUE, indicating that a task switch is required. If you want to switch tasks, you need to switch tasks after exiting this function and before exiting the interrupt service function.

(8) If the queue is locked, add one to the queue member variable cTxLock, indicating that an enqueue operation has been performed, and it will be processed accordingly when the queue is unlocked (prvUnlockQueue()).

(9) Return pdPASS, indicating that the queue entry is complete.

(10) If the queue is full, return errQUEUE_FULL directly, indicating that the queue is full.

5. Queue lock and unlock

When explaining the task-level general enqueue function and the interrupt-level general enqueue function above, the lock and unlock of the queue are mentioned. The lock and unlock of the queue are two API functions: prvLockQueue() and prvUnlockQueue().
Let's first look at the queue locking function prvLockQueue(). This function is essentially a macro and is defined as follows:

#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{
      
       \
	if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
	{
      
       \
		( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;\
	} \
	if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
	{
      
       \
		( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;\
	} \
} \
taskEXIT_CRITICAL()

The prvLockQueue() function is very simple, just set the member variables cRxLock and cTxLock in the queue to
queueLOCKED_UNMODIFIED.

Let's take a look at the unlocking function prvUnlockQueue() of the queue. The function is as follows:

static void prvUnlockQueue( Queue_t * const pxQueue )
{
    
    
	//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
	//上锁以后队列项是可以加入或者移除队列的,但是相应的列表不会更新。
	taskENTER_CRITICAL();
	{
    
    
		//处理 cTxLock。
		int8_t cTxLock = pxQueue->cTxLock;
		while( cTxLock > queueLOCKED_UNMODIFIED ) (1)
		{
    
    
			/**************************************************************************/
			/**************************省略掉与队列集相关代码**************************/
			/**************************************************************************/
			{
    
    
				//将任务从事件列表中移除
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (2)
				pdFALSE )
				{
    
    
					if( xTaskRemoveFromEventList( &( pxQueue->\ (3)
					xTasksWaitingToReceive ) ) != pdFALSE )
					{
    
    
						//从列表中移除的任务优先级比当前任务的优先级高,因此要
						//进行任务切换。
						vTaskMissedYield(); (4)
					}
					else
					{
    
    	
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
    
    
					break;
				}
			}
			--cTxLock; (5)
		}
		pxQueue->cTxLock = queueUNLOCKED; (6)
	}
	taskEXIT_CRITICAL();
	//处理 cRxLock。
	taskENTER_CRITICAL();
	{
    
    
		int8_t cRxLock = pxQueue->cRxLock;
		while( cRxLock > queueLOCKED_UNMODIFIED ) (7)
		{
    
    
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
    
    
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) !=\
				pdFALSE )
				{
    
    
					vTaskMissedYield();
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
				--cRxLock;
			}
			else
			{
    
    
				break;
			}
		}
		pxQueue->cRxLock = queueUNLOCKED; 
	}
	taskEXIT_CRITICAL();
}

(1) Determine whether an interrupt has sent a message to the queue. As mentioned in the previous section when explaining the interrupt-level general enqueue function, if the queue is locked, then the enqueue counter cTxLock will be added to the enqueue counter cTxLock after the message is successfully sent to the queue. .

(2) Determine whether the list xTasksWaitingToReceive is empty, and if not, remove the corresponding task from the list.

(3) Remove the task from the list xTasksWaitingToReceive.

(4) If the priority of the task just removed from the list xTasksWaitingToReceive is higher than the priority of the current task, it is necessary to mark that task switching is required. Here the function vTaskMissedYield() is called to complete this task. The function vTaskMissedYield() simply sets the global variable xYieldPending to pdTRUE. So where is the actual task switching done? In the clock tick processing function xTaskIncrementTick(), this function will judge the value of xYieldPending to decide whether to switch tasks

(5) Decrease cTxLock by one each time one is processed, until all are processed.

(6) After processing, mark cTxLock as queueUNLOCKED, that is to say, cTxLock is not locked.

(7) After processing cTxLock, xRxLock will be processed next, and the processing process is very similar to xTxLock

6. Read messages from the queue

If there is a queue, there will be a queue, and the queue is to get the queue item (message) from the queue. The queue function in FreeRTOS is shown in the following table:
insert image description here

6.1 Function xQueueReceive()

This function is used to read a (request) message from the queue in a task. After the read is successful, the data in the queue will be deleted. The essence of this function is a macro, and the function actually executed is xQueueGenericReceive(). This function uses a copy method when reading messages, so the user needs to provide an array or buffer to save the read data, and the length of the read data is each queue item set when creating the queue The length of the function prototype is as follows:

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

Parameters:
xQueue: Queue handle, which indicates which queue data to read, and will return the queue handle of this queue after the queue is successfully created.
pvBuffer: The buffer for saving data, the read data will be copied to this buffer during the process of reading the queue.
xTicksToWait: Blocking time, this parameter indicates the maximum time for the task to enter the blocking state and wait for the queue to have data when the queue is empty. If it is 0, it will return immediately when the queue is empty; if it is portMAX_DELAY,
it will wait until there is data in the queue, that is, waiting dead, but the macro
INCLUDE_vTaskSuspend must be 1.

Return value:
pdTRUE: read data from the queue successfully.
pdFALSE: Failed to read data from the queue.

6.2 Function xQueuePeek()

This function is used to read a (request) message from the queue and can only be used in tasks! This function will not delete the message after it is successfully read
. This function is a macro, and the function actually executed is xQueueGenericReceive(). This function uses a copy method when reading messages, so the user needs to provide an array or buffer to save the read data, and the length of the read data is each queue item set when creating the queue The length of the function prototype is as follows:

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

Parameters:
xQueue: Queue handle, which indicates which queue data to read, and will return the queue handle of this queue after the queue is successfully created.
pvBuffer: The buffer for saving data, the read data will be copied to this buffer during the process of reading the queue.
xTicksToWait: Blocking time, this parameter indicates the maximum time for the task to enter the blocking state and wait for the queue to have data when the queue is empty. If it is 0, it will return immediately when the queue is empty; if it is portMAX_DELAY,
it will wait until there is data in the queue, that is, waiting dead, but the macro INCLUDE_vTaskSuspend must be 1.

Return value:
pdTRUE: read data from the queue successfully.
pdFALSE: Failed to read data from the queue.

6.3 Function xQueueGenericReceive()

Whether it is the function xQueueReceive() or xQueuePeek(), it is the function xQueueGenericReceive() that is called in the end. This function is the real thing. The function prototype is as follows:

BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
								void* pvBuffer,
								TickType_t xTicksToWait
								BaseType_t xJustPeek)

Parameters:
xQueue: Queue handle, which indicates which queue data to read, and will return the queue handle of this queue after the queue is successfully created.
pvBuffer: The buffer for saving data, the read data will be copied to this buffer during the process of reading the queue.
xTicksToWait: Blocking time, this parameter indicates the maximum time for the task to enter the blocking state and wait for the queue to have data when the queue is empty. If it is 0, it will return immediately when the queue is empty; if it is portMAX_DELAY,
it will wait until there is data in the queue, that is, waiting dead, but the macro
INCLUDE_vTaskSuspend must be 1.
xJustPeek: Marks whether to delete the queue item after the read is successful. When it is pdTRUE, it does not need to be deleted, that is to say, the queue item obtained by calling the function xQueueReceive() later is the same. When it is
pdFALSE, the queue entry will be deleted.

Return value:
pdTRUE: read data from the queue successfully.
pdFALSE: Failed to read data from the queue.

6.4 Function xQueueReceiveFromISR()

This function is the interrupt version of xQueueReceive(), which is used to read (request) a message from the queue in the interrupt service function, and the data in the queue will be deleted after the read is successful. This function uses a copy method when reading messages, so the user needs to provide an array or buffer to save the read data. The length of the read data is each queue item set when creating the queue The length of the function prototype is as follows:

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
								void* pvBuffer,
								BaseType_t * pxTaskWoken);

Parameters:
xQueue: Queue handle, which indicates which queue data to read, and will return the queue handle of this queue after the queue is successfully created.
pvBuffer: The buffer for saving data, the read data will be copied to this buffer during the process of reading the queue.
pxTaskWoken: Mark whether to switch tasks after exiting this function. The value of this variable is set by the function, and the user does not need to set it. The user only needs to provide a variable to save this value. When this value
is pdTRUE, a task switch must be performed before exiting the interrupt service function.

Return value:
pdTRUE: read data from the queue successfully.
pdFALSE: Failed to read data from the queue.

6.5 Function xQueuePeekFromISR()

This function is an interrupted version of xQueuePeek(). This function will not delete the message after it is successfully read. The prototype of this function is as follows:

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
							 void * pvBuffer)

Parameters:
xQueue: Queue handle, which indicates which queue data to read, and will return the queue handle of this queue after the queue is successfully created.
pvBuffer: The buffer for saving data, the read data will be copied to this buffer during the process of reading the queue.

Return value:
pdTRUE: read data from the queue successfully.
pdFALSE: Failed to read data from the queue.

7. Queue program example

7.1 Example Design Requirements

This example designs three tasks: start_task, task1_task, Keyprocess_task The functions of these three tasks are as follows: The functions of these three tasks are as follows:
start_task: used to create others: used to create other 2 tasks.
task1_task : Read the value of the key, and then send it to the queue: Read the value of the key, and then send it to the queue Key_Queue, and check the remaining capacity of the queue, and check the remaining capacity of the queue and other information.
Keyprocess_task : key processing task, read the message in the queue Key_Queue, and do corresponding processing according to different message values.
The instance needs three keys KEY_UP, KEY2 and KEY0, and different keys correspond to different key values. The task task1_task will send these values ​​to the queue Key_Queue.
Two queues, Key_Queue and Message_Queue, are created in the example. The queue Key_Queue is used to pass key values, and the queue Message_Queue is used to pass messages sent from the serial port.
The example also needs two interrupts, one is the serial port 1 receiving interrupt, and the other is the timer 2 interrupt. Their functions are as follows: Serial port 1 receiving interrupt: receive the data sent by the serial port, and send the received data to the queue Message_Queue. Timer 2 interrupt: The timing period is set to 500ms, and the message in the queue Message_Queue is read in the timing interrupt and displayed on the LCD.

7.2 Example code

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "beep.h"
#include "string.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
 
//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		256  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
 
//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#define TASK1_STK_SIZE 		256  
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
 
//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小	
#define KEYPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);
 
 
//按键消息队列的数量
#define KEYMSG_Q_NUM    1  		//按键消息队列的数量  
#define MESSAGE_Q_NUM   4   	//发送数据的消息队列的数量 
QueueHandle_t Key_Queue;   		//按键值消息队列句柄
QueueHandle_t Message_Queue;	//信息队列句柄
 
//LCD刷屏时使用的颜色
int lcd_discolor[14]={
    
    	WHITE, BLACK, BLUE,  BRED,      
						GRED,  GBLUE, RED,   MAGENTA,       	 
						GREEN, CYAN,  YELLOW,BROWN, 			
						BRRED, GRAY };
 
//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{
    
    
	LCD_Fill(5,230,110,245,WHITE);					//先清除显示区域
	LCD_ShowString(5,230,100,16,16,str);
}
 
//加载主界面
void freertos_load_main_ui(void)
{
    
    
	POINT_COLOR = RED;
	LCD_ShowString(10,10,200,16,16,"ATK STM32F103/407");	
	LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");
	LCD_ShowString(10,50,200,16,16,"Message Queue");
	LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY0:Refresh LCD");
	LCD_ShowString(10,90,200,16,16,"KEY1:SendMsg KEY2:BEEP");
	
	POINT_COLOR = BLACK;
	LCD_DrawLine(0,107,239,107);		//画线
	LCD_DrawLine(119,107,119,319);		//画线
	LCD_DrawRectangle(125,110,234,314);	//画矩形
	POINT_COLOR = RED;
	LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");
	LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");
	LCD_ShowString(0,210,100,16,16,"DATA_Msg:");
	POINT_COLOR = BLUE;
}
 
//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{
    
    
    u8 *p;
	u8 msgq_remain_size;	//消息队列剩余大小
    u8 msgq_total_size;     //消息队列总大小
    
    taskENTER_CRITICAL();   //进入临界区
    msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小
    msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。
	p=mymalloc(SRAMIN,20);	//申请内存
	sprintf((char*)p,"Total Size:%d",msgq_total_size);	//显示DATA_Msg消息队列总的大小
	LCD_ShowString(10,150,100,16,16,p);
	sprintf((char*)p,"Remain Size:%d",msgq_remain_size);	//显示DATA_Msg剩余大小
	LCD_ShowString(10,190,100,16,16,p);
	myfree(SRAMIN,p);		//释放内存
    taskEXIT_CRITICAL();    //退出临界区
}
 
int main(void)
{
    
     
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	delay_init(168);					//初始化延时函数
	uart_init(115200);     				//初始化串口
	LED_Init();		        			//初始化LED端口
	KEY_Init();							//初始化按键
	BEEP_Init();						//初始化蜂鸣器
	LCD_Init();							//初始化LCD
	TIM9_Int_Init(5000,16800-1);		//初始化定时器9,周期500ms
	my_mem_init(SRAMIN);            	//初始化内部内存池
    freertos_load_main_ui();        	//加载主UI
	
	//创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}
 
//开始任务任务函数
void start_task(void *pvParameters)
{
    
    
    taskENTER_CRITICAL();           //进入临界区
	
	//创建消息队列
    Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8));        //创建消息Key_Queue
    Message_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度
	
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,             
                (const char*    )"task1_task",           
                (uint16_t       )TASK1_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )TASK1_TASK_PRIO,        
                (TaskHandle_t*  )&Task1Task_Handler);   
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )Keyprocess_task,     
                (const char*    )"keyprocess_task",   
                (uint16_t       )KEYPROCESS_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )KEYPROCESS_TASK_PRIO,
                (TaskHandle_t*  )&Keyprocess_Handler); 
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}
 
//task1任务函数
void task1_task(void *pvParameters)
{
    
    
	u8 key,i=0;
    BaseType_t err;
	while(1)
	{
    
    
		key=KEY_Scan(0);            	//扫描按键
        if((Key_Queue!=NULL)&&(key))   	//消息队列Key_Queue创建成功,并且按键被按下
        {
    
    
            err=xQueueSend(Key_Queue,&key,10);
            if(err==errQUEUE_FULL)   	//发送按键值
            {
    
    
                printf("队列Key_Queue已满,数据发送失败!\r\n");
            }
        }
        i++;
        if(i%10==0) check_msg_queue();//检Message_Queue队列的容量
        if(i==50)
        {
    
    
            i=0;
            LED0=!LED0;
        }
        vTaskDelay(10);                           //延时10ms,也就是10个时钟节拍	
	}
}
 
 
//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{
    
    
	u8 num,key;
	while(1)
	{
    
    
        if(Key_Queue!=NULL)
        {
    
    
            if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue
            {
    
    
                switch(key)
                {
    
    
                    case WKUP_PRES:		//KEY_UP控制LED1
                        LED1=!LED1;
                        break;
                    case KEY2_PRES:		//KEY2控制蜂鸣器
                        BEEP=!BEEP;
                        break;
                    case KEY0_PRES:		//KEY0刷新LCD背景
                        num++;
                        LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
                        break;
                }
            }
        } 
		vTaskDelay(10);      //延时10ms,也就是10个时钟节拍	
	}
}

Reference Video: Punctual Atoms
insert image description here

Guess you like

Origin blog.csdn.net/qq_27928443/article/details/130871612