How to do a FIFO

Hi guys, I'm new in the Arduino world and im first trying to know well the language. I was asked to do a FIFO; starting with an array of 4 strings, the program will ask the user values and show them. It will keep asking values indefinitely and placing them in a FIFO queue.

I start trying with an array of ints; first of all I declared an Array of four 0 and I managed to ask the user the first value and place it on the first spot of the array. But I have no idea of how to make that once that I asked another value place it on the first spot of the array and move all the others one position. Any ideas? I suppose that there is a way using a do..while loop or something...

Is this a class assignment? It changes our attitude when you are up front with us.

You only need to keep track of current position in array and check that your pointer doesn't address outside of the array bounds.

That is quite a tough homework assignment, especially if you have to also implement the standard functions of a queue. Apart from adding data at one end, there is retrieving data at the other end and handling overflow and underflow conditions. Queues are generally implemented as a circular buffer with an array and two indexes, one index to the head and one to the tail. It starts to get interesting when, after several adding and retrieving operations, the head of the queue wraps around the top of the array and begins filling it from the bottom.

@OP

Check if you can follow the tutorial of this link on queue; else, ask help for an alternative discussion/example.

Here I am using 2 arrays as a FIFO buffer to collect dat to be used in a Linear Regression formula to predict future readings. The variable magicNumber is the buffer size.

void fDoTrends( void *pvParameters )
{
  const int magicNumber = 24*4;
  double    values[2];
  int       lrCount = 0;
  float     lrData = 0.0f;
  float     DataPoints[magicNumber] = {0.0f};
  float     TimeStamps[magicNumber] = {0.0f};
  double    dpMean = 0.0f; //data point mean
  float     tsAtom = 0.0f;
  float     tsUnit = 0.0f;
  float     tsMean = 0.0f;
  for (;;)
  {
    if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
    {
      if ( lrCount != magicNumber )
      {
        DataPoints[lrCount] = lrData;
        TimeStamps[lrCount] = (float)xTaskGetTickCount() / 1000.0f;
        log_i( "lrCount %d TimeStamp %f lrData %f", lrCount, TimeStamps[lrCount], DataPoints[lrCount] );
        lrCount++;
      } else {
        //shift datapoints collected one place to the left
        for ( int i = 0; i < magicNumber; i++ )
        {
          DataPoints[i] = DataPoints[i + 1];
          TimeStamps[i] = TimeStamps[i + 1];
        }
        //insert new data points and time stamp (ts) at the end of the data arrays
        DataPoints[magicNumber - 1] = lrData;
        TimeStamps[magicNumber - 1] = (float)xTaskGetTickCount() / 1000.0f;
        //calculate data mean, normalize
        for ( int i = 0; i < magicNumber; i++ )
        {
          dpMean += DataPoints[i];
        }
        dpMean /= magicNumber;
        //timestamp mean is ts * (1 / ts_Firstcell - ts_Lastcell[magicNumber]). ts=time stamp
        tsAtom = 1.0f / (TimeStamps[magicNumber - 1] - TimeStamps[0]); // no need to do this part of the calculation every for loop ++
        for (int i = 0; i < magicNumber; i++)
        {
          tsMean = (float)TimeStamps[i] * tsAtom;
          lr.Data( tsMean, DataPoints[i] - dpMean ); // train lr
        }
        lr.Parameters( values );
        //calculate
        tsUnit = TimeStamps[magicNumber - 1] - TimeStamps[magicNumber - 2]; //get the time stamp quantity
        tsUnit += TimeStamps[magicNumber - 1]; //get a future time
        tsUnit *= tsAtom; //setting time units to the same scale
        log_i( "Calculate(x) = %f", lr. Calculate( tsUnit ) ); //calculate next time unit?
        log_i( "Correlation: %f Values: Y=%f and *X + %f ", lr.Correlation(), values[0], values[1] ); // correlation is the strength and direction of the relationship
        lr.Reset();
      }
      //log_i( "DoTheBME280Thing high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
    } //if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete ( NULL );
} //void fDoTrends( void *pvParameters )

If you have an ESP32 or a ESP8266 with OS, here is how to setup a FIFO queue and use a Queue.

global decleration

include "freertos/FreeRTOS.h" 

QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
struct stu_message
{
  char payload [150] = {'\0'};
  String topic ;
} x_message;

setting up the Queue in setup()

 xQ_Message = xQueueCreate( 1, sizeof(stu_message) );

receiving queue data

void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    //wait 'forever' or until a message arrives in the queue
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
      mqttOK = 0;
      xSemaphoreGive( sema_mqttOK );
      if ( px_message.topic == topicRemainingMoisture_0 )
      {
        x_eData.RM0  = String(px_message.payload).toFloat();
      }
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete( NULL );
} // void fparseMQTT( void *pvParameters )

sending queue data

void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', 150 ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
  int i = 0; // extract payload
  for ( i; i < length; i++)
  {
    x_message.payload[i] = ((char)payload[i]);
  }
  x_message.payload[i] = '\0';
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)

The Queue defined is only 1 element long but you may add more queue elements as desired.

See freeRTOS Queue Management docs.

Make an array of size 'N' (in your case, N=4).

Create two variables (uint8_t is fine as the array size is < 256 bytes): call one idxHead and the other idxTail. Initialize both to zero (0).

When you receive a value to enter into the array, place it where idxHead currently points (so '0') and then bump idxHead by one. If you get another entry, again place it where idxHead points (so '1' now). You can keep storing information into your array whereever idxHead points. (Of course, you need to be careful not to overflow the array; if idxHead == 3 your array is full and you can't accept new data.)

Recall the other pointer idxTail: it points to where idxHead was at the start. It points to the first value that was entered. You can use ptrTail to access that first data entry. When you're finished dealing with it, bump ptrTail by one (so it'd be '1'); now you're pointing to the 2nd byte of data received. Process it, bump ptrTail again and now you're pointing at the 3rd entry received. And so on. When you bump ptrTail and it matches ptrHead, you've gone through all the data that was entered.

In this way, data is accessed in the order it was received (e.g. first in, first out.)

This is called a "circular buffer" (google it); you might make the array of size eight (8): When one of the pointers (ptrHead or ptrTail) is incremented and becomes equal to the size of the array, you then set that pointer to zero to "circle" back to the start of the array space. It's possible during execution that ptrTail equals, say, 6 and ptrHead equals 2. When processing ptrTail the increment would then be 7, 8 -> 0, 1, 2.

You could use 4 if you know you'll process data as fast as it comes in. Suppose ptrHead equals 7 and ptrTail equals 0. You get a byte of data so array[ptrHead] = data and you bump ptrHead now; it becomes 8; because it equals the size of the array you reset its value back to zero. Now ptrHead and ptrTail are the same. If another byte comes in before you process the value pointed to by ptrTail you'll overwrite the value there and lose the "oldest" data in the buffer.

So when incrementing the head pointer you need to verify it doing so would cause the head to catch up to the tail. If so, you can have the buffer overrun problem; you want to check and prevent that.

You should be able to process data in a buffer reasonably quickly. If you think the data is "bursty" -- comes in rapidly in quick bursts -- and you might get around to processing the data some time later, consider making the buffer bigger than the burst size. That way you have padding space in your buffer to accept new data while still processing older stuff.

I don't think our professor want us to use either queues or pointers because we he hasn't teach us that yet. I he want us to do the algorithm with only for, ifs, do..while... loops. Thats the hard part. I am trying to implement the solution blackfin posted but I haven't gone very far yet. Will keep asking about this, thank you all!!!

Here's an example of what I talked about:

/*
 * Sketch...:   fifo.ino
 * Target...:   Uno
 * 
 * Notes....:
 */

//includes

//defines
#define BUFF_SIZE       16

//constants

//variables
uint8_t 
    grBuffer[BUFF_SIZE];
uint8_t
    tailIndex,
    headIndex;
uint32_t
    timeCheck,
    timeNow;    

void setup( void )
{
    Serial.begin( 115200 );
           
}//setup

void loop( void )
{
    //see if there's any new data to store
    ReceiveData();

    //update the display once per second
    timeNow = millis();
    if( timeNow - timeCheck >= 1000ul )
    {
        timeCheck = timeNow;

        //if the head and tail are the same, there's no new data
        if( tailIndex == headIndex )
        {
            Serial.println( "No new data received." );
            
        }//if
        else
        {
            //head has been moved away from tail so there's data there
            do
            {
                //print a blurb illustrating the order in which data was received
                Serial.print( "Received character " );
                Serial.print( grBuffer[tailIndex], HEX );
                Serial.print( "h which is '" );
                Serial.write( grBuffer[tailIndex] );
                Serial.println( "'" );

                //bump the tail index
                //if we reach the end of the buffer, circle back to element zero
                tailIndex++;
                if( tailIndex == BUFF_SIZE )
                    tailIndex = 0;

                //keep doing this until the tail is the same as the head
                //at which point we've processed all new data
            }while( tailIndex != headIndex );
            
        }//else
                        
    }//if
    
}//loop

void ReceiveData( void )
{
    //if one of more characters is available...
    if( Serial.available() > 0 )
    {
        //read all characters that are ready
        do
        {
            //read the character
            uint8_t ch = Serial.read();
            //put it in the buffer at the head index
            grBuffer[headIndex] = ch;

            //bump the head index. Like the tail index above,
            //when we reach the end of the buffer, circle back
            //to element zero.
            headIndex++;
            if( headIndex == BUFF_SIZE )
                headIndex = 0;
            
        }while( Serial.available() > 0 );
        
    }//if
    
}//ReceiveData

A FIFO and queue are synonyms. Your OP mentions an array so you need some way of addressing the individual elements of that array. This is an index or, loosely, a pointer.

However, your OP is strangely expressed in that you cannot add items to a queue indefinitely unless you can be sure that for every operation to add an item, an integer in your case, is followed by a remove operation. A practical queue implementation has a finite size and is usually used where the add operation and remove operation are asynchronous.

If you want to implement a working example using the serial monitor you have to have some way of distinguishing between add and remove operations. Say A9 means add the integer 9 to the queue and R means remove the next item from the queue and display it to the user.

However, I would have thought that it would be better to get the basic queue functions working, say add( item) and remove() working first using sample calls in setup() to do that. After that, progress on to developing a user interface to demonstrate it.

But, even before you start coding, you start with a pencil and paper working through examples of adding items and removing items from a small, say 4 element, queue.

You’ve already been given a number of coded examples. This is yet another one but I find good (subject to testing it) because it is a clean and simple FIFO implemented as a circular buffer. The red text is a set of comments: Queue Program In C

But your starting point should not be an existing implementation because you have to first go through understanding the basic FIFO (queue) operations.

You have an array, and you keep an index where new elements are added to the array (The "First In" part), and another index where you take elements out of the array ("First out")

to add a thing, put it in the array at the current IN index, and increment the index. If the index exceeds the size of the array, reset it to 0 (this is what makes the buffer "circular")

To remove a thing, get it form the OUT index, and then increment that (same circular behavior.)

There are a couple of ways to check whether the FIFO is "full" or "empty", and a couple of ways of dealing with those states. I'll leave them as an exercise for the student.

Note that the array can be of any type.

1 Like

@OP

This short tutorial may be helpful for OP to understand how items are added, removed/retrieved, displayed etc. in a linear queue. This tutorial has received inspirations from Post-6, 9, 10, and an example of this book: C Complete Reference By: Herbert Schildt.

1. Linear Queue Definition
A Linear Queue is a list of “linear information”; where the item that is stored first is taken out first. It is a FIFO (First-in First-out). In Fig-1, we have a queue which can store five characters. The queue has been implemented using an array named myData[].

2. Understanding Linear Queue Principles and Operations by Diagrams


Fig-1 is a conceptual view of a queue with no elements inside it. The queue is empty. The spos (storage position) variable always refers an empty location; where, we can insert/store a data/character item. The rpos variable refers a location from where we take out/remove/retrieve the data item that was inserted first. The rpos variable is also known as head/front and the rpos is known as tail/rear. In an empty queue, spos and rpos variables have same numerical values.

Fig-2 shows a queue in which user has inserted a character A. Now, the spos variable has sifted to the right and has assumed value 1. It is pointing a location where next data item could be inserted. The rpos variable keeps the value 0 to indicate that the data item of this location must be taken out first.


Fig-3 show a queue where the user has inserted the character B at the current empty position being pointed by spos variable. Now, the value of spos variable has increased to 2 and is pointing a new empty location.

Fig-4 shows the same queue with another data item - the character C.


Fig-5 shows the queue with the removal/retrieval of data item (character A) from location being pointed by rpos variable. Now, spos variable has assumed value 1 (spos++) to indicate that the next data item must be removed from location as referred by rpos.

Fig-6 shows the queue with the insertion of another data item - the character D.


Fig-7 shows the queue with the removal of the data item (character B) from location pointed by rpos variable. Now, rpos has assumed the value 2.

Fig-8 shows queue with the removal of data item - the character C. Now, the queue holds only one character - the D. If we add one more item in the queue, the queue will be Full and spos variable will assume 6. The following codes could check that the queue is full.

if(spos == 6)
{
    Serial.println("Queue is Full!");
}

If we keep removing items from queue, then at one time rpos variable will also assume 6. This is the "Empty Condition" of queue. The following codes will check the emptiness of the queue:

if(rpos == spos)
{
    Serial.println("Queue is EMpty!");
}

3. Assume that we have qstore() function to place a data item into the queue in an empty position. We have qretrieve() function to retrieve/remove a data item from the queue. The following Table-1 shows the effect of the operations we carried out on the queue of Step-2.
Action Meaning Contents of Queue Comments
qstore(‘A’) A is placed in queue A spos = 1, rpos = 0 (Fig-2)
qstore(‘B’) B is placed in queue AB spos = 2, rpos = 0 (Fig-3)
qstore(‘C’) C is placed in queue ABC spos = 3, rpos = 0 (Fig-4)
qretrieve() Take out A from queue BC spos = 3, rpos = 1 (Fig-5)
qstore(‘D’) D is place in queue BCD spos = 4, rpos = 1 (Fig-6)
qretrieve() Take out B fro queue CD spos = 4, rpos = 2 (Fig-7)
qretrieve() Take out C from queue D spos = 4, rpos = 3 (Fig-8)

4. A simple Sketch using C-codes to demonstrate the principles of queue operation of Step-2.

char myData[6] = "";   //all locations hold 0s (null charcaters)
byte spos = 0;  //storage postion of item
byte rpos = 0;  //retrieve position of item
char myChar[5];
char z;

void setup()
{
  Serial.begin(9600);
  Serial.println("Eneter (from InputBox) P to Place, R to Remove, D to Display, Q to Quit: ");
}

void loop()
{
  byte y = Serial.available();
  if (y != 0)
  {
    byte m = Serial.readBytesUntil('\n', myChar, 5);
    myChar[m] = '\0';    //insert null character
    z = myChar[0];
    memset(myChar, 0, 5);
  }
  switch (z)
  {
    case 'P':
      {
        Serial.println("Placing an item in queue: ");
        z = NULL;
        qstore('A');
        break;
      }
    case  'R':
      {
        qretrieve();
        break;
      }
    case  'D':
      {
        Display();
        z = NULL;
        break;
      }
    case  'Q':
      {
        break;
      }
  }
}

void qstore(char x)
{
  myData[spos] = x;
  spos++;
}

void qretrieve()
{

}

void Display()
{
  Serial.print("Displaying the contents of queue: ");
  Serial.println(myData);
  rpos++;
}

5. Upload the sketch of Step-4 in UNO.
6. Press RESET button of UNO.
7. Follow the menu of Serial Monitor.
(1) Choose Newline option for the Line ending tab of Serial Monitor.
(2) Enter P (to place an item in the queue)) in the InputBox and then click on Send button,
(3) Enter A (the item to be placed in queue) in the InputBox and then click on Send button.
(4) Enter D (to display contents of queue) in the InputBox and then click on Send button.
(5) Check that the OutputBox of Serial Monitor shows messages as in Fig-9.


Figure-9:
8. Study the program codes in consultation with the diagrams of Step-2.
9. Add C-codes with the sketch of Step-4 to implement full queue operations of Step-3.

char myData[6] = "";   //all locations hold 0s (null charcaters)
byte spos = 0;  //storage postion of item
byte rpos = 0;  //retrieve position of item
char myChar[5];
char z;

void setup()
{
  Serial.begin(9600);
  Serial.println("Eneter (from InputBox) P to Place, R to Remove, D to Display, Q to Quit: ");
}

void loop()
{
  z = getChar(); //get a single Newline terminated character from Serial Monitor
  switch (z)
  {
    case 'P':
      {
        Serial.print("Placing an item in queue: ");
        z = NULL;
        char p = getChar();
        Serial.println(p);
        qstore(p);
        break;
      }
    case  'R':
      {
        qretrieve();
        break;
      }
    case  'D':
      {
        Display();
        z = NULL;
        break;
      }
    case  'Q':
      {
        break;
      }
  }
}

void qstore(char x)
{
  if(spos == 5)
  {
    Serial.println("Queue is Full!");
    return;
  }
  myData[spos] = x;
  spos++;
}

void qretrieve()
{
  Serial.println("Removeing/Retrieveing first item from queue.");
  if(rpos == spos)
  {
    Serial.print("Queue is Empty!");
    return;
  }
  (void)myData[rpos];  //retrieve and discard
  myData[rpos] = ' ';
  rpos++;              //move pointer to the next position
}

void Display()
{
  Serial.print("Displaying the contents of queue: ");
  Serial.println(myData);
}

char getChar()  //get a single Newline terminated character from Serial Monitor
{
  while (1)
  {
    byte y = Serial.available();
    if (y != 0)
    {
      byte m = Serial.readBytesUntil('\n', myChar, 5);
      myChar[m] = '\0';    //insert null character
      z = myChar[0];
      memset(myChar, 0, 5);
      return (z);
    }
  }
}


Figure-10:
10. Create your own sketch to make a queue of your requirements.
11. Re-write C-codes of Step-4, 9 using C++ codes to realize advantage of C++ codes.

That Queue (FIFO) implementation in post #11 appears to be a linear buffer, not a circular buffer.
Take figure-7 in that post. In a linear buffer implementation, the queue is full when an item is inserted into position 4. In a circular buffer implementation, the head of the queue wraps around and can use also the currently empty positions 0 and 1.
Having said that, a linear buffer is easier to implement but I am wondering how all this helps the OP who now has a considerable amount of code samples and techniques to work through.

Hi guys, Here is what I've done until now but I have some crazy errors I would want to discuss with you.

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.println("Want to include a value? Y/N");
  delay (2000);
  if (Serial.available()>0) 
  {
   int myArray[4];
   int idxFirst = 0;
   int n=0; 
   char action = Serial.read();
   
   if ((action == 'Y' && idxFirst<4)|| (action == 'y' && idxFirst<4)){//loop that keeps adding values to te array
     Serial.println("Enter value: ");
     delay(2000);
     if (Serial.available())
     {
       int Value = Serial.parseInt();
       Serial.println(Value);
       miArray[idxFirst]=Value;
       idxFirst++;
     }
   }
    //if ((action == 'N' && idxLast<4)|| (action == 'n' && idxLast<4)) {
    else {
      Serial.println("Out values: ");
      int idxLast = 0;
      do {
        int FinalValue = myArray[idxLast];
        delay(1000);
        Serial.println(FinalValue);
        idxLast++;
      } while (idxLast<4);
    
      Serial.print("Finished");
      delay(1000);
      exit(0);
    }
  }
}

The first loop seems to work fine but in the second one, the one that returns the out values, I don't know what's wrong but it returns me the last value that I enter, then a 0, and then two strange numbers: -2296 and -29184.
When I manage to do this I will try to do the same but with an array of n values and with strings.

@Blackfin , I will study your solution in case I have any idea and it seems that I will have to learn about queues too. Thank you.

miArray[idxFirst]=Value; Please post code that at least has a chance of compiling

sorry :frowning:

void setup()
{
  Serial.begin(9600);
}

void loop()
{[color=#222222
  Serial.println("Want to include a value? Y/N");
  delay (2000);
  if (Serial.available()>0) 
 
   int myArray[4];
   int idxFirst = 0;
   int n=0; 
   char action = Serial.read();
   
   if ((action == 'Y' && idxFirst<4)|| (action == 'y' && idxFirst<4)){//loop that keeps adding values to the array
     Serial.println("Enter value: ");
     delay(2000);
     if (Serial.available())
     {
       int Value = Serial.parseInt();
       Serial.println(Value);
       myArray[idxFirst]=Value;
       idxFirst++;
     }
   }
    //if ((action == 'N' && idxLast<4)|| (action == 'n' && idxLast<4)) {
    else {
      Serial.println("Out values: ");
      int idxLast = 0;
      do {
        int FinalValue = myArray[idxLast];
        delay(1000);
        Serial.println(FinalValue);
        idxLast++;
      } while (idxLast<4);
    
      Serial.print("Finished");
      delay(1000);
      exit(0);
    }
  }
}

It keeps doing the same any way; any idea why??

@OP

Having said that, a linear buffer is easier to implement but I am wondering how all this helps the OP who now has a considerable amount of code samples and techniques to work through.

Just a suggestion - if you haven't already, develop a sketch just to prompt for a string and display it. Add the array/queue stuff once this basic functionality works.

YMMV

Just to get you started I did this with minimal modifications to your original structure:

void setup()
{
  Serial.begin(9600);
  Serial.setTimeout(10000) ; // 10 seconds to enter an integer
}

void loop()
{
  Serial.println("Want to include a value? Y/N");
  delay (2000);
  if (Serial.available() > 0) {

    int myArray[4];
    int idxFirst = 0;
    int n = 0;
    char action = Serial.read();

    while ((action == 'Y' && idxFirst < 4) || (action == 'y' && idxFirst < 4)) { //loop that keeps adding values to the array
      Serial.println("Enter value: ");
      while ( ! Serial.available() ) ; // wait here until we get a value
      int Value = Serial.parseInt();
      Serial.println(Value);
      myArray[idxFirst] = Value;
      idxFirst++;
    }
    //if ((action == 'N' && idxLast<4)|| (action == 'n' && idxLast<4)) {

    Serial.println("Out values: ");
    int idxLast = 0;
    do {
      int FinalValue = myArray[idxLast];
      delay(1000);
      Serial.println(FinalValue);
      idxLast++;
    } while (idxLast < 4);

    Serial.print("Finished");
    delay(1000);
    exit(0);
  }
}

Serial is not very intuitive to use. Have a look here for a tutorial: Serial Input Basics - updated - Introductory Tutorials - Arduino Forum

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.