Introduction
In the previous article, we explained the concept of queues and the related API functions. In this article, we will start learning how to use queues.
1. Basic Usage of Queues
This example will create three tasks, two of which send data to the queue, and the other reads data from the queue.
void Task1Function(void * param){
int val;
while (1)
{
val = 100;
xQueueSend(xQueueCalcHandle, &val, 0);
vTaskDelay(1000);
}
}
void Task2Function(void * param){
int val;
while (1)
{
val = 200;
xQueueSend(xQueueCalcHandle, &val, 0);
vTaskDelay(1000);
}
}
void Task3Function(void * param){
int val;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
BaseType_t xStatus;
while (1)
{
xStatus = xQueueReceive(xQueueCalcHandle, &val, xTicksToWait);
if( xStatus == pdPASS )
{
/* Received data */
printf( "Received = %d\r\n", val );
}
else
{
/* Did not receive data */
printf( "Could not receive from the queue.\r\n" );
}
}
}
xQueueCalcHandle = xQueueCreate(5, sizeof(int));
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, NULL);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 2, NULL);
Execution Result:
From the execution result, we can see that when there is data in the queue, it can be read from the queue. When there is no data in the queue, it will return pdFALSE after a timeout.
Here we use an image from Baidu to describe this process:
2. How to Identify Data Sources
Through the above experiment, we completed sending and receiving queue data. However, we cannot know which queue sent the data. The next experiment will help us identify the data source.
In the previous experiment, we used a single int variable to represent the data. This way, we can only receive the corresponding data but cannot identify who sent the data. So, how can we differentiate who sent the data?
The solution here is to use a structure:
typedef enum
{
Task1,
Task2
}ID_t;
typedef struct data
{
ID_t id;
int data;
}Data_t;
static Data_t senddata[2] = {
{Task1, 10},
{Task2, 20}
};
void Task1Function(void * param){
while (1)
{
xQueueSend(xQueueCalcHandle, &senddata[0], 0);
vTaskDelay(1000);
}
}
void Task2Function(void * param){
while (1)
{
xQueueSend(xQueueCalcHandle, &senddata[1], 0);
vTaskDelay(1000);
}
}
void Task3Function(void * param){
Data_t mydata;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
BaseType_t xStatus;
while (1)
{
xStatus = xQueueReceive(xQueueCalcHandle, &mydata, xTicksToWait);
if( xStatus == pdPASS )
{
/* Received data */
if(mydata.id == Task1)
{
printf("this is Task1 data :%d\r\n", mydata.data);
}
else
{
printf("this is Task2 data :%d\r\n", mydata.data);
}
}
else
{
/* Did not receive data */
printf( "Could not receive from the queue.\r\n" );
}
}
}
xQueueCalcHandle = xQueueCreate(5, sizeof(Data_t));
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, NULL);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 2, NULL);
When receiving data, we first check the id in the structure to determine which task sent the data.
Execution Result:
3. Transmitting Large Data Blocks
In FreeRTOS, queues typically use data copying to transmit data. This means that when you send data to the queue or receive data from the queue, the queue internally copies a copy of the data rather than passing a pointer to the original data.
This data copying method ensures data safety and consistency, as multiple tasks can independently access their own copies without interfering with other tasks. However, it is important to note that data copying can introduce some performance overhead, especially when dealing with large amounts of data.
So, how do we transmit large data using queues?
Here we can use pointers to solve this problem. When transmitting large data, we can first obtain the address of the data and pass the address as data. When receiving the data address, we can access the corresponding data through the address.
Example:
char pcbuffer[100] = "Hello World";
void Task1Function(void * param){
char* buffer;
while (1)
{
buffer = pcbuffer;
xQueueSend(xQueueCalcHandle, &buffer, 0);
vTaskDelay(1000);
}
}
void Task2Function(void * param){
char* buffer;
while (1)
{
buffer = pcbuffer;
xQueueSend(xQueueCalcHandle, &buffer, 0);
vTaskDelay(1000);
}
}
void Task3Function(void * param){
char* rebuffer;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
BaseType_t xStatus;
while (1)
{
xStatus = xQueueReceive(xQueueCalcHandle, &rebuffer, xTicksToWait);
if( xStatus == pdPASS )
{
/* Received data */
printf("recv buffer : %s\r\n", rebuffer);
}
else
{
/* Did not receive data */
printf( "Could not receive from the queue.\r\n" );
}
}
}
xQueueCalcHandle = xQueueCreate(5, sizeof(char*));
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, NULL);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 2, NULL);
Execution Result:
Here are a few points to note:
`1.`
Since the queue transmits data by copying a copy of the data, the original data will not be affected during data transmission. However, here we use addresses, and when the data in this address space is changed, the original data will also be affected.
`2.`
Since we are passing the address space, we must ensure that this data is global data because local data will be released, and once released, it cannot be used. Therefore, we need to ensure that the data is global.
Conclusion
This article concludes here. This article mainly explains the specific code and usage methods of queues.