Skip to content

Commit

Permalink
Template metaprogramming stuff. The size of the buffer determines the…
Browse files Browse the repository at this point in the history
… datatype used for size and index. limits are statically checked
  • Loading branch information
Koryphon committed Dec 12, 2018
1 parent 2f10dcb commit 0e114aa
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 45 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ A simple and easy to use ring buffer library for Arduino. Interrupt safe functio

## Changelog

- 1.0.3 Changed the way templates are instanciated. Now, a size greater than 255 is allowed and leads to a uint16_t datatype used for size and index. In addition, wrong size are detected and a compilation error is emited. Added example ```BigBuffer``` with a size over 255.
- 1.0.2 Changed the name of the template from RingBuffer to RingBuf in order to avoid a name conflict with and internal RingBuffer class used in the ARM version of the Arduino core.
- 1.0.1 Fix a mistake in pop documentation
- 1.0 Initial release.

## Limitation

The size of the ring buffer is limited to 255 elements. The compiler will not prevent you from declaring a buffer size 0 but a size 0 is not supported (and otherwise silly). Among the quirks with a size of 0 is the fact that the buffer is both empty and full.
The size of the ring buffer is limited to 65535 elements. The compiler will emit an error is the buffer size is 0 or if the buffer size is greated than 65535.

## Using the library

Expand All @@ -26,7 +27,7 @@ Instantiate a ring buffer by using the following syntax:
RingBuf<type, size> myRingBuffer;
```

```type``` is the type name of each element of the ring buffer. ```size``` is the size, from 1 to 255, of the ring buffer. For instance the declaration shown below instantiate a ring buffer where each element is a ```byte``` with a size of 20.
```type``` is the type name of each element of the ring buffer. ```size``` is the size, from 1 to 65535, of the ring buffer. For instance the declaration shown below instantiate a ring buffer where each element is a ```byte``` with a size of 20.

```
RingBuf<byte, 20> aBuffer;
Expand All @@ -44,11 +45,11 @@ The following functions are available to manage the ring buffer.

### maxSize()

```maxSize()``` returns an ```uint8_t``` which is the maximum size of the ring buffer. It is the value set when the ring buffer has been instantiated.
```maxSize()``` returns an ```uint8_t``` for buffers of size lower or equal to 255 and an ```uint16_t``` for buffers of size in the [256, 65535] interval. It is the value set when the ring buffer has been instantiated.

### size()

```size()``` returns an ```uint8_t``` which is the current size of the ring buffer. It is between 0 and ```maxSize()```.
```size()``` returns an ```uint8_t``` for buffers of size lower or equal to 255 and an ```uint16_t``` for buffers of size in the [256, 65535] interval, which is the current size of the ring buffer. It is between 0 and ```maxSize()```.

### clear()

Expand Down
66 changes: 66 additions & 0 deletions examples/BigBuffer/BigBuffer.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Test of a big buffer (greater than 255)
*
* Output should be
* Filled with 300 values
* Buffer agrees !
* Checking 300 values... 0 error(s) found
* Popping the values... popped 300 values, 0 wrong
*/

#include <RingBuf.h>

RingBuf<uint16_t, 300> myBuffer;

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

uint16_t i = 0;

/* fill the buffer */
while (! myBuffer.isFull()) {
myBuffer.push(i++);
}
Serial.print("Filled with ");
Serial.print(i);
Serial.println(" values");

/* Check the buffer agrees */
if (myBuffer.size() == i) {
Serial.println("Buffer agrees !");
}
else {
Serial.println("*** Error: Buffer does not agree !");
}

/* Check the values */
Serial.print("Checking ");
Serial.print(myBuffer.size());
Serial.print(" values... ");
uint16_t errorCount = 0;
for (uint16_t j = 0; j < myBuffer.size(); j++) {
if (myBuffer[j] != j) errorCount++;
}
Serial.print(errorCount);
Serial.println(" error(s) found");

/* Pop the values */
Serial.print("Popping the values... ");
errorCount = 0;
uint16_t value;
i = 0;
while (myBuffer.pop(value)) {
if (value != i++) errorCount++;
}
Serial.print("popped ");
Serial.print(i);
Serial.print(" values, ");
Serial.print(errorCount);
Serial.println(" wrong");
}

void loop()
{

}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Examples of RingBuffer library
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=RingBuffer
version=1.0.2
version=1.0.3
author=Jean-Luc - Locoduino
maintainer=Jean-Luc - Locoduino
sentence=This library allows to use ring buffer with and without interrupts.
Expand Down
121 changes: 81 additions & 40 deletions src/RingBuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,123 +47,164 @@

#include <Arduino.h>

template <typename rg_element_t, uint8_t __maxSize__>
/*
* Set the integer size used to store the size of the buffer according of
* the size given in the template instanciation. Thanks to Niklas Gürtler
* to share his knowledge of C++ template meta programming.
* https://niklas-guertler.de/
*
* If Index argument is true, the ring buffer has a size and an index
* stored in an uint8_t (Type below) because its size is within [1,255].
* Intermediate computation may need an uint16_t (BiggerType below).
* If Index argument is false, the ring buffer has a size and an index
* stored in an uint16_t (Type below) because its size is within [256,65535].
* Intermediate computation may need an uint32_t (BiggerType below).
*/

namespace RingBufHelper {
template<bool fits_in_uint8_t> struct Index {
using Type = uint16_t; /* index of the buffer */
using BiggerType = uint32_t; /* for intermediate calculation */
};
template<> struct Index<false> {
using Type = uint8_t; /* index of the buffer */
using BiggerType = uint16_t; /* for intermediate calculation */
};
}

template <
typename ET,
size_t S,
typename IT = typename RingBufHelper::Index<(S > 255)>::Type,
typename BT = typename RingBufHelper::Index<(S > 255)>::BiggerType
>
class RingBuf
{
/*
* check the size is greater than 0, otherwise emit a compile time error
*/
static_assert(S > 0, "RingBuf with size 0 are forbidden");

/*
* check the size is lower or equal to the maximum uint16_t value,
* otherwise emit a compile time error
*/
static_assert(S <= UINT16_MAX, "RingBuf with size greater than 65535 are forbidden");

private:
rg_element_t mBuffer[__maxSize__];
uint8_t mReadIndex;
uint8_t mSize;
ET mBuffer[S];
IT mReadIndex;
IT mSize;

uint8_t writeIndex();
IT writeIndex();

public:
/* Constructor. Init mReadIndex to 0 and mSize to 0 */
RingBuf();
/* Push a data at the end of the buffer */
bool push(const rg_element_t inElement) __attribute__ ((noinline));
bool push(const ET inElement) __attribute__ ((noinline));
/* Push a data at the end of the buffer. Copy it from its pointer */
bool push(const rg_element_t * const inElement) __attribute__ ((noinline));
bool push(const ET * const inElement) __attribute__ ((noinline));
/* Push a data at the end of the buffer with interrupts disabled */
bool lockedPush(const rg_element_t inElement);
bool lockedPush(const ET inElement);
/* Push a data at the end of the buffer with interrupts disabled. Copy it from its pointer */
bool lockedPush(const rg_element_t * const inElement);
bool lockedPush(const ET * const inElement);
/* Pop the data at the beginning of the buffer */
bool pop(rg_element_t &outElement) __attribute__ ((noinline));
bool pop(ET &outElement) __attribute__ ((noinline));
/* Pop the data at the beginning of the buffer with interrupt disabled */
bool lockedPop(rg_element_t &outElement);
bool lockedPop(ET &outElement);
/* Return true if the buffer is full */
bool isFull() { return mSize == __maxSize__; }
bool isFull() { return mSize == S; }
/* Return true if the buffer is empty */
bool isEmpty() { return mSize == 0; }
/* Reset the buffer to an empty state */
void clear() { mSize = 0; }
/* return the size of the buffer */
uint8_t size() { return mSize; }
IT size() { return mSize; }
/* return the maximum size of the buffer */
uint8_t maxSize() { return __maxSize__; }
IT maxSize() { return S; }
/* access the buffer using array syntax, not interrupt safe */
rg_element_t &operator[](uint8_t inIndex);
ET &operator[](IT inIndex);
};

template <typename rg_element_t, uint8_t __maxSize__>
uint8_t RingBuf<rg_element_t, __maxSize__>::writeIndex()
template <typename ET, size_t S, typename IT, typename BT>
IT RingBuf<ET, S, IT, BT>::writeIndex()
{
uint16_t wi = (uint16_t)mReadIndex + (uint16_t)mSize;
if (wi >= (uint16_t)__maxSize__) wi -= (uint16_t)__maxSize__;
return (uint8_t)wi;
BT wi = (BT)mReadIndex + (BT)mSize;
if (wi >= (BT)S) wi -= (BT)S;
return (IT)wi;
}

template <typename rg_element_t, uint8_t __maxSize__>
RingBuf<rg_element_t, __maxSize__>::RingBuf() :
template <typename ET, size_t S, typename IT, typename BT>
RingBuf<ET, S, IT, BT>::RingBuf() :
mReadIndex(0),
mSize(0)
{
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::push(const rg_element_t inElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::push(const ET inElement)
{
if (isFull()) return false;
mBuffer[writeIndex()] = inElement;
mSize++;
return true;
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::push(const rg_element_t * const inElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::push(const ET * const inElement)
{
if (isFull()) return false;
mBuffer[writeIndex()] = *inElement;
mSize++;
return true;
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::lockedPush(const rg_element_t inElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::lockedPush(const ET inElement)
{
noInterrupts();
bool result = push(inElement);
interrupts();
return result;
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::lockedPush(const rg_element_t * const inElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::lockedPush(const ET * const inElement)
{
noInterrupts();
bool result = push(inElement);
interrupts();
return result;
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::pop(rg_element_t &outElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::pop(ET &outElement)
{
if (isEmpty()) return false;
outElement = mBuffer[mReadIndex];
mReadIndex++;
mSize--;
if (mReadIndex == __maxSize__) mReadIndex = 0;
if (mReadIndex == S) mReadIndex = 0;
return true;
}

template <typename rg_element_t, uint8_t __maxSize__>
bool RingBuf<rg_element_t, __maxSize__>::lockedPop(rg_element_t &outElement)
template <typename ET, size_t S, typename IT, typename BT>
bool RingBuf<ET, S, IT, BT>::lockedPop(ET &outElement)
{
noInterrupts();
bool result = pop(outElement);
interrupts();
return result;
}

template <typename rg_element_t, uint8_t __maxSize__>
rg_element_t &RingBuf<rg_element_t, __maxSize__>::operator[](uint8_t inIndex)
template <typename ET, size_t S, typename IT, typename BT>
ET &RingBuf<ET, S, IT, BT>::operator[](IT inIndex)
{
if (inIndex >= mSize) return mBuffer[0];
uint16_t index = (uint16_t)mReadIndex + (uint16_t)inIndex;
if (index >= (uint16_t)__maxSize__) index -= (uint16_t)__maxSize__;
return mBuffer[(uint8_t)index];
BT index = (BT)mReadIndex + (BT)inIndex;
if (index >= (BT)S) index -= (BT)S;
return mBuffer[(IT)index];
}

#endif /* __RINGBUF_H__ */

0 comments on commit 0e114aa

Please sign in to comment.