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.

Reference: Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

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.

Simple Queue Result
Simple Queue Result

Check out the Video Below



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

structured Queue result
structured Queue result

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

8 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
keyboard_arrow_up