FreeRTOS message queue & esp32 actual combat
FreeRTOS message queue
The message queue of FreeRTOS is very different from the message queue in the operating system class, which is to send information between multiple tasks in an orderly and safe manner. Here are some of its features.
- Multitasking access
the queue does not belong to a specially specified task. Any task can send messages to the queue or extract messages from the queue. - Original value transfer
the message content in the queue is not a reference, that is, instead of delivering the address of the content, the content of the data is directly copied to the message queue. This has two advantages: first, after delivery, the message buffer can be changed immediately without waiting for the message to be delivered. Second, it will not cause message confusion due to changes in local variables, When a local variable of a function is passed as a message, when the function is completed and returned, the local variable will be destroyed. As a result, the address passed to the queue is a garbage address, and the content is useless. Of course, you can also directly pass the address as content, so as to realize reference passing. - Out of line blocking
when a task wants to read a message from the queue, it can block itself and wait for the data to arrive. Of course, it can not wait for acquisition, or it can wait for a certain time, or it can wait forever. - Queue blocking
when a task wants to deliver a message to the queue and finds that the queue is full, it can choose to block itself and wait for the queue to have a free position before delivering the message. Of course, there are three blocking modes: no blocking, blocking for a period of time and permanent blocking.
Source code analysis of FreeRTOS message queue structure
/* * Definition of the queue used by the scheduler. * Items are queued by copy, not reference. See the following link for the * rationale: https://www.freertos.org/Embedded-RTOS-Queues.html */ typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */ { int8_t *pcHead; /*< Pointer to queue header */ int8_t *pcWriteTo; /*< Pointer to the next free location of the queue */ union { QueuePointers_t xQueue; /*< This should refer to the handle when used as a queue, pointing to the created queue */ SemaphoreData_t xSemaphore; /*< This should refer to the handle when used as a semaphore, pointing to the created semaphore */ } u; List_t xTasksWaitingToSend; /*Waiting to send task list, queue blocking list*/ /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */ List_t xTasksWaitingToReceive; /*Waiting to receive task list, out of queue blocking list*/ /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */ volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */ UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes.Queue content length, calculated by the number of messages, not by bytes */ UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. Maximum length of message content*/ volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */ volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */ #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */ #endif #if ( configUSE_QUEUE_SETS == 1 ) struct QueueDefinition *pxQueueSetContainer; #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxQueueNumber; uint8_t ucQueueType; #endif portMUX_TYPE mux; //Mutex required due to SMP } xQUEUE; /* The old xQUEUE name is maintained above then typedefed to the new Queue_t name below to enable the use of older kernel aware debuggers. */ typedef xQUEUE Queue_t;
FreeRTOS API analysis
- xQueueCreate();
Create completed diagram:QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) { Queue_t *pxNewQueue; size_t xQueueSizeInBytes; uint8_t *pucQueueStorage; configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); if( uxItemSize == ( UBaseType_t ) 0 ) { /* There is not going to be a queue storage area. */ xQueueSizeInBytes = ( size_t ) 0; } else { /* Allocate enough space to hold the maximum number of items that can be in the queue at any time. */ xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. *///Queue length * message size = maximum } /* Check for multiplication overflow. */ configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) ); /* Check for addition overflow. */ configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes ); /* Allocate the queue and storage area. Justification for MISRA deviation as follows: pvPortMalloc() always ensures returned memory blocks are aligned per the requirements of the MCU stack. In this case pvPortMalloc() must return a pointer that is guaranteed to meet the alignment requirements of the Queue_t structure - which in this case is an int8_t *. Therefore, whenever the stack alignment requirements are greater than or equal to the pointer to char requirements the cast is safe. In other cases alignment requirements are not strict (one or two bytes). *//*This means that the area applied by malloc should comply with MISRA standard alignment.*/ pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );//Queue structure size + buffer size if( pxNewQueue != NULL ) { /* Jump past the queue structure to find the location of the queue storage area. */ pucQueueStorage = ( uint8_t * ) pxNewQueue; pucQueueStorage += sizeof( Queue_t ); /*lint !e9016 Pointer arithmetic allowed on char types, especially when it assists conveying intent. *///The pointer offset directly skips the queue structure size to the buffer. #if( configSUPPORT_STATIC_ALLOCATION == 1 ) { /* Queues can be created either statically or dynamically, so note this task was created dynamically in case it is later deleted. */ pxNewQueue->ucStaticallyAllocated = pdFALSE; } #endif /* configSUPPORT_STATIC_ALLOCATION */ prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); } else { traceQUEUE_CREATE_FAILED( ucQueueType ); mtCOVERAGE_TEST_MARKER(); } return pxNewQueue;//Returns a pointer to the queue. }

2. xQueueSend() & xQueueSendFromISR()
Send a message to the end of the queue (backward queue). The two functions are the same. FromISR is used to interrupt the service function.
The source code is too long. The main function is to record the time of joining the team and joining the team, so as to facilitate the blocking operation. There is no specific analysis here. If you want to see it, you can leave a message. I will send it separately.
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait )
Parameters:
xQueue: queue handle, indicating which queue to send data to. After the queue is created successfully, the queue handle of this queue will be returned.
pvltemToQueue: refers to the message to be sent, which will be copied to the queue when sending.
xTicksToWait: blocking time. This parameter indicates the maximum time that the task enters the blocking state when the queue is full and waits for the queue to be idle
Time. If it is 0, it will return immediately when the queue is full; if it is portMAX_DELAY, it will wait until the queue has free queue items, that is, dead waiting, but the macro INCLUDE_vTaskSuspend must be 1.
3. xQueueSendToFront() & xQueueSendToFrontFromISR()
xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )
Send a message to the queue header (forward queue), and FromISR is used to interrupt the service function.
Parameters:
xQueue: queue handle, indicating which queue to send data to. After the queue is created successfully, the queue handle of this queue will be returned.
pvltemToQueue: refers to the message to be sent, which will be copied to the queue when sending.
xTicksToWait: blocking time. This parameter indicates the maximum time that the task enters the blocking state when the queue is full and waits for the queue to be idle
Time. If it is 0, it will return immediately when the queue is full; if it is portMAX_DELAY, it will wait until the queue has free queue items, that is, dead waiting, but the macro INCLUDE_vTaskSuspend must be 1.
4. xQueueOverwrite() & xQueueOverwriteFromISR()
Send messages to the queue with overwrite function. When the queue is full, the old messages are automatically overwritten. Therefore, there is no full waiting. FromISR is used to interrupt the service function.
xQueue: queue handle, indicating which queue to send data to. After the queue is created successfully, the queue handle of this queue will be returned.
pvltemToQueue: refers to the message to be sent, which will be copied to the queue when sending.
xQueueOverwrite( xQueue, pvItemToQueue )
5. xQueueReceive() & xQueueReceiveFromISR()
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
Read the queue item message from the queue, and delete the lagged item (message) after reading.
Parameters:
xQueue: queue handle, indicating which queue data to read. After the queue is created successfully, the queue handle of this queue will be returned.
pvBuffer: a buffer for storing data. During the process of reading the queue, the read data will be copied to this buffer.
xTicksToWait: blocking time. This parameter indicates the maximum time for the task to enter the blocking state and wait for data in the queue 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, dead waiting. However, the macro INCLUDE_vTaskSuspend must be 1.
6. xQueuePeek() & xQueuePeekFroemISR ()
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
The queue item message is read from the queue, and the queue item (message) is not deleted after reading.
Parameters:
xQueue: queue handle, indicating which queue data to read. After the queue is created successfully, the queue handle of this queue will be returned.
pvBuffer: a buffer for storing data. During the process of reading the queue, the read data will be copied to this buffer.
xTicksToWait: blocking time. This parameter indicates the maximum time for the task to enter the blocking state and wait for data in the queue 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, dead waiting. However, the macro INCLUDE_vTaskSuspend must be 1.
ESP32 use
Example: design an interface to send a message to the specified queue every time the key is pressed. If there is no message, it will wait forever. When both threads get the information, they will eliminate the information. The specific block diagram is as follows.
Program framework:
The purpose of this experiment is to the information transmission part of T12 soldering iron. Because the network interface has not been realized, the network data refresh is replaced by a screen display thread.
The final code is as follows
External interrupt configuration section
#include "key.h" #define GPIO(n) (1ULL<<n) #define GPIO_Logic(n) n #define EXTI_Num 0 #define EXTI2_Num 18 extern QueueHandle_t Datasender; void EXIT_Handelr_2() { BaseType_t xHigherPriorityTaskWoken; int Temp=185; xHigherPriorityTaskWoken=pdFALSE; if(Datasender!=NULL)//The interrupt may have run before the queue is created. You need to judge here xQueueSendFromISR(Datasender,&Temp,&xHigherPriorityTaskWoken);//Use the API with ISR to transfer data in interrupt. Note that task switching is required. if( xHigherPriorityTaskWoken )//Judge task switching { // Actual macro used here is port specific. portYIELD_FROM_ISR (); } } void EXIT_Config()//GPIO terminal configuration function { gpio_config_t EXTI_config; EXTI_config.pin_bit_mask=GPIO(EXTI_Num); /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */ EXTI_config.mode=GPIO_MODE_INPUT; /*!< GPIO mode: set input/output mode*/ EXTI_config.pull_up_en = 1; /*!< GPIO pull-up*/ EXTI_config.pull_down_en = 0; EXTI_config.intr_type=GPIO_INTR_NEGEDGE; /*!< GPIO interrupt type*/ gpio_config(&EXTI_config); gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); gpio_isr_handler_add(EXTI_Num,EXIT_Handelr,NULL); }
Queue creation section:
#include "myqueue.h" /*settings*/ #define queue_size 10 #define Itemsize sizeof(int) /*proseperity*/ QueueHandle_t Datasender; /*functionsy*/ void Create_queue() { Datasender=xQueueCreate(queue_size,Itemsize); if(Datasender == NULL) printf("Create_queue Failed "); else printf("Create_queue Successful"); }
lvgl GUI display:
/*********************** GUI_SHOW_CODE_START***********************/ lv_obj_t * label_1; lv_obj_t * label_2; extern QueueHandle_t Datasender; void Refresh_State() { Create_queue();//Function implementation below EXIT_Config();//Function implementation below char a1[15]; int num = 0; while (1) { if(Datasender!=NULL) { xQueuePeek(Datasender,&num,portMAX_DELAY); sprintf(a1,"num is %d",num); lv_label_set_text(label_1,a1);//Set text content xQueueReceive(Datasender,&num,portMAX_DELAY); sprintf(a1,"num is %d",num); lv_label_set_text(label_2,a1);//Set text content } } } void Show_State() { lv_obj_t *scr = lv_scr_act(); //Create scr lv_obj_set_pos(scr,0,0); lv_scr_load(scr); label_1 =lv_label_create(scr);//Create label lv_label_set_recolor(label_1,1);//Color changeable lv_label_set_long_mode(label_1,LV_LABEL_LONG_SCROLL_CIRCULAR);//Set scroll mode lv_obj_set_pos(label_1,10,0);//Set position lv_obj_set_size(label_1,100,60);//Set size lv_label_set_text(label_1, "This is the GUI thread");//Set text content label_2 =lv_label_create(scr);//Create label lv_label_set_recolor(label_2,1);//Color changeable lv_label_set_long_mode(label_2,LV_LABEL_LONG_SCROLL_CIRCULAR);//Set scroll mode lv_obj_set_pos(label_2,10,30);//Set position lv_obj_set_size(label_2,100,60);//Set size lv_label_set_text(label_2, "This is the Intetnet thread");//Set text content xTaskCreatePinnedToCore(Refresh_State,"Refresh_State_task",1024*2,NULL,3,NULL,1); //Here, you must use the task allocation function for which core. Interrupt allocation is bound to the core. } /*********************** GUI_SHOW_CODE_END***********************/
Effect demonstration:
Two display threads:
Press the key to trigger the interrupt. Two threads get messages in turn
Two display threads:
If you are interested in introducing the interruption of ESP32 architecture, please pay close attention.