FreeRTOS Tutorial #5 ->Using Queue
This is the Fifth tutorial in the series of FreeRTOS, and today in this tutorial we are going to learn how to use Queue to communicate between the tasks. You can check the other tutorials on FreeRTOS by going HERE. Below is the picture of How the Queue works, and it’s self explanatory.
Queue is the easiest way to send and receive data between the tasks. We are going to use the simple queue at first, where all the elements in the Queue are of same data types, and later we will use a structured Queue, where the data types can be different.
Simple Queue
As I mentioned, in a simple Queue all the elements are of same type. For example a Queue can only hold 5 integers, or 6 characters, or 3 unsigned integers etc.
A Queue is recognized by it’s handler, so first of all we need to create a handler for the Queue
/**************** QUEUE HANDLER ***********************/
xQueueHandle SimpleQueue;
Next, inside the main function, we will create a Queue, which can store 5 integers
/************************* Create Integer Queue ****************************/
SimpleQueue = xQueueCreate(5, sizeof (int));
if (SimpleQueue == 0) // Queue not created
{
char *str = "Unable to create Integer Queue\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
else
{
char *str = "Integer Queue Created successfully\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
If there is an error while creating a Queue, like shortage of memory, than the xQueueCreate
function return a ‘0’. Otherwise it will return any other value. The above strings will be printed on the console based on if the Queue was created successfully or not.
Inside the sender task function, we can send the data to the Queue using the following
void Sender_HPT_Task (void *argument)
{
int i=222;
uint32_t TickDelay = pdMS_TO_TICKS(2000);
while (1)
{
if (xQueueSend(SimpleQueue, &i, portMAX_DELAY) == pdPASS)
{
char *str2 = " Successfully sent the number to the queue\nLeaving SENDER_HPT Task\n\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str2, strlen (str2), HAL_MAX_DELAY);
}
vTaskDelay(TickDelay);
}
}
The parameters of xQueueSend
are handler to the Queue, the address of the data to send, and the waiting time incase the Queue is full. I have specified the waiting as portMAX_DELAY, that means the task is going to wait forever for the space to become available in the Queue. For this waiting time, this task will be in the suspension.
If the data is sent successfully, the xQueueSend
function will return pdPASS, and we can print the string for the confirmation.
On the other hand, RECEIVER TASK will receive this data, and store it in a variable.
void Receiver_Task (void *argument)
{
int received=0;
uint32_t TickDelay = pdMS_TO_TICKS(3000);
while (1)
{
if (xQueueReceive(SimpleQueue, &received, portMAX_DELAY) != pdTRUE)
{
HAL_UART_Transmit(&huart2, (uint8_t *)"Error in Receiving from Queue\n\n", 31, 1000);
}
else
{
sprintf(str, " Successfully RECEIVED the number %d to the queue\nLeaving RECEIVER Task\n\n\n",received);
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
vTaskDelay(TickDelay);
}
}
The parameters of xQueuReceive
are handler to the Queue, the address where the data is to be stored, and the waiting time incase the Queue is Empty. I have specified the waiting as portMAX_DELAY, that means the task is going to wait forever for the data to become available in the Queue. For this waiting time, this task will be in the suspended State.
Again, if the data is received successfully, xQueueReceive
will return pdTRUE, and we can display the data on the console.
If we want to send the data to the Queue from an ISR, we have to use the interrupt safe version of these functions. Below is an example of How to send the data to the Queue from an ISR
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &Rx_data, 1);
int ToSend = 123456789;
if (Rx_data == 'r')
{
/* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
it will get set to pdTRUE inside the interrupt safe API function if a
context switch is required. */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueSendToFrontFromISR(SimpleQueue, &ToSend, &xHigherPriorityTaskWoken) == pdPASS)
{
HAL_UART_Transmit(huart, (uint8_t *)"\n\nSent from ISR\n\n", 17, 500);
}
/* Pass the xHigherPriorityTaskWoken value into portEND_SWITCHING_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
then calling portEND_SWITCHING_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling
portEND_SWITCHING_ISR() will have no effect */
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}
}
xQueueSendToFrontFromISR
will send the data to the front of the Queue. All the data, which is already available in the queue, will shift back and next time if we read the Queue, we will get this particular data.
Also note that there is no waiting time here. So if the Queue is full, the function will simply timeout, as in the ISR, we can’t afford to wait for the space to become available in the Queue.
Below Shown is the result of this code.
Structured Queue
Like I mentioned in the beginning, if we want to send the different data types, we have to use the Structured Queue.
First of all create the handler for this Queue
/**************** QUEUE HANDLER *****************/
xQueueHandle St_Queue_Handler;
We need to create a structure which can store all the data types that we want to use. I will call it my_struct
/**************** STRUCTURE DEFINITION *****************/
typedef struct {
char *str;
int counter;
uint16_t large_value;
} my_struct;
Next, inside the main function, create the Queue
/***** create QUEUE *****/
St_Queue_Handler = xQueueCreate(2, sizeof (my_struct));
if (St_Queue_Handler == 0) // if there is some error while creating queue
{
char *str = "Unable to create STRUCTURE Queue\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
else
{
char *str = "STRUCTURE Queue Created successfully\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
Here I have created a Queue which can store 2 elements of my_struct data type. Again if there is some error while creating Queue, the xQueueCreate
will return 0, or else it will return any other value.
We will now send the data to this Queue from a sender Task
void Sender1_Task (void *argument)
{
my_struct *ptrtostruct;
uint32_t TickDelay = pdMS_TO_TICKS(2000);
while (1)
{
char *str = "Entered SENDER1_Task\n about to SEND to the queue\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
/****** ALOOCATE MEMORY TO THE PTR ********/
ptrtostruct = pvPortMalloc(sizeof (my_struct));
/********** LOAD THE DATA ***********/
ptrtostruct->counter = 1+indx1;
ptrtostruct->large_value = 1000 + indx1*100;
ptrtostruct->str = "HELLO FROM SENDER 1 ";
/***** send to the queue ****/
if (xQueueSend(St_Queue_Handler, &ptrtostruct, portMAX_DELAY) == pdPASS)
{
char *str2 = " Successfully sent the to the queue\nLeaving SENDER1_Task\n\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str2, strlen (str2), HAL_MAX_DELAY);
}
indx1 = indx1+1;
vTaskDelay(TickDelay);
}
}
Before sending the data to the Queue, we must allocate the memory to the structure. And to do that, we use the function pvPortMALLOC
.
After allocating the memory, Load the data into the pointer to this struct.
Next, we will send this data to the Queue by passing it’s address in the parameter.
void Receiver_Task (void *argument)
{
my_struct *Rptrtostruct;
uint32_t TickDelay = pdMS_TO_TICKS(3000);
char *ptr;
while (1)
{
char *str = "Entered RECEIVER Task\n about to RECEIVE FROM the queue\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
/**** RECEIVE FROM QUEUE *****/
if (xQueueReceive(St_Queue_Handler, &Rptrtostruct, portMAX_DELAY) == pdPASS)
{
ptr = pvPortMalloc(100 * sizeof (char)); // allocate memory for the string
sprintf (ptr, "Received from QUEUE:\n COUNTER = %d\n LARGE VALUE = %u\n STRING = %s\n\n\n",Rptrtostruct->counter,Rptrtostruct->large_value, Rptrtostruct->str);
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, strlen(ptr), HAL_MAX_DELAY);
vPortFree(ptr); // free the string memory
}
vPortFree(Rptrtostruct); // free the structure memory
vTaskDelay(TickDelay);
}
}
We will create another pointer to struct in the receiver function to store the received data.
After receiving the data, we will free the memory allocated by the sender Task using vPortFREE
function.
The result for the above code is shown below