FreeRTOS#2 Queue concept in FreeRTOS

 

In this example, queue concept is discussed so that data transfer between the two task can be successfully done without any problem.
problems: let say there is a global variable defined in the code and there are two task want to read/modify the same variable. As the variable read/modify is not an atomic operation, problem occurs when once thread is reading/modifying the variable and the other trying to read/modify the same variable.
To avoid the above problem either, one can make sure that the variable read/modify operation is atomic ( that is not always guaranteed over different embedded platforms) or use the Queue concept. Queue is nothing but just a buffer where data is placed(using xQueueSend) by one task and can be read(using xQueueReceive) by the other task. For more information on queue, i recommend reading the RULES FOR HANDLING DATA USING QUEUE and comments in the code section.
#include <Arduino.h>
#include <Arduino_FreeRTOS.h>
#include <queue.h>

static const int ledPin = 13;

//defining the queue parameters
static const uint8_t msg_queue_len =5;
QueueHandle_t msg_queue;
/**
 * RULES FOR HANDLING DATA USING QUEUE
 * once you read the data from the queue it will be lost from the queue.
 * 
 * data writing to the queue must be faster than the reading data from the queue
 * to avoid queue empty problem
 * 
 * if the data reading and data writing from/to the queue are not synchronized 
 * then the queue empty/full problem may occur
 * 
 * This is why xQueueReceive function returns pdFALSE if the queue is empty
 * and xQueueSend function returns pdFalse in case the queue is full.
**/

/**
 * this function receives data from queue in delay variable
 * and puts the processor to in wait state for delay (in ms) time
 * 
 * 
 * every delay ms this function checks for the data in queue
**/
void toggleLED(void *parameter){
  (void)parameter;

  uint16_t delay = 0;
  uint16_t localDelay = 0;
  while (1){
    if(xQueueReceive(msg_queue,&delay,10) == pdTRUE){     // receiving the timeDelay variable value from queue in delay variable
      localDelay = delay;
      Serial.println("Data received successfully!");
    }else{
      Serial.println("Queue is empty!");
    }

    PORTB ^= (1<<5);                                      // turn LED-ON/OFF
    vTaskDelay(localDelay/portTICK_PERIOD_MS);            // wait for localDelay_ms
  }
}


/**
 * this function sets the blink time of LED 
 * by writing the delay amount in queue using the timeDelay variable
 * 
 * every second the data is being updated in queue.
**/
void setLEDBlinkDelay(void *parameter){
  (void)parameter;

  uint16_t timeDelay=0;
  while (1){
    timeDelay = 500;
    if(xQueueSend(msg_queue, &timeDelay, 10) == pdTRUE){   // sending the LED delay time in queue from timeDelay variable
      vTaskDelay(10/portTICK_PERIOD_MS);               // waiting for 10000 ms
    }else{
      Serial.println("Queue is full");
    }
  }
}

void setup() {
  // put your setup code here, to run once:
  DDRB |= (1<<5);                                         // setting the pin 13 or PORTB5 as output
  Serial.begin(9600);

  //creating the task
  xTaskCreate(toggleLED, "LED Toggle", 128, NULL, 1, NULL);
  xTaskCreate(setLEDBlinkDelay, "set LED delay", 128, NULL, 1, NULL);

  msg_queue = xQueueCreate(msg_queue_len, sizeof(int));
}

void loop() {
  // put your main code here, to run repeatedly:
}
There are two task running concurrently and calling the functions toggleLED and setLEDBlinkDelay. toggleLED() is a function that reads the amount of delay from queue and blink the LED, while setLEDBlinkDelay() function sets the delay length and write it the the queue.
As it is mentioned above that if data read and write operations are not synchronized then the queue empty/full problem may occur. To demonstrate the effect of non-synchronization, in the above example data is being written to the queue nearly every 10secs interval, while the data being read from the queue is nearly every 500ms. Hence, while reading data, queue empty problem occurs as shown below. 
OUTPUT:
02:19:49.831 > Data received successfully!
02:19:50.531 > Queue is empty!
02:19:51.223 > Queue is empty!
02:19:51.923 > Queue is empty!
02:19:52.624 > Queue is empty!
02:19:53.319 > Queue is empty!
02:19:54.020 > Queue is empty!
02:19:54.716 > Queue is empty!
02:19:55.415 > Queue is empty!
02:19:56.107 > Queue is empty!
02:19:56.805 > Queue is empty!
02:19:57.510 > Queue is empty!
02:19:58.207 > Queue is empty!
02:19:58.907 > Queue is empty!
02:19:59.607 > Queue is empty!
02:20:00.305 > Queue is empty!
02:20:00.846 > Data received successfully!
02:20:01.530 > Queue is empty!
02:20:02.228 > Queue is empty!
02:20:02.924 > Queue is empty!
02:20:03.627 > Queue is empty!
02:20:04.312 > Queue is empty!
02:20:05.023 > Queue is empty!
02:20:05.720 > Queue is empty!
02:20:06.420 > Queue is empty!
02:20:07.106 > Queue is empty!
02:20:07.807 > Queue is empty!
02:20:08.508 > Queue is empty!
02:20:09.213 > Queue is empty!
02:20:09.912 > Queue is empty!
02:20:10.611 > Queue is empty!
02:20:11.140 > Data received successfully!
02:20:11.823 > Queue is empty!
02:20:12.536 > Queue is empty!

For the first time when the data is written to the queue and it is successfully read by the toggleLED() at 02:19:49.831 time stamp and hence "Data received successfully" string is displayed on the serial monitor, right after the read operation the data from the queue is erased automatically. As the data is written to the queue at around 02:20:00.846 time stamp and being read at 02:19:50.531 time stamp it is obvious that the queue is empty and hence "Queue is emptly" is is displayed on the serial monitor.

if we only change the data writing delay form 10sec to 10ms to the queue and left the reading interval at every 500ms then it should show that the  "Queue is Full" on the serial monitor. 
OUTPUT:
02:38:59.788 > Queue is full
02:38:59.818 > Data received successfully!
02:38:59.973 > Queue is full
02:39:00.143 > Queue is full
02:39:00.313 > Queue is full
02:39:00.348 > Data received successfully!
02:39:00.503 > Queue is full
02:39:00.673 > Queue is full
02:39:00.840 > Queue is full
02:39:00.875 > Data received successfully!
02:39:01.030 > Queue is full
02:39:01.200 > Queue is full
02:39:01.370 > Queue is full
02:39:01.405 > Data received successfully!
02:39:01.560 > Queue is full
02:39:01.730 > Queue is full
02:39:01.900 > Queue is full
02:39:01.930 > Data received successfully!
02:39:02.090 > Queue is full
02:39:02.260 > Queue is full
02:39:02.430 > Queue is full
02:39:02.460 > Data received successfully!
02:39:02.615 > Queue is full
02:39:02.785 > Queue is full
02:39:02.960 > Queue is full
02:39:02.990 > Data received successfully!
02:39:03.145 > Queue is full
02:39:03.320 > Queue is full
02:39:03.485 > Queue is full
02:39:03.520 > Data received successfully!

For better software embedded practices, it is not necessary to synchronize the data writing and reading to/from the queue but it is necessary and recommended to always check whether is queue is empty or full.

Comments

  1. If you find this tutorial helpful, please leave a comment below.

    ReplyDelete

Post a Comment