The objective of this ESP32 Arduino Tutorial is to give an introduction to FreeRTOS queues, using the ESP32 and the Arduino core. The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 module device integrated in a ESP32 development board.
Introduction
The objective of this ESP32 Arduino Tutorial is to give an introduction to FreeRTOS queues, using the ESP32 and the Arduino core.
Queues are very useful for inter-task communication, allowing to send messages from one task to another safely in terms of concurrency [1]. They are typically used as FIFOs (First In First Out) [1], meaning that new data is inserted at the back of the queue and consumed from the front.
Nonetheless, FreeRTOS has a very rich queue API, which offers a lot more functionality. You can check the queue API here.
One very important aspect to keep in mind is that the data that is inserted in the queue is copied rather that only a reference to it being stored [1]. This means that if we send an integer to the queue, its value will be actually copied and if we change the original value after that no problem should occur.
Nonetheless, we can still insert pointers to data as elements of our queue, which is useful specially if the messages to exchange are big. In this case, the pointer will be copied to the queue, not the message itself, and thus we need to guarantee that it is not changed. But this is a more advanced situation which we are not going to cover here.
Other important behavior to keep in mind is that the insertion in a full queue or the consumption of an empty queue can be made blocking calls for a specified amount of time [1] (this amount of time is a parameter of the API).
Although as mentioned queues are typically used for inter-task communication, for this introductory example we will insert and consume elements in the queue on the Arduino main loop function, in order to focus on the basic API calls we need to do.
The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 Module device integrated in a ESP32 development board.
The code
For this tutorial we will not need any additional libraries. Thus, we will start our code by declaring a global variable of type QueueHandle_t, which is the type we need to reference a FreeRTOS queue.
QueueHandle_t queue;
Moving to the setup function, we will start by opening a serial connection, which we will use to output the results of our program.
Next, we will create the queue with a call to the xQueueCreate function. This function receives as first input the maximum number of elements the queue can hold at a given time and as second argument the size (in bytes) of each element [2]. Note that each element of the queue should have the same size [2].
So, we will create a queue that can hold a maximum of 10 elements and since it will contain integers, we can get the size of an integer in bytes with a call to the sizeof function.
Upon a successful execution, the xQueueCreate function will return an handle for the queue, which is of type QueueHandle_t [2], the same of the variable we declared globally. In case a problem with the allocation of the queue occurs, it will return NULL [2]. Thus, we will also do a NULL check on the setup function and warn the user if a problem has occured.
void setup() {
Serial.begin(115200);
queue = xQueueCreate( 10, sizeof( int ) );
if(queue == NULL){
Serial.println("Error creating the queue");
}
}
Moving on to the main function, we will now start by inserting the values in the queue for later consumption. To insert an item in the queue, we simply call the xQueueSend function, which inserts an element at the end of the queue [3].
This function receives as first input the queue handle [3], which will be the global variable we declared and assigned with the result of the call to the queue creation function. As second input, it receives a pointer to the item we want to insert (remember, although we are passing a pointer, the item will actually be copied) and as last argument the maximum amount of time the task should block waiting, in case the queue is full [3].
For the last argument, the value is specified in ticks [3], and we will pass the value portMAX_DELAY, meaning that we will wait indefinitely in case the queue is full. Nonetheless, given the way we will build the program, we will never try to insert when the queue is full, so the main loop will never block.
Since our queue can hold a maximum of 10 elements, we will do a simple for loop and at each iteration we will insert the current value.
for(int i = 0; i<10; i++){
xQueueSend(queue, &i, portMAX_DELAY);
}
Note that we are always passing a pointer to the same variable but since its actual value will be copied, there is no problem in changing it to a new value in each iteration, as we will confirm when running the code.
We will follow the same loop approach to consume the items of the queue. So, to consume an item, we simple need to call the xQueueReceive function. It will receive as first input the handle for the queue, as second input a pointer to a buffer to where the received item will be copied and finally the number of ticks to wait in case the queue is empty.
As before, we pass as first argument our global queue handle variable and as last the portMAX_DELAY value. As for the buffer to which the item gets copied, we need to declare a variable of type integer prior to the consumption of the item, which we will use to store the received item.
Note that upon the consumption of the item, it will be removed from the queue. If you don’t want to remove the item upon retrieval, you can use the xQueuePeek function.
In our loop to consume the elements, we will also print them to the serial port, as can be seen bellow.
int element;
for(int i = 0; i<10; i++){
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
The full source code can be seen bellow. We also do a null check at the beginning of the Arduino main loop to avoid operating on the queue if it has not been allocated. We also added a delay between each iteration of the main loop.
QueueHandle_t queue;
void setup() {
Serial.begin(115200);
queue = xQueueCreate( 10, sizeof( int ) );
if(queue == NULL){
Serial.println("Error creating the queue");
}
}
void loop() {
if(queue == NULL)return;
for(int i = 0; i<10; i++){
xQueueSend(queue, &i, portMAX_DELAY);
}
int element;
for(int i = 0; i<10; i++){
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
Serial.println();
delay(1000);
}
Testing the code
To test the code, simply compile it and upload it with the Arduino IDE. Then, open the serial monitor, with a baud rate equal to the one specified in the begin function (115200). You should get an output similar to figure 1, which shows the queue inserted items being printed in each iteration of the main loop function.
Figure 1 – Output of the queue example program.