Ring Buffer library

Good evening,

I’ve been working on a ring buffer library (attached) for use in various sketches that I’m working on.

This is the first time I’ve created a library, and also my first attempt at a class, so apologies for any poor use of code. I’ve not tested it exhaustively, but it appears to function as intended. Any suggestions on how to improve things are more than welcome, but be gentle :-[

It basically functions in the same way as the ring buffer found in HardwareSerial. You can write to, and read from the buffer in much the same way you do with HardwareSerial. It also has similar functions like Available & Peek etc.

Here’s a sketch that sort of shows how it can be used. I just use it to temporarily store data until it’s ready to be sent on it’s way, and occasionally to hold incoming serial data while I process it.

#include "RingBuffer.h"

RingBuffer myBuffer;
RingBuffer myOtherBuffer;

void setup()
{
  Serial.begin(9600);
  myOtherBuffer.write(60);
  myOtherBuffer.write(20);

  for (int i = 0; i < 50; i++) {
    myBuffer.write(i);
    Serial.print("myBuffer  ");
    Serial.println(myBuffer.read() );
  }

  Serial.print("myOtherBuffer  ");
  Serial.println(myOtherBuffer.read() );
}

void loop()
{
}

Now to my question. As it stands, the size of the buffer is fixed, and each instance has the same size buffer/array. Is it possible to pass the array size to the class when it’s called ?

Something like this:

RingBuffer myBuffer(32);
RingBuffer myOtherBuffer(64);

I’m not looking to dynamically assign the array size at run time or anything. I’m happy for it to be fixed at compile time. I’d just like to be able to set the size of each buffer/array when it’s called from the sketch. Sometimes I have a couple of buffers in the same sketch that need different sizes.

Thanks,

Ian.

RingBuffer.cpp (1.19 KB)

RingBuffer.h (458 Bytes)

Interesting.

Once you compile the sketch, things are set in stone(silicon).

LarryD:
Interesting.

Once you compile the sketch, things are set in stone(silicon).

The size of the array is, but the contents can be changed.

Yes

Yes, the constructor for the class is allowed to have arguments, just like any function. Look at how SoftwareSerial works, for example.

No, it's not set in sillicon. You could, if you really needed to, do something like this:

RingBuffer &MyBuffer;
void setup() {
  Serial.begin(9600);
  Serial.print("Please enter a buffer size>");
  int size = Serial.parseInt();
  MyBuffer = new Ringbuffer(size);
}

Or something like that - I haven't tested it.

MorganS:
Yes, the constructor for the class is allowed to have arguments, just like any function. Look at how SoftwareSerial works, for example.

No, it's not set in sillicon. You could, if you really needed to, do something like this:

RingBuffer &MyBuffer;

void setup() {
  Serial.begin(9600);
  Serial.print("Please enter a buffer size>");
  int size = Serial.parseInt();
  MyBuffer = new Ringbuffer(size);
}



Or something like that - I haven't tested it.

Not sure that would compile. Maybe if RingBuffer was a pointer:

RingBuffer *pMyBuffer = NULL;
//....
pMyBuffer = new Ringbuffer(size);

Why are these virtual...

virtual int available(void);
virtual int peek(void);
virtual int peek(int);
virtual void remove(int);
virtual int read(void);
virtual char write(int);

(I assume the class is meant to be used to transfer data to/from an interrupt service routine.) You access multi-byte values without protection. That's a no-no.

There is nothing wrong with dynamically allocating the storage according to the size that you specify when you construct the object.

If I'm honest, there was a certain amount of cut 'n' paste going on. They don't need to be virtual, and this has been corrected.

michinyon:
There is nothing wrong with dynamically allocating the storage according to the size that you specify when you construct the object.

Good point. Any pointers (no pun intended) on how this would be achieved though ?

Ian.

Could you allow the user to create the space using a byte/char array and then pass this to the ringbuffer constructor? Not sure if sizeof() would work though in the library to determine the size but you could add another parameter for size if not.

uint8_t buffer[32];

ringBuffer myBuffer(buffer);

or 

ringBuffer myBuffer(buffer, sizeof(buffer));

Well, I though I had it, but then I didn’t :confused:

I like to use Visual Studio when I’m trying to figure things out. It has some nice features and active syntax checking etc.

I changed the header file to:

#include "Arduino.h"

#ifndef RingBuffer_h
#define RingBuffer_h


class RingBuffer
{
private:
	int BUFFER_SIZE;
	unsigned int BufferHead, BufferTail;
	char *Buffer = new char[BUFFER_SIZE];
	
public:
	RingBuffer();
	RingBuffer(int);

	int available(void);
	int peek(void);
	int peek(int);
	void remove(int);
	int read(void);
	char write(int);
};
#endif

and added this to the top of the cpp file:

RingBuffer::RingBuffer()
{
	BufferTail = 0;
	BufferHead = 0;
	BUFFER_SIZE = 32;
}


RingBuffer::RingBuffer(int s)
{
	BufferTail = 0;
	BufferHead = 0;
	BUFFER_SIZE = s;
}

In the sketch, I can call:

RingBuffer myBuffer;

This creates a buffer with a default size of 32 bytes.

Or, I can call:

RingBuffer myBuffer(64);

Which creates a buffer with 64 bytes.

This all appears to work fine in Visual Studio 2013, but using the same code in Arduino 1.0.6 generates the following errors:

In file included from RingBuffer.ino:1:
C:\Users\Ian\Documents\Arduino\libraries\RingBuffer/RingBuffer.h:12: error: ‘RingBuffer::BUFFER_SIZE’ cannot appear in a constant-expression
C:\Users\Ian\Documents\Arduino\libraries\RingBuffer/RingBuffer.h:12: error: `new’ cannot appear in a constant-expression
C:\Users\Ian\Documents\Arduino\libraries\RingBuffer/RingBuffer.h:12: error: ISO C++ forbids initialization of member ‘Buffer’
C:\Users\Ian\Documents\Arduino\libraries\RingBuffer/RingBuffer.h:12: error: making ‘Buffer’ static
C:\Users\Ian\Documents\Arduino\libraries\RingBuffer/RingBuffer.h:12: error: invalid in-class initialization of static data member of non-integral type ‘char*’

Is this to do with Arduino 1.0.6 using an older compiler or something else?

Ian.

RingBuffer.cpp (1.28 KB)

RingBuffer.h (375 Bytes)

No, this shouldn't really compile using visual studio either, unless micro$oft have been up to their old tricks again.

You can't initialise non static variables in a class. And you don't want it to be static either, as you'll have other instances.

char *Buffer = new char[BUFFER_SIZE];

Also all upper case is usually reserved for constants and defines, but thats more preference.

I think what you want to do in this case, is to take a ring buffer size in the constructor, have a char * in the class, then allocate it based off the size. Then free it up in the deconstructor.

So something like this:

header:

#include "Arduino.h"

#ifndef RingBuffer_h
#define RingBuffer_h


class RingBuffer
{
private:

	unsigned int BufferHead, BufferTail;
	char *buffer_p;
	
public:
	RingBuffer(int size);
        ~RingBuffer();
	int available(void);
	int peek(void);
	int peek(int);
	void remove(int);
	int read(void);
	char write(int);
};
#endif

.cpp:

RingBuffer::RingBuffer( int size )
{
	buffer_p = ( char* )malloc( size );
        memset( buffer_p, 0, size );  // maybe not needed depending on how you use the buffer ...
}

RingBuffer::~RingBuffer()
{
	if( buffer_p )
          free( buffer_p );
}

improvement:

You could make it a template class so you could create a ring buffer for any type of data
e.g. int's unsigned longs floats etc.

Check - Classes (II) - C++ Tutorials -

tammytam:
No, this shouldn’t really compile using visual studio either, unless micro$oft have been up to their old tricks again.

I guess they have. It compiles and runs fine.

char *Buffer = new char[BUFFER_SIZE];

Also all upper case is usually reserved for constants and defines, but thats more preference.

I think that was more work from Mr cut ‘n’ paste. I really must stop doing that.

I think what you want to do in this case, is to take a ring buffer size in the constructor, have a char * in the class, then allocate it based off the size. Then free it up in the deconstructor.

So something like this:

header:

#include "Arduino.h"

#ifndef RingBuffer_h
#define RingBuffer_h

class RingBuffer
{
private:

unsigned int BufferHead, BufferTail;
char *buffer_p;

public:
RingBuffer(int size);
       ~RingBuffer();
int available(void);
int peek(void);
int peek(int);
void remove(int);
int read(void);
char write(int);
};
#endif




.cpp:



RingBuffer::RingBuffer( int size )
{
buffer_p = ( char* )malloc( size );
       memset( buffer_p, 0, size );  // maybe not needed depending on how you use the buffer …
}

RingBuffer::~RingBuffer()
{
if( buffer_p )
         free( buffer_p );
}

Now that works just fine. Thank you very much for taking the time to assist. One last question though.

Is the memory allocated at compile or runtime ? I only ask because the buffer size makes no difference to the Binary Sketch size when it’s compiled. I assumed this would be allocated at compile time, but I assume lots of things that aren’t necessarily correct.

If it helps anyone in the future, I’ve attached the revised .h & .cpp files.

Thanks to all,

Ian.

RingBuffer.cpp (1.34 KB)

RingBuffer.h (367 Bytes)

I can't make it work in Arduino 1.65. I initialize:

#include "C:\\Users\\Admin\\Documents\\Arduino\\libraries\\RingBuffer.h"
RingBuffer fifo_1(64);

And try to read from it:

 if( fifo_1.available() ) {
    int inByte = fifo_1.read();

I have not yet added how to put in bytes, but the only way to make the buffer useful for me is to do that in an interrupt.

Unfortunately it does not compile. I am given these lines of information...

NMEA_Serial_GW.cpp.o: In function `__static_initialization_and_destruction_0':
K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino/NMEA_Serial_GW.ino:17: undefined reference to `RingBuffer::RingBuffer(int)'
K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino/NMEA_Serial_GW.ino:17: undefined reference to `RingBuffer::~RingBuffer()'
NMEA_Serial_GW.cpp.o: In function `loop':
K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino/NMEA_Serial_GW.ino:37: undefined reference to `RingBuffer::available()'
K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino/NMEA_Serial_GW.ino:39: undefined reference to `RingBuffer::read()'
collect2.exe: error: ld returned 1 exit status
Error compiling.

Can anyone give me a hint where I fail?

I’ve not tried it on version 1.6.5, but it works fine with 1.0.6.

Have you tried the more traditional include ?

#include <RingBuffer.h>

Ian.

I started with that, but I got NMEA_Serial_GW.ino:17:24: fatal error: RingBuffer.h: No such file or directory

After number of tests later I learned that

  1. I Need to make a folder for the "loose" .c and .h-files. The folder can have any name, it does not need to be ..Libraries\RingBuffer.
  2. After I add/move files in the Libraries folder, I need to restart Arduino IDE.

Now when it compiles the quest can continue.

It's not clear from your last post, but does it now compile ?

Ian.

Sorry for being unclear. It compiles.

Well, it compiled until I tried to make an interrupt to input data to the buffer:

ISR(SIG_USART1_RECV) {
  while ( Serial1.available() ) {
    int a = Serial1.read();
    byte ret = fifo_1.write(a);
    if ( ret ) {
      Serial.println("fifo_1 buffer overflow!!"); // If the unthinkable happens :)
    }
  }
}

I got the error NMEA_Serial_GW:21: error: attempt to use poisoned "SIG_USART1_RECV"
attempt to use poisoned "SIG_USART1_RECV"

Some googling later I found someone used
#define AVR_LIBC_DEPRECATED_ENABLE 1
to bypass that error, but trying that I got some even more cryptic error messages...

D:\Temp\build9101866816572215400.tmp/core.a(HardwareSerial1.cpp.o): In function __vector_36': K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino\hardware\arduino\avr\cores\arduino/HardwareSerial1.cpp:46: multiple definition of __vector_36'
NMEA_Serial_GW.cpp.o:K:\Dur2k_D\Download\Programmering\Arduino\arduino-1.6.5\Arduino/NMEA_Serial_GW.ino:21: first defined here
k:/dur2k_d/download/programmering/arduino/arduino-1.6.5/arduino/hardware/tools/avr/bin/../lib/gcc/avr/4.8.1/../../../../avr/bin/ld.exe: Disabling relaxation: it will not work with multiple definitions
collect2.exe: error: ld returned 1 exit status
Error compiling.

I get the feeling I am putting time on a thing that thousands of people must have invented solutions for.
Problem definition: I need to save the fast burst of up to 1kB serial data while the slow loop() is working with other things, i.e I cannot guarantee that it has finished before characters are lost.