RobTillaart/FRAM_I2C

Ringbuffer

crami opened this issue · 18 comments

crami commented

Hi Rob

I'm very interested in using FRAM as ring-buffer. Have you already started coding in that direction?

Regards

Matthias

Hi Matthias,

Thanks for opening this issue. I have not taken time to write a ringbuffer yet. Mostly these jobs get done when I need certain functionality in a project myself. Do you have specific requirements or data types in mind?

As I am almost through my open issues and PR's etc, I can give it a try this evening.

Did you have a look at - FRAM_logging.ino ?
it is a basic ring buffer for logging

crami commented

Sorry had not the chance to check sooner.

I like to store an arbitrary struct into the ring-buffer like this:

struct GPSBuffer {
  float lat;
  float lon;
  float speed;
  float alt;
};

I like to be able to store into the buffer and read back until it is empty (FIFO)

No problem, I have enough things to do

As you know the size of the object, the ringbuffer should be a multiple of this size preferably.
You can test this by writing two functions that do the "serialize and deserialize" to the ring buffer.

Something like this, can you try?

bool RB_write(GPSBuffer * data)
{
   //  test if there is space 
  if (fb.size() - fb.count() < sizeof(GPSBuffer)) return false;
  uint8_t *p = (uint8_t) data;
  for (int i = 0; i < sizeof(GPSBuffer); i++)  framRB.write(*p++);
  return true;
}

bool RB_read(GPSBuffer * data)
{
   //  test if there is enough data
  if (fb.count() < sizeof(GPSBuffer)) return false;
  uint8_t *p = (uint8_t) data;
  for (int i = 0; i < sizeof(GPSBuffer); i++)  *p++ =  framRB.read();
  return true;
}

If this work I can extend the API to support something like this.

crami commented

Code looks really promising. I started writing some code around it, but hat no time to test it jet.
Am I correct that size and count should be fb.size and fb.count?

It would be cool if the load and save functions would read memory at the beginning or end of the FRAM where all the ringbuffer parameters are stored (size, head, tail, "signature to check it ring buffer exists") so it can be initialized from there on boot.

Code looks really promising.

Thanks, I am working on direct support for objects in the class. It is not that different (need a length param).
An update will be later this evening.

Am I correct that size and count should be fb.size and fb.count?

Yes, fb.size() and fb.count() ==> updated in the example above.

It would be cool if the load and save functions would read memory at the beginning or end of the FRAM where all the ringbuffer parameters are stored (size, head, tail, "signature to check it ring buffer exists") so it can be initialized from there on boot.

I am thinking of that, but first the interface should be functional stable.
Otherwise save/load will break with every update.

In my idea the metadata of the ringbuffer must be allocated "inside the ringbuffer-space"
That would allow to have multiple independent ringbuffers in a single FRAM.
Only thing the user needs to remember is the size in bytes and the start offset location.

This persistency will take a few days but it is definitely on the list. It really uses the FRAM potential.

updated - https://github.com/RobTillaart/FRAM_I2C/tree/ringbuffer
(build is running...)
update -> minor fix needed for ESP32
(build is running...)
success

@crami
Please check the latest version if it meets your needs...
PS, I have updated the example too (used your GPS struct as example)
To be continued

Rob

Notes to myself for possible examples.

The current interface allows to write an identifier (typical 1 byte?) in the ringbuffer, followed by an object of that type.
That would allow the ringbuffer to contain mixed datatypes. Optional one also writes the size for certain objects e.g. if one want to write a string of text.

The ringbuffer can also be used to store a configuration this way. It is not a ring buffer, just a persistent buffer.
At the same time it is overkill as the FRAM class can write/read a whole config struct "at once".

updated - https://github.com/RobTillaart/FRAM_I2C/tree/ringbuffer

Now the buffer can store any object due to using templates. - interface changed slightly, see example

Please give it a try.

@crami
Did you find some time to evaluate?

new version is coming including load() and save().


update: persistence interface pushed to ringbuffer branch.

Observations

  • As _count can be derived from _front and _tail one need only to store two of them.
  • _start is needed to access the meta data so it makes no sense to store this.

Todo an update is needed.


done

As _count can be derived from _front and _tail one need only to store two of them.

Apparently not true as the tail and front pointer are identical when buffer == full or when buffer == empty.
(alternative is to keep one slot free so front == tail ==> count = 0; but that means if you want to buffer for 10 objects of size 8 you need to allocate 80 + 8 bytes which is not "user friendly".

@crami
New push, after several tests the ringbuffer behaves stable.

  • A new example is added that has two ringbuffers
  • save(), load() and wipe() seem to work as intended

Please give it a try. I will do more tests if time permits

@crami
Did you find some time to evaluate?

@crami
Apparently you do not have time to evaluate. As I did not find anymore bugs I am going to make a PR of the branch and will merge the ring buffer class later today.

@crami
New release is nearby

  • moved code into .cpp file FRAM.h -> FRAM.cpp
  • added a separate FRAM_RINGBUFFER.md file
  • updated documentation and meta data

@crami
Merged ringbuffer branch into master, released 0.4.2

if there are problems feel free to create a new issue.