Tip: After the article is written, the table of contents can be automatically generated. How to generate it can refer to the help document on the right
Article directory
- Preface
- 1. What is a queue?
-
- In freertos, what is a queue?
-
- ① If I want to communicate interrupts and tasks, can I use global variables?
- ② Then why can queues replace the function of global variables?
- ③ Take a look at the structure of the queue in freertos
- ④Problem: When multiple tasks write messages to a "full queue", these tasks will enter the blocking state, which means that there are multiple tasks waiting for space in the same queue. So when there is space in the queue, which task will enter the ready state?
- ⑤Data write queue and read queue operation process! [Insert picture description here](https://img-blog.csdnimg.cn/02032663fbd14548b0b9f6e699186440.png)
- 2. The structure of the queue
- 3. Introduction to queue-related API functions
- 4. Team entry and exit operation experiments
- 5. Analysis of queue-related API functions
Preface
`This article includes the following:
1. What is a queue?
Overview: A queue is a special data structure that follows the first-in-first-out (FIFO) principle. The elements in the queue are accessed and processed in the order in which they were inserted. New elements are inserted at the end of the queue, and existing elements are operated and deleted at the front of the queue. Queue operations include enqueue and dequeue. Enqueue means inserting elements into the end of the queue, while dequeue means removing and returning the front-end elements of the queue. Queues are often used in scenarios where elements need to be processed in sequence, such as task scheduling, message delivery, etc.
In freertos, what is a queue?
Queue is a mechanism (message passing) for data exchange from task to task, task to interrupt, and interrupt to task.
① If I want to communicate interrupts and tasks, can I use global variables?
Answer : No. Take this picture as an example. For example, if a global variable a=0, both tasks will increment the global variable a. If the priorities of the two tasks are different, a++ may run to read data-modify data when running task 1. , but it was interrupted by the high-priority task two before the data was written, resulting in a≠1, and it =1 after executing task two. In this case, the value of the global variable a is wrong after executing the task twice.
Therefore, the disadvantages of global variables: data is not protected, resulting in data insecurity, when multiple tasks operate on the variable at the same time, the data is easily damaged
② Then why can queues replace the function of global variables?
First, let’s look at the structure of the queue:
You can see that tasks A and B are write queue operations. As for the write queue function, it will enter the critical section and exit the critical section after completing the actual operation. Therefore: the system is actually shut down when writing to the queue . Interrupts allow the critical section code to run completely without being interrupted, and the same applies to reading queues .
Therefore, the read-write queue has a program protection function to prevent data conflicts caused by simultaneous access by multiple tasks.
③ Take a look at the structure of the queue in freertos
A limited amount of fixed-size data can be stored in the queue. Each data in the queue is called a "queue item", and the maximum number of "queue items" that the queue can store is called the length of the queue.
So the core characteristics of queues: queue length and size of each queue item . We need to set it when we create it ourselves.
In Freertos, the characteristics of the queue:
(1) FIFO is the abbreviation of First-In First-Out, which means first-in, first-out. In a queue, new elements are inserted at the end of the queue, while existing elements are manipulated and removed at the front of the queue. When it is necessary to access or process elements in the queue, the earliest inserted element in the queue is accessed or processed first, and then other elements are accessed or processed in order of insertion. This first-in, first-out feature makes the queue a commonly used data structure and is widely used in scenarios such as task scheduling, cache management, and message delivery.
(2) In FreeRTOS, queues can transfer data by passing actual values or passing pointers. Actual value transfer refers to copying a copy of the data to the queue for transfer. This operation is a copy of the data and has no impact on the original data. Passing a pointer puts the pointer to the actual data into the queue, which can avoid copying a large amount of data. However, you need to pay attention when using pointer transfer to ensure that the pointer does not point to invalid data.
When transferring larger data, using pointer transfer can avoid frequent data copying and improve efficiency. However, it is necessary to pay attention to the validity of the data when using pointer transfer, that is, to ensure that the data pointed to by the pointer will not be modified or released during the transfer process, so as to avoid data errors or dangling pointers.
(3) Queues are a general mechanism in FreeRTOS that can be used by any task or interrupt to send and read messages. This is because a queue is a shared data structure used to pass data between different tasks or interrupts. Any task or interrupt can use the queue's API functions to send messages to or read messages from the queue, no matter which task they belong to.
This flexibility makes queues a commonly used communication mechanism, which can facilitate data transfer and synchronization between tasks in multi-task or multi-interrupt systems. Through queues, tasks and interrupts can safely share data, avoiding race conditions and data collision problems. At the same time, tasks and interrupts can be blocked or woken up as needed for efficient synchronization and communication.
(4) When a task sends a message to a queue, the behavior of the task can be controlled by specifying a blocking time .
If the queue is full and the message cannot be enqueued, the task can choose the following behaviors :
①Blocking waiting: The task can specify a blocking time. If the queue is full, the task will be blocked before the queue has free space. The task will wait until the queue has a free place and successfully enqueues the message, or the wait timeout expires .
② Non-blocking immediate return: The task can choose to return immediately when the queue is full without blocking waiting. The task can judge whether the message is successfully sent to the queue according to the returned result .
④Problem: When multiple tasks write messages to a "full queue", these tasks will enter the blocking state, which means that there are multiple tasks waiting for space in the same queue. So when there is space in the queue, which task will enter the ready state?
⑤Data writing queue and reading queue operation process![insert image description here](https://img-blog.csdnimg.cn/02032663fbd14548b0b9f6e699186440.png)
2. The structure of the queue
1. Structure content
as follows:
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; /* 队列项目的大小 */
volatile int8_t cRxLock; /* 读取上锁计数器 */
volatile int8_t cTxLock; /* 写入上锁计数器 */
/* 其他的一些条件编译 */
} xQUEUE;
This code is the definition of a queue, and the specific structure members are explained as follows:
int8_t * pcHead: The starting address of the storage area, that is, the first address of the storage space of the queue.
int8_t * pcWriteTo: pointer to the next write position, used to indicate the next position to write data.
union { QueuePointers_t xQueue; SemaphoreData_t xSemaphore; } u: A union used to save queue pointers or semaphore data.
List_t xTasksWaitingToSend: Waiting to send list, used to store tasks waiting to send messages to the queue.
List_t xTasksWaitingToReceive: Waiting to receive list, used to store tasks waiting to receive messages from the queue.
volatile UBaseType_t uxMessagesWaiting: The number of non-idle queue items, used to record the number of messages waiting to be received in the current queue.
UBaseType_t uxLength: The length of the queue, indicating the maximum number of items that the queue can hold.
UBaseType_t uxItemSize: The size of the queue item, indicating the number of bytes occupied by each item.
volatile int8_t cRxLock: read lock counter, used to record the number of times the current queue is locked by read operations.
volatile int8_t cTxLock: write lock counter, used to record the number of times the current queue is locked by write operations.
2. Structure diagram
3. Introduction to queue-related API functions
The main process of using queues: create queue->write queue->read queue. It mainly includes three parts: creating queue, writing queue and reading queue.
1. Create a queue
Parameter Description:
uxQueueLength: The length of the queue, that is, the maximum number of items that the queue can hold.
uxItemSize: The size of each item in the queue, that is, the number of bytes each item occupies.
return value:
When the queue is successfully created, a valid queue handle (QueueHandle_t) is returned.
When the queue creation fails, NULL is returned.
Example usage:
#include "FreeRTOS.h"
#include "queue.h"
// 创建一个长度为10,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));
if (xQueue != NULL) {
// 队列创建成功
} else {
// 队列创建失败
}
The xQueueCreate() function is used to dynamically create a queue at runtime and returns a queue handle, which can be used for subsequent operations on the queue, such as sending and receiving messages. Note that after using the queue, you need to use the vQueueDelete() function to delete the queue to release related resources.
2. Write messages into the queue
There are only three ways to write messages to the queue: the head of the queue, the tail of the queue, and overwriting. Overwriting can only be used when the queue length of the queue is 1.
(1) The first three functions
The xQueueSend(), xQueueSendToBack(), and xQueueSendToFront() functions are all functions used to write messages to the queue. Their functions are similar, but there are some subtle differences.
The function prototypes of these 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 xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
Parameter description:
xQueue: The handle of the queue to be written.
pvItemToQueue: Pointer to the message to be written to the queue.
xTicksToWait: The timeout period for write operations. If the queue is full, wait for a while before trying to write. You can use portMAX_DELAY to indicate an infinite wait.
Return value:
pdPASS is returned if the message is successfully written to the queue.
Returns errQUEUE_FULL if writing a message fails (e.g. the queue is full) and the write fails within the specified timeout.
Example usage:
#include "FreeRTOS.h"
#include "queue.h"
// 创建一个长度为10,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));
int message = 42;
// 向队列尾部写入消息
if (xQueueSend(xQueue, &message, portMAX_DELAY) == pdPASS) {
// 消息写入成功
} else {
// 消息写入失败
}
// 向队列尾部写入消息(与 xQueueSend() 等效)
if (xQueueSendToBack(xQueue, &message, portMAX_DELAY) == pdPASS) {
// 消息写入成功
} else {
// 消息写入失败
}
// 向队列头部写入消息
if (xQueueSendToFront(xQueue, &message, portMAX_DELAY) == pdPASS) {
// 消息写入成功
} else {
// 消息写入失败
}
These functions are used to write messages to the queue, xQueueSend() and xQueueSendToBack() write the message to the tail of the queue, and xQueueSendToFront() writes the message to the head of the queue. If the queue is full, the write operation will block until the queue becomes available or times out.
(2) The following functions
The xQueueOverwrite() and xQueueOverwriteFromISR() functions are used to overwrite messages in the queue, and are only applicable when the queue length is 1.
The function prototypes of these functions are as follows:
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
Parameter Description:
xQueue: The handle of the queue to be operated on.
pvItemToQueue: Pointer to the message to be written to the queue.
pxHigherPriorityTaskWoken: A pointer to a BaseType_t type variable used to indicate whether a higher priority task needs to be awakened. Used in xQueueOverwriteFromISR() and can be set to NULL.
return value:
pdPASS is returned if the message in the queue was successfully overwritten.
Returns errQUEUE_FULL if the queue is empty or if the queue length is not 1.
Example usage:
#include "FreeRTOS.h"
#include "queue.h"
// 创建一个长度为1,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(1, sizeof(int));
int message = 42;
// 覆写队列中的消息
if (xQueueOverwrite(xQueue, &message) == pdPASS) {
// 消息覆写成功
} else {
// 消息覆写失败
}
// 在中断中覆写队列中的消息
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueOverwriteFromISR(xQueue, &message, &xHigherPriorityTaskWoken) == pdPASS) {
// 消息覆写成功
} else {
// 消息覆写失败
}
The xQueueOverwrite() and xQueueOverwriteFromISR() functions are used to overwrite messages in the queue. With a queue length of 1, these functions can be used to overwrite existing messages in the queue without waiting for or creating new messages. Note that these functions only work when the queue length is 1.
When using the xQueueOverwriteFromISR() function in an interrupt handler, you need to set the pxHigherPriorityTaskWoken parameter to a non-null pointer, and determine whether a higher priority task needs to be awakened based on the actual situation.
3. Read messages from the queue
xQueueReceive()
reads the message from the head of the queue and deletes the message from the queue.
If the queue is empty, the task will enter the blocking state until there is a message in the queue to read.
Returning pdPASS indicates that the message was successfully read, and returning errQUEUE_EMPTY indicates that the queue is empty.
xQueuePeek()
reads messages from the head of the queue but does not delete the messages.
If the queue is empty, the task will enter the blocking state until there is a message in the queue to read.
Returning pdPASS indicates that the message was successfully read, and returning errQUEUE_EMPTY indicates that the queue is empty.
xQueueReceiveFromISR()
reads the message from the head of the queue in an interrupt and deletes the message from the queue.
Similar to the xQueueReceive() function, but especially suitable for use in interrupt service routines (ISR).
Returning pdPASS indicates that the message was successfully read, and returning errQUEUE_EMPTY indicates that the queue is empty.
xQueuePeekFromISR()
reads the message from the head of the queue in an interrupt, but does not delete the message.
Similar to the xQueuePeek() function, but especially suitable for use in interrupt service routines (ISR).
Returning pdPASS indicates that the message was successfully read, and returning errQUEUE_EMPTY indicates that the queue is empty.
These functions are used to read messages in the queue and delete or retain messages as needed. Among them, the xQueueReceiveFromISR() and xQueuePeekFromISR() functions are specially used in interrupt service routines. These functions block the task or interrupt service routine until there are messages in the queue to read. If the queue is empty, the task or interrupt service routine will block until there are messages in the queue to read. The return value indicates whether the read operation was successful.
Code example:
Here is a simple example code that shows how to read messages from a queue using the xQueueReceive() and xQueuePeek() functions:
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
// 定义一个全局队列
QueueHandle_t queue;
// 任务函数1,向队列中发送消息
void task1(void *pvParameters) {
int msg = 100;
while(1) {
// 发送消息到队列中
xQueueSend(queue, &msg, 0);
// 任务延时1秒
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务函数2,从队列中读取消息并删除
void task2(void *pvParameters) {
int receivedMsg;
while(1) {
// 从队列中读取并删除消息
if(xQueueReceive(queue, &receivedMsg, portMAX_DELAY) == pdPASS) {
printf("Received message: %d\n", receivedMsg);
}
// 任务延时500毫秒
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main() {
// 创建队列
queue = xQueueCreate(5, sizeof(int));
// 创建任务1
xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 创建任务2
xTaskCreate(task2, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败,则打印错误信息
printf("Failed to start FreeRTOS scheduler!\n");
return 0;
}
In this example, we create a global queue queue, and then create two tasks task1 and task2. The task1 task sends messages to the queue through the xQueueSend() function, while the task2 task reads and deletes messages from the queue using the xQueueReceive() function. Each task uses the vTaskDelay() function to delay for a certain period of time to simulate the task execution process.
When the task2 task successfully reads the message from the queue, it will print the message content. Here portMAX_DELAY is used as the blocking time parameter, which means that if the queue is empty, the task will block until there is a message to read.
This example shows how to use the xQueueReceive() function to read and delete messages from the queue, and how to use the xQueueSend() function to send messages to the queue. In practical applications, these functions can be used in tasks to implement message passing and synchronization as needed.
4. Team entry and exit operation experiments
1. Experimental objectives
This time I will give a very detailed explanation of the routine:
2. Routine
①main.c
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */
delay_init(180); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
sdram_init(); /* SRAM初始化 */
lcd_init(); /* 初始化LCD */
my_mem_init(SRAMIN); /* 初始化内部内存池 */
my_mem_init(SRAMEX); /* 初始化外部内存池 */
my_mem_init(SRAMCCM); /* 初始化CCM内存池 */
freertos_demo();
}
In addition to those functions also used by bare metal, there are also these memory initialization settings:
sdram_init(): used to initialize SRAM (static random access memory). SRAM is a high-speed memory, usually used to store data, variables or code.
my_mem_init(SRAMIN): used to initialize the internal memory pool. The internal memory pool refers to a storage space inside the chip used to store data, variables or code.
my_mem_init(SRAMEX): used to initialize the external memory pool. The external memory pool refers to a storage space connected to the outside of the chip, usually using external memory (such as SDRAM, NOR Flash) to expand the memory.
my_mem_init(SRAMCCM): Used to initialize the CCM (Core-Coupled Memory) memory pool. CCM memory is a memory that is tightly coupled with the CPU core. It has the characteristics of low latency and high bandwidth and is suitable for storing critical data and code.
The most important thing is of course the freertos_demo(); function.
②freertos_demo();
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
/* 队列的创建 */
key_queue = xQueueCreate( 2, sizeof(uint8_t) );
if(key_queue != NULL)
{
printf("key_queue队列创建成功!!\r\n");
}else printf("key_queue队列创建失败!!\r\n");
big_date_queue = xQueueCreate( 1, sizeof(char *) );
if(big_date_queue != NULL)
{
printf("big_date_queue队列创建成功!!\r\n");
}else printf("big_date_queue队列创建失败!!\r\n");
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
This is a function called freertos_demo(), which is used to demonstrate the creation of queues and the creation and scheduling of tasks in FreeRTOS.
key_queue = xQueueCreate(2, sizeof(uint8_t)): Create a queue with a capacity of 2 and an element size of uint8_t for storing key values. If the queue is created successfully, "key_queue queue created successfully!!" will be printed, otherwise "key_queue queue creation failed!!" will be printed.
big_date_queue = xQueueCreate(1, sizeof(char *)): Create a queue with a capacity of 1 and an element size of char pointer for storing large data blocks. If the queue is created successfully, it will print "big_date_queue queue created successfully!!", otherwise print "big_date_queue queue creation failed!!" .
xTaskCreate(start_task, "start_task", START_TASK_STACK_SIZE, NULL, START_TASK_PRIO, &start_task_handler): Create a task named "start_task" and use the start_task() function as the task function. The task's stack size is START_TASK_STACK_SIZE and its priority is START_TASK_PRIO. The task handle start_task_handler is used for subsequent operations.
vTaskStartScheduler(): Start the FreeRTOS scheduler and start executing tasks.
③Operation queue and storage of large data blocks
QueueHandle_t key_queue; /* 小数据句柄 */
QueueHandle_t big_date_queue; /* 大数据句柄 */
char buff[100] = {
"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
key_queue and big_date_queue are the handles of the queue (or pointers to the queue), which are used to reference these two queues in the program. key_queue is a handle pointing to a small data queue, and big_date_queue is a handle pointing to a big data queue.
In addition, a character array named buff with a length of 100 is defined to store large data blocks. The array contains a string representing the contents of a large data block.
④Task 1: Join the team
/* 任务一,实现入队 */
void task1( void * pvParameters )
{
uint8_t key = 0;
char * buf;
BaseType_t err = 0;
buf = &buff[0]; /* buf = &buff[0] */
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES || key == KEY1_PRES)
{
err = xQueueSend( key_queue, &key, portMAX_DELAY );
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}else if(key == WKUP_PRES)
{
err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}
vTaskDelay(10);
}
}
At the beginning of the task function, a key variable and a buf pointer variable are defined. The buf pointer points to the first element of the buff array mentioned earlier.
In the main loop of the task, the key_scan(0) function is first called to obtain the key value and assign it to the key variable. Then use conditional judgment to determine whether the key value is KEY0_PRES or KEY1_PRES. If so, send the key value to the key_queue queue and use the xQueueSend() function to implement it. If the sending fails, "key_queue queue sending failed" is printed.
In addition, if the key value is WKUP_PRES, the buf pointer is sent to the big_date_queue queue, which is also implemented by using the xQueueSend() function. If the sending fails, "key_queue queue sending failed" is printed.
Finally, the vTaskDelay(10) function is used to delay 10 system clock cycles, and then continue with the next cycle.
The function of this task function is to send data to different queues according to the key value, realizing the operation of data enqueue.
⑤Task 2: Dequeue small data
/* 任务二,小数据出队 */
void task2( void * pvParameters )
{
uint8_t key = 0;
BaseType_t err = 0;
while(1)
{
err = xQueueReceive( key_queue,&key,portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列读取失败\r\n");
}else
{
printf("key_queue读取队列成功,数据:%d\r\n",key);
}
}
}
At the beginning of the task function, a key variable and an err variable are defined.
In the main loop of the task, call the xQueueReceive() function to receive data from the key_queue queue and save the received data in the key variable. Use portMAX_DELAY as the blocking time, which means that if the queue is empty, the task will block until data is available.
After receiving the data, judge whether the data reception is successful or not through conditional judgment. If the reception fails, print "key_queue queue read failed". If the reception is successful, print "key_queue read queue successfully, data:" and print out the received key value.
The function of this task function is to receive data from the key_queue queue and process it.
⑥Task 3: Big data dequeue
/* 任务三,大数据出队 */
void task3( void * pvParameters )
{
char * buf;
BaseType_t err = 0;
while(1)
{
err = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_date_queue队列读取失败\r\n");
}else
{
printf("数据:%s\r\n",buf);
}
}
}
According to the code you provided, this is a task function named task3(), which is used to implement big data dequeuing operations.
At the beginning of the task function, a buf pointer variable and an err variable are defined.
In the main loop of the task, call the xQueueReceive() function to receive data from the big_date_queue queue and save the received data in the buf pointer variable. Use portMAX_DELAY as the blocking time, which means that if the queue is empty, the task will block until data is available.
After receiving the data, conditional judgment is used to determine whether the data reception is successful. If receiving fails, print "big_date_queue queue read failed". If the reception is successful, print "data:" and print out the received string data.
The function of this task function is to receive large data blocks from the big_date_queue queue and process them.
3. Routine running results:
5. Analysis of queue-related API functions
1. Queue creation API function: xQueueCreate()
The xQueueCreate() function is an API function used to create a queue in FreeRTOS. Its internal implementation process is as follows:
First, the function checks whether the incoming queue length and queue element size are legal. If it is invalid, the function will return NULL, indicating that the queue creation failed.
Next, the function allocates memory space for the queue, including the queue control block and queue storage area. The queue control block is a structure used to manage various attributes and status information of the queue; the queue storage area is a continuous memory block used to store elements in the queue.
The function then initializes the various fields of the queue control block. For example, set the length of the queue, the size of the elements, the starting address of the storage area, etc.
Next, the function initializes the semaphore of the queue to implement synchronization and mutually exclusive access to the queue. This semaphore is used to control the reading and writing operations of tasks on the queue, ensuring that only one task accesses the queue.
Finally, the function returns a pointer to the created queue. If queue creation fails, the function returns NULL.
It should be noted that the xQueueCreate() function only creates the queue data structure and does not allocate the memory space of the queue storage area. The actual memory allocation occurs when functions such as xQueueSend() and xQueueReceive() are called. This is because the queue's storage area size is dynamically calculated based on the queue length and element size, so memory space needs to be dynamically allocated at runtime.
2. Write data to the queue API function (enqueue): xQueueSend()
The xQueueSend() function is an API function in FreeRTOS used to write data to the queue, also known as the queue operation. Its internal implementation process is as follows:
First, the function checks whether the incoming queue pointer and the data pointer to be written are legal. If the queue pointer or data pointer is empty, the function returns an error code indicating that the write operation failed.
Next, the function attempts to obtain the queue's semaphore. This is to ensure that only one task accesses the queue and avoids data confusion caused by multiple tasks writing to the queue at the same time.
If the queue's semaphore is successfully obtained, the function will copy the data to be written to the queue's storage area. The exact method of replication depends on the type of queue. For example, if it is a byte queue, just copy the data directly to the storage area; if it is a structure queue, you need to copy the data one by one according to the size of the structure.
After writing data, the function updates the relevant properties of the queue, such as the number of elements in the queue, read and write pointers, etc.
Finally, the function releases the semaphore of the queue, indicating that the write operation is complete.
It should be noted that the xQueueSend() function has two writing modes to choose from when writing data: blocking mode and non-blocking mode. In blocking mode, if the queue is full, the write operation will block the current task until there is a free space in the queue to write; in non-blocking mode, if the queue is full, the write operation will immediately return an error code, Indicates that the write operation failed. This mode is determined by the blocking time parameter passed in when the function is called.
3. API function to read data from the queue (dequeue): xQueueReceive( )
The xQueueReceive() function is an API function used in FreeRTOS to read data from the queue, also known as dequeue operation. Its internal implementation process is as follows:
First, the function checks whether the incoming queue pointer and the pointer to receive data are legal. If the queue pointer or the pointer to receive data is empty, the function will return an error code, indicating that the read operation failed.
Next, the function tries to acquire the queue's semaphore. This is to ensure that only one task accesses the queue to avoid data confusion caused by multiple tasks reading the queue at the same time.
If the queue's semaphore is successfully obtained, the function reads the data from the queue's storage area and copies the read data to the pointer that receives the data. The exact method of replication depends on the type of queue. For example, if it is a byte queue, just read the data directly from the storage area; if it is a structure queue, you need to copy it member by member according to the size of the structure.
After reading the data, the function updates the relevant properties of the queue, such as the number of elements in the queue, read and write pointers, etc.
Finally, the function releases the semaphore of the queue, indicating that the read operation is complete.
It should be noted that when the xQueueReceive() function reads data, there are two reading modes to choose from: blocking mode and non-blocking mode. In blocking mode, if the queue is empty, the read operation will block the current task until the queue has data to read; in non-blocking mode, if the queue is empty, the read operation will immediately return an error code, indicating The read operation failed. This mode is determined by the blocking time parameter passed in when the function is called.