Created NEW library t simulate a CircularBuffer aka software Rolodex

BriComp

Well-known member
EDIT: I have changed the name of the library to UniversalRingBuffer. It is stored/available here. Where CircularBuffer is stated "insert" RingBuffer. Nothing other than the name has changed except SaveRBInfo and SaveRBInfo rather than CB.

I have just created a new Library here which creates/manages a CircularBuffer aka software Rolodex.
It can be used for any type of data as long as each entry is of the same physical size.

I will post the ReadMe below, but would appreciate your help. Is CircularBuffer the correct/appropriate name for the library?
***CircularBuffer***

This library is designed to offer a Circular buffer control aka **Rolodex**.
Although other libraries exist they are usually limited to storing standard numeric
types, eg uint32_t, int etc. With this library you can use any type you wish, create
your own type in a struct and use that. No problem.

The library manages the index into the array/storage structure. It is NOT limited to
storage in an array in memory but could be used to store data in EEPROM, SD card or
any other storage medium.

If you examine the Example **TestCircularBuffer** I have used an array of char, just because
it's easy to track the addition of ASCII characters to demonstrate usage.

You start off by defining your storage medium. As I said before it can be used for
any storage medium but for the purposes of describing it's usage I will use arrays of char
by way of examples.

Then create an instance of the **CircularBuffer** passing the Maximum size of the array.
By maximum size I mean maximum index, NOT the storage size i.e. `data[ maximumSize]`.

AddDataToBuffer()

Ok, that's it we can now start storing data into our "**Rolodex**" type storage medium.
To add data just use **AddDataToBuffer()**. This function returns the index of the array
where the data is to be stored. So usage is **buffer[ cBuf.AddDataToBuffe() ] = dataChar**.

You can keep on adding data to your hearts content, storing many more items than the
original array could accommodate. Bear in mind that once you have exceeded the size of the
original array that the data will roll over and overwrite the original data. The array index
returned by **AddDataToBuffer()** will always be a legal array location. Think of this like
having completely filled your Rolodex and have to remove a card to add new information.
The library works in a LIFO manner.

ReadFromHead(), ReadFromTail()

Having stored the data you will want to do something with it. Retrieval of the data starts
by using the functions either **ReadFromHead()** or **ReadFromTail()**. Head represents the
last entered data and tail represents the first entered data. **ReadFromHead()** and
**ReadFromTail()** both return an index into the array of data. they can be used like:-
`Serial.print( buffer[ cBuf.ReadFromHead() ] );` where the data is returned from the array
and printed.

ReadNext()

In order to continue retrieving data from the store (array) we now use the function
**ReadNext().** This returns the next array index in the sequence and is used in the
same manner as the previously described functions **ReadFromHead()** and **ReadFromTail()**,
those having set the direction for subsequent **ReadNext()**s. As you can imagine it would
be possible to read past head or tail which would give rise to an erroneous condition.
To ensure that this does not happen the boolean variable **badCommand** is set to true
and the index returned by **ReadNext()** is NOT incremented or decremented, i.e. the
previous value is returned.

The **badCommand** is reset to false by using either **ReadFromHead()** or **ReadFromTail()** again.

MaxBufferSize(), currentBufferLength()

In order to help with data management there are two functions which return array information.
these are **MaxBufferSize()**, which effectively returns the original maximum array index passed
when the instance of **CircularBuffer** was created, and **currentBufferLength()** which returns the
number of entries into the array. If the system has started to roll over then **currentBufferLength()**
will be the same as **MaxBufferSize()**.

Obviously before any data has been added **currentBufferLength()** will be zero.

SaveCBInfo(), RestoreCBInfo()

Should you wish to power down your system, but not lose your Circular Buffer database, then you can use the function **SaveCBInfo()**. First start by saving your Array Data away then get the **CircularBuffer**
internal data with `CircularBuffer::infoType cbData = SaveCBData();` then save that cbData away.

To recover your Circular Buffer restore your array data and then read in your cbData and then
use the function **RestoreCBInfo( cbData )** to put the data back. This function attempts to check
the validity of the data and return bool if the data looks legal. If false is returned the data is
not valid and the **CircularBuffer** internal variables will NOT have been updated.
The following sketch is an example of it's usage.
Code:
/*
    Name:       aaTestCircularBuffer.ino
    Created:	01/05/2023 16:01:48
    Author:     Robert E Bridges (C) 4 May 2023
*/

#include "Arduino.h"
#include <CircularBuffer.h>
#include <EEPROM.h>

#define CircularBufferArrayEEPROMAddr 0

CircularBuffer::infoType data;

uint32_t currentBufferSize;

#define arrayBufferSize 20
char buffer[arrayBufferSize];

CircularBuffer cBuf(arrayBufferSize);

void setup()
{
    Serial.begin(9600);
    while (!Serial && millis() < 5000);
    Serial.println(F("Putting some data uint32_to Circular Buffer, but NOT filling it."));
    Serial.println(F("Adding to buffer with A to K inclusive"));

    for (char i = 'A'; i < 'L'; i++) {
        buffer[cBuf.AddDataToBuffer()] = i;
    }

    Serial.println(F("Reading from circular buffer - from Head to Tail (LIFO)"));
    Serial.print( buffer[ cBuf.ReadFromHead() ] );

    for (uint32_t i = 1; i < cBuf.currentBufferLength(); i++) { // wouldn't normally use "i < cBuf.BufferLength();" just used to demonstrate use
        Serial.print( buffer[ cBuf.ReadNext() ]);        // better to use next example. The limit is not repeatedly evaluated.
    }
    Serial.println();

    Serial.println(F("Reading from circular buffer in opposite direction - from Tail to Head (FIFO)"));
    Serial.print( buffer[ cBuf.ReadFromTail() ] );

    currentBufferSize = cBuf.currentBufferLength();
    for (uint32_t i = 1; i < currentBufferSize; i++) {
        Serial.print( buffer[ cBuf.ReadNext() ] );
    }
    Serial.println();

    Serial.println(F("Adding more data to just fill the buffer."));
    Serial.println(F("Adding extra to buffer with L to U inclusive"));

    for (char i = 'L'; i < 'U'; i++) {
        buffer[cBuf.AddDataToBuffer()] = i;
    }

    Serial.print( buffer[ cBuf.ReadFromHead() ] );

    currentBufferSize = cBuf.currentBufferLength();
    for (uint32_t i = 1; i < currentBufferSize; i++) {
        Serial.print( buffer[ cBuf.ReadNext() ] );       
    }

    Serial.println();
    Serial.print( buffer[ cBuf.ReadFromTail() ] );

    for (uint32_t i = 1; i < cBuf.currentBufferLength(); i++) {
        Serial.print( buffer[ cBuf.ReadNext() ] );
    }
    Serial.println();

    Serial.println(F("Adding extra to go uint32_to roll over."));
    Serial.println(F("Adding extra to buffer with U to Z inclusive" ));

    for (char i = 'U'; i < '['; i++) {
        buffer[cBuf.AddDataToBuffer()] = i;
    }
    Serial.print( buffer[ cBuf.ReadFromHead() ] );
    currentBufferSize = cBuf.currentBufferLength();
    for (uint32_t i = 1; i < currentBufferSize; i++) {
        Serial.print( buffer[ cBuf.ReadNext() ] );
    }
    Serial.println();
    Serial.print( buffer[ cBuf.ReadFromTail() ] );
    for (uint32_t i = 1; i < currentBufferSize; i++) {
        Serial.println( buffer[cBuf.ReadNext() ] );
    }
    Serial.println();
    
    Serial.println("Save away Circular Buffer to EEPROM");
    data=cBuf.SaveCBInfo();
    EEPROM.put(CircularBufferArrayEEPROMAddr, buffer);
    EEPROM.put(CircularBufferArrayEEPROMAddr + sizeof(buffer), data);

    Serial.println("Get Back Circular Buffer from EEPROM");
    EEPROM.get(CircularBufferArrayEEPROMAddr, buffer);
    EEPROM.get(CircularBufferArrayEEPROMAddr + sizeof(buffer), data);
    cBuf.RestoreCBInfo(data);
}

void loop()
{


}
 
Last edited:
Any thoughts on a suitable name?
Possibly RingBuffer?

Since Simplicity is the aim SimpleCircuitBuffer/SimpleRingBuffer or UnComplicated?

Since it will work with any type of data UniversalRingBuffer / UniversalCircularBiuffer?
 
Last edited:
Yea, I am inclining towards RingBuffer (with initials). I think it seems to better describe the function.
 
@tonton81 made this one that seems 5 years back: github.com/tonton81/Circular_Buffer

So the name is well used ...

Code:
Circular_Buffer
This is pretty much an advanced version of a circular buffer that follows the STL naming conventions of std::queue,deque,vector, etc...

It supports multiple circular buffer creations without the use of reallocation, new, or malloc. The system uses a template for the configuration of the buffers for the class.

Both circular buffers and circular arrays all support FIFO, LIFO, and MIXED (FIFO+LIFO); This can lead you to design a priority queue system where front entries for priority items and back entries for least priority.

The library is capable of inserting and reading from both the front and the back of the queue.

The buffer system this library supports is not just for ring buffer usage. There is an even more powerful array system. Both buffer types have advanced feature sets of their own we will talk about.

Templates and feature rich including some basic stats ...
 
@tonton81 made this one that seems 5 years back: github.com/tonton81/Circular_Buffer

So the name is well used ...

Code:
Circular_Buffer
This is pretty much an advanced version of a circular buffer that follows the STL naming conventions of std::queue,deque,vector, etc...

It supports multiple circular buffer creations without the use of reallocation, new, or malloc. The system uses a template for the configuration of the buffers for the class.

Both circular buffers and circular arrays all support FIFO, LIFO, and MIXED (FIFO+LIFO); This can lead you to design a priority queue system where front entries for priority items and back entries for least priority.

The library is capable of inserting and reading from both the front and the back of the queue.

The buffer system this library supports is not just for ring buffer usage. There is an even more powerful array system. Both buffer types have advanced feature sets of their own we will talk about.

Templates and feature rich including some basic stats ...

Yes but could I use it with an array of struct?
It seems to handle standard types.
 
Yes but could I use it with an array of struct?
It seems to handle standard types.

IIRC - yes.

ReadMe text notes: "There is an even more powerful array system."

Github name text:
Code:
Circular_Buffer 
Circular Buffer/ Circular Array

Refers to 'circular array's in the function notes.

Though if those words meet the expected use at hand ...

<edit>: just saw the word Struct ... so that may be outside the scope - but seems it allowed arbitrary size.
 
If you have to make a function call (ReadNext) to access each successive element, I would consider this to be more of a list (a queue, since it looks like new data is only added at the end) rather than a buffer/array.
 
I would consider this to be more of a list (a queue, since it looks like new data is only added at the end) rather than a buffer/array.
When the end of the array is reached it starts again and loops rounds, so a true RING. It doesn't just fill up the array.
 
IIRC - yes.

ReadMe text notes: "There is an even more powerful array system."

Github name text:
Code:
Circular_Buffer 
Circular Buffer/ Circular Array

Refers to 'circular array's in the function notes.

Though if those words meet the expected use at hand ...

<edit>: just saw the word Struct ... so that may be outside the scope - but seems it allowed arbitrary size.

I use such a ring buffer I have created to keep a record of events and occurrences/outcomes.

By way of an example I have a system where two MCUs are communicating. MCUa is commanding MCUb.

MCUb takes a command, stores the event in a struct shown below:
Code:
struct eventType {
    uint32_t                    time;   // Stores time of the event or error
    commandOrErrorType what;  // stores whether command or error
    eventOrErrorType       event; // stores the event/error
}
MCUb will start executing tasks required by the MCUa command recording the sub tasks and any errors in the RingBuffer.

In this way it is possible to review the contents of the ring buffer determine what might have caused any error in the remote system.

It is necessary to have RingBuffer as the data array could not possibly be allowed to expand forever. Simply use a RingBuffer which will likely encompass a suitable time duration to show any errors.

In fact my complete system consists of a MASTER communicating with a REMOTE. Nearly all REMOTES have their own REMOTES so you can see that some form of recording is required in able to be able to track errors.
 
That's not what I consider to be a "true" ring/circular buffer. If you can't use each successive element without a function call or individual element lookup, it's a list.

A ring buffer of size X would be implemented using X*2 storage space. Each write gets stored to two locations (A and A+X) and the current read/write pointers loop from X to 0. This ensures there is always X contiguous elements accessible, no matter where the current pointers are.
 
That's how my system works, data is entered until the array fills up, then data entry recommences from zero.

Head points to the next free space, Tail points to the first data entry.

When the system starts Head=Tail=0. As data is entered Head increments. When Head gets to be larger than the array it goes to 0 and Tail increments.
For each subsequent entry Head and Tail increment. When either get to the end of the array they go to zero.
 
If it's of length X and full of data with an arbitrary read position, and I want to DMA Y items out of it, how do I get a pointer that is guaranteed to address that many items? I can't just take the address of the item returned by ReadNext because it will wrap around at some point... ReadNext has to be called for each individual item. With that limitation, it's basically the same as a one-way linked list.
 
If it's of length X and full of data with an arbitrary read position, and I want to DMA Y items out of it, how do I get a pointer that is guaranteed to address that many items? I can't just take the address of the item returned by ReadNext because it will wrap around at some point... ReadNext has to be called for each individual item. With that limitation, it's basically the same as a one-way linked list.

What BriComp has implemented may not meet the needs of your DMA process, but it is what is commonly referred to as a ring buffer. From Wikipedia...

In computer science, a circular buffer, circular queue, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. This structure lends itself easily to buffering data streams.
 
Back
Top