Files
fifofast/fifofast.h
nqtronix 196edda9d3 Fix typo
2022-07-02 21:16:43 +02:00

574 lines
22 KiB
C

/*
* fifofast.h
*
* Created: 23.10.2017 06:45:20
* Author: Dennis aka nqtronix (github.com/nqtronix)
*
* Description:
* Most libraries for data structures are designed for dynamically allocating memory, which can't be
* efficiently used on small, low-cost, embedded devices.
* This library implements the common fifo data structure based on function-like macros, which
* allows the code to be generic for all data types and buffer sizes. All memory is allocated at
* compile time. As a side effect the execution time for most functions and RAM memory consumption
* is reduced, but the program size slightly increased.
*
* Some programs may need to access multiple fifos, e.g. to distribute measured ADC values to their
* corresponding handler functions. For theses applications you can create fifos, which can be safely
* passed by pointers. Although these fifos can be access by all function-like macros, they can't be
* as generic and are limited to user selectable depth 'FIFOFAST_MAX_DEPTH_POINTABLE'.
*
* If any fifo is accessed from ISRs make sure all access to the involved fifo from normal code is
* atomic! This is important to prevent glitches in the middle of an operation!
*/
#ifndef FIFOFAST_H_
#define FIFOFAST_H_
#include <stdint.h> // required for data types (uint8_t, uint16_t, ...)
#include <stddef.h> // required for "NULL"
#include <string.h> // required for memcopy
// include required macros
// by default, the project's macros will be used, they have to be located at:
// "utility/macros/..."
//
// If your project has no global macros, you can re-direct GCC to the included files:
// a) in Atmel Studio edit project properties (ALT+F7 OR "Project -> <project name> Properties...")
// b) Go to "Toolchain / AVR/GNU C Compiler / Directories"
// c) add the relative path to: "../src/subrepo/fifofast/" or wherever this code is located. DONE!
//
// YOU DO NOT need to change the include(s) below.
#include "utility/macros/mpl/macro_cat.h"
#include "utility/macros/com/macro_array.h"
#include "utility/macros/com/macro_math.h"
#include "utility/macros/com/macro_type.h"
//////////////////////////////////////////////////////////////////////////
// User Config
//////////////////////////////////////////////////////////////////////////
// defines the maximum depth of pointable fifos. The performance of fifofast drops with increased
// maximum depth in discrete steps, especially on 8bit MCUs. 32bit MCUs (such as ARM cortex) are
// mostly unaffected, except they require slightly more RAM. If the value is NOT a 2^n value, it
// will automatically be rounded up.
//
// depth | performance
// -----------------+-------------
// 4 <= x <= 128 | best
// x == 256 | ok
// 512 <= x | slow
#define FIFOFAST_MAX_DEPTH_POINTABLE 128
//////////////////////////////////////////////////////////////////////////
// General Info
//////////////////////////////////////////////////////////////////////////
// version numbering is based on "Semantic Versioning 2.0.0" (semver.org)
#define FIFOFAST_VERSION_MAJOR 0
#define FIFOFAST_VERSION_MINOR 8
#define FIFOFAST_VERSION_PATCH 0
#define FIFOFAST_VERSION_SUFFIX
#define FIFOFAST_VERSION_META
// For all development versions (0.x.x) the patch version is increased whenever a function was renamed
//////////////////////////////////////////////////////////////////////////
// Check requirements
//////////////////////////////////////////////////////////////////////////
#ifndef __GNUC__
#error fifofast.h requires "compound statments" and "typeof" offered by a GNU C/ GCC compiler!
#endif
#ifndef __OPTIMIZE__
#pragma message "fifofast.h is intended to be compiled with optimisation and will run VERY SLOWLY without!"
#endif
//////////////////////////////////////////////////////////////////////////
// internal macros (_FFF_*)
//////////////////////////////////////////////////////////////////////////
// rounds up given argument to next 2^n value. Used to deal with invalid user input.
#define ROUND_UP_2N(_arg) ((uint64_t)1<<(_log2(_arg-1)+1))
// returns the structure name matching to given ID without the keyword 'struct'
#define _FFF_NAME_STRUCT(_id) CAT(fff_, _id, _s)
// returns matching type for internal index values; fifo constrains are automatically applied
#define _FFF_GET_TYPE(_depth) _type_min(_limit_lo(_depth,4)-1)
#define _FFF_SIZEOF_DATA(_id) sizeof(((struct _FFF_NAME_STRUCT(_id)*)0)->data[0])
#define _FFF_SIZEOF_ARRAY(_id) _sizeof_array(((struct _FFF_NAME_STRUCT(_id)*)0)->data)
// returns the length of the data array; fifo constrains are automatically applied
#define _FFF_GET_ARRAYDEPTH(_depth) _limit(ROUND_UP_2N(_depth), 4, ((uint32_t)1<<31))
#define _FFF_GET_ARRAYDEPTH_P(_depth) _limit(ROUND_UP_2N(_depth), 4, ROUND_UP_2N(FIFOFAST_MAX_DEPTH_POINTABLE))
//////////////////////////////////////////////////////////////////////////
// Data Structures (for inline functions only)
//////////////////////////////////////////////////////////////////////////
// extra #defines prevent VAssitX from marking the type red (because it doesn't understand 'typeof')
#define FIFOFAST_INDEX_T _type_min(ROUND_UP_2N(FIFOFAST_MAX_DEPTH_POINTABLE)-1)
#define FIFOFAST_LEVEL_T _type_min(ROUND_UP_2N(FIFOFAST_MAX_DEPTH_POINTABLE))
typedef FIFOFAST_INDEX_T fff_index_t;
typedef FIFOFAST_LEVEL_T fff_level_t;
typedef struct
{
const fff_index_t data_size; // bytes per element in data array
const fff_index_t mask; // (max amount of elements in data array) - 1
fff_index_t read; // index from which to read next element
fff_index_t write; // index to which to write next element
fff_level_t level; // current amount of stored data. Is larger than 'mask', if full
uint8_t data[]; // data storage array
} fff_proto_t;
//////////////////////////////////////////////////////////////////////////
// Function Declarations (User)
//////////////////////////////////////////////////////////////////////////
// these functions behave as their corresponding macros, so please refer to their description
// for infos on usage.
static inline fff_index_t fff_mem_mask(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline fff_level_t fff_mem_level(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline fff_index_t fff_mem_free(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline fff_index_t fff_data_size(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline uint8_t fff_is_empty(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline uint8_t fff_is_full(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline void fff_reset(fff_proto_t *fifo) __attribute__((__always_inline__));
static inline void fff_remove(fff_proto_t *fifo, fff_level_t amount) __attribute__((__always_inline__));
static inline void fff_remove_lite(fff_proto_t *fifo, fff_level_t amount) __attribute__((__always_inline__));
static inline void fff_write(fff_proto_t *fifo, void *data) __attribute__((__always_inline__));
static inline void fff_write_lite(fff_proto_t *fifo, void *data) __attribute__((__always_inline__));
static inline void* fff_peek_read(fff_proto_t *fifo, fff_index_t idx) __attribute__((__always_inline__));
static inline void fff_peek_write(fff_proto_t *fifo, fff_index_t idx, void *data) __attribute__((__always_inline__));
//////////////////////////////////////////////////////////////////////////
// Function Declarations (Internal)
//////////////////////////////////////////////////////////////////////////
static inline fff_index_t fff_wrap(fff_proto_t *fifo, fff_index_t idx) __attribute__((__always_inline__));
static inline void* fff_data_p(fff_proto_t *fifo, fff_index_t idx) __attribute__((__always_inline__));
//////////////////////////////////////////////////////////////////////////
// user macros (_fff_*)
//////////////////////////////////////////////////////////////////////////
// all function-like macros are suitable for ANY fifo, independent of data type or size.
// declares semi-anonymous fifofast structure
// semi-anonymous means it appears anonymous for the user as it is derived from the '_id' whenever
// needed, but is not anonymous on compiler level. This has the additional benefit of VAssisX and
// debugging (inspecting variables during runtime with an external debugger) working as usual. The
// fifo shall only be access through macros or functions provided in this file.
//
// The variant _fff_declare_p(...) declares a structure which includes the size of each element and
// the maximum amount of elements and can thus be used by the inline functions. They still can be
// accessed with all function-like macros provided for some speed gain.
//
// The variant_fff_declare_pa(...) declares an array with structures as declared by _fff_declare_p(...).
//
//
// _id: C conform identifier
// _type: any C type except pointers and structs. To store pointers or structs use typedef first
// _depth: maximum amount of elements, which can be stored in the fifo. Only values of 2^n are
// possible. If another value is passed the next larger value will be automatically
// selected. The amount of additional RAM required increases in discrete steps:
// depth (elements) | RAM (bytes)
// ---------------------+-------------
// 4 <= x <= 128 | 3
// x == 256 | 4
// 512 <= x <= 32768 | 6
// x == 65536 | 8
// 131072 <= x | 12
#define _fff_declare(_type, _id, _depth) \
struct _FFF_NAME_STRUCT(_id) { \
_FFF_GET_TYPE(_depth) read; \
_FFF_GET_TYPE(_depth) write; \
_FFF_GET_TYPE(_depth+1) level; \
_type data[_FFF_GET_ARRAYDEPTH(_depth)]; \
} _id
#define _fff_declare_p(_type, _id, _depth) \
struct _FFF_NAME_STRUCT(_id) { \
const fff_index_t data_size; \
const fff_index_t mask; \
fff_index_t read; \
fff_index_t write; \
fff_level_t level; \
_type data[_FFF_GET_ARRAYDEPTH_P(_depth)]; \
} _id
// declares an array with '_size' fifos. '_size' can be any positive integer.
#define _fff_declare_a(_type, _id, _depth, _size) _fff_declare(_type, _id, _depth) [_size]
#define _fff_declare_pa(_type, _id, _depth, _size) _fff_declare_p(_type, , _depth) [_size]
// initializes the fifo with the name '<_id>'
// This initialization function is technically identical to the term "definition" in c, but to
// prevent confusion with "#define" it has been named '_fff_init()'. Since it is a definition it
// can be only called once. Use '_fff_reset()' to reset any fifo back to it's original state.
//
// The variants '_fff_init_p(_id)' and '_fff_init_pa(_id, _arraysize)' are intended for the
// respective declarations.
#define _fff_init(_id) \
struct _FFF_NAME_STRUCT(_id) _id = \
{ \
0, \
0, \
0, \
{} \
}
#define _fff_init_a(_id, _arraysize) \
struct _FFF_NAME_STRUCT(_id) _id [] = \
{[0 ... _arraysize-1] = { \
0, \
0, \
0, \
{} \
}}
#define _fff_init_p(_id) \
struct _FFF_NAME_STRUCT(_id) _id = \
{ \
_FFF_SIZEOF_DATA(_id), \
_FFF_SIZEOF_ARRAY(_id)-1, \
0, \
0, \
0, \
{} \
}
#define _fff_init_pa(_id, _arraysize) \
struct _FFF_NAME_STRUCT(_id) _id [] = \
{[0 ... _arraysize-1] = { \
_FFF_SIZEOF_DATA(_id), \
_FFF_SIZEOF_ARRAY(_id)-1, \
0, \
0, \
0, \
{} \
}}
// masks a given index value based on a given fifo
// This macro is used to simplify other marcos below; the end user will likely never need it
// _id: C conform identifier
// idx: the index value to mask. MUST be larger than -_sizeof_array(_id.data)
#define _fff_wrap(_id, idx) ((idx) & _fff_mem_mask(_id))
// returns the maximum amount of data elements which can be stored in the fifo
// The returned value is calculated at compile time and thus a constant. No atomic access is needed.
// NOTE: For a fifo of size 256 a "256" is returned and thus does not fit into uint8_t
// _id: C conform identifier
#define _fff_mem_depth(_id) (_sizeof_array(_id.data))
#define _fff_mem_mask(_id) (_sizeof_array(_id.data)-1)
// returns the amount of bytes per data element
// The returned value is calculated at compile time and thus a constant. No atomic access is needed.
// _id: C conform identifier
#define _fff_data_size(_id) (sizeof(_id.data[0]))
// returns !0 if empty
#define _fff_is_empty(_id) (_id.level == 0)
// returns !0 if full
#define _fff_is_full(_id) (_id.level > _fff_mem_mask(_id))
// returns the current fill level of the fifo (the amount of elements that can be read)
// _id: C conform identifier
#define _fff_mem_level(_id) (_id.level)
// returns the current free space of the fifo (the amount of elements that can be written)
// _id: C conform identifier
#define _fff_mem_free(_id) (_fff_mem_depth(_id) - _id.level)
// clears/ resets buffer completely
// _id: C conform identifier
#define _fff_reset(_id) do{_id.read=0; _id.write=0; _id.level=0;} while (0)
// removes a certain number of elements or less, if not enough elements are available.
// This function is especially useful after data has been used by _fff_peek(...)
// _id: C conform identifier
// amount: Amount of elements which will be removed, amount >= 0 (positive integer)
#define _fff_remove(_id, amount) \
do{ \
typeof(_id.level) _amount = amount; \
if(amount > _id.level) \
_amount = _id.level; \
_fff_remove_lite(_id, _amount); \
}while(0)
// removes a certain number of elements. The user must ensure that the given amount of elements can
// be removed; values larger than _fff_depth(_id) are invalid! If you require argument checking use
// _fff_remove().
// This function is especially useful after data has been used by _fff_peek(...)
// _id: C conform identifier
// amount: Amount of elements which will be removed; must be 0 <= amount <= _fff_mem_level(_id);
#define _fff_remove_lite(_id, amount) \
do{ \
_id.level -= amount; \
_id.read = _fff_wrap(_id, _id.read+amount); \
}while(0)
// returns the next element from the fifo and removes it from the memory
// Use if(!_fff_is_empty(_id)) if amount of stored data is unknown
// _id: C conform identifier
#define _fff_read_lite(_id) \
({ \
typeof(_id.data[0]) _return; \
_id.level--; \
_return = _id.data[_id.read]; \
_id.read = _fff_wrap(_id, (_id.read+1)); \
_return; \
})
// returns the next element from the fifo and removes it from the memory
// if no element is available, 0 is returned
// _id: C conform identifier
#define _fff_read(_id) \
({ \
typeof(_id.data[0]) _return = (typeof(_id.data[0])){0}; \
if(!_fff_is_empty(_id)) \
_return = _fff_read_lite(_id); \
_return; \
})
// adds an element to the fifo
// Use if(!_fff_is_full(_id)) if amount of stored data is unknown
// _id: C conform identifier
// newdata: data to be written
#define _fff_write_lite(_id, newdata) \
do{ \
_id.data[_id.write] = (newdata); \
_id.write = _fff_wrap(_id, (_id.write+1)); \
_id.level++; \
}while(0)
// adds an element to the fifo, if space is available
// if full element will be dismissed
// _id: C conform identifier
// newdata: data to be written
#define _fff_write(_id, newdata) \
do{ \
if(!_fff_is_full(_id)) \
_fff_write_lite(_id, newdata); \
}while(0)
// copies an array of elements to the fifo as long as space is available
// if full all excess elements will be dismissed
// _id: C conform identifier
// newdata: array of data to be written
// n: amount of data do be written
#define _fff_write_multiple(_id, newdata, n) \
do{ \
typeof(_id.level) tocopy, btw; \
btw = _min(_fff_mem_free(_id), (n)); \
if (btw == 0) { \
break; \
} \
tocopy = _min(btw, _fff_mem_depth(_id) - _id.write); \
memcpy(&_id.data[_id.write], (newdata), tocopy); \
_id.level += tocopy; \
_id.write = _fff_wrap(_id, (_id.write+tocopy)); \
btw -= tocopy; \
if (btw > 0) { \
memcpy(&_id.data[_id.write], (newdata)+tocopy, btw); \
_id.write = btw; \
_id.level += btw; \
} \
}while(0)
// adds an element to the fifo, but does not write any data to it. instead, a pointer to the data
// section is returned. The caller may write up to _fff_data_size(_id) bytes to this location.
// Use if(!_fff_is_full(_id)) if amount of stored data is unknown
// _id: C conform identifier
#define _fff_add_lite(_id) \
({ \
typeof(&_id.data[0]) _return = & _id.data[_id.write]; \
_id.write = _fff_wrap(_id, (_id.write+1)); \
_id.level++; \
_return; \
})
// like _fff_add_lite(_id), but checks if space is available before writing. Returns 'null' if full.
// _id: C conform identifier
#define _fff_add(_id) \
({ \
typeof(&_id.data[0]) _return = (typeof(&_id.data[0]))NULL; \
if(!_fff_is_full(_id)) \
_return = _fff_add_lite(_id); \
_return; \
})
// allows accessing the data of a fifo as an array without removing any elements
// Like any array this function can be used as a right or left site operant. Attempting to access
// more elements than currently in the buffer will return undefined data on read and will have no
// effect on write. Accidental read/write operations outside the assigned data space are not
// possible.
// This macro is NOT ATOMIC. If fifo "_id" is accessed within an ISR at least once, this macro MUST
// be placed within an atomic block outside of any ISR.
// _id: C conform identifier
// idx: Offset from the first element in the buffer
#define _fff_peek(_id, idx) _id.data[_fff_wrap(_id, _id.read+(idx))]
// re-writes the internal array, so that the element _fff_peek(0) will be at the physical idx 0
// Although this does not effect any of the fifo functions, it does simplify operations on string
// stored in the fifo.
// NOTE that this macro copies element-by-element and might take very long for large fifos with many
// elements or large data sizes.
//
// Macro inspired by Jon Bentley's array rotation algorithm and this stackoverflow answer:
// https://stackoverflow.com/a/22079960/6215916
//
// This version has only one call of _FFF_REVERSE to safe program memory
#define _fff_rebase(_id) \
do{ \
/* check if rebase required */ \
if (_id.read == 0) \
break; \
\
typeof(_id.read) idx1, idx2; \
\
/* reversing 1st half, 2nd half and everything together */ \
/* rotates the array */ \
for (uint8_t rep = 0; rep<3; rep++) \
{ \
switch (rep) \
{ \
default: \
case 0: \
idx1 = 0; \
idx2 = _id.read-1; \
break; \
case 1: \
idx1 = _id.read; \
idx2 = _fff_mem_mask(_id); \
break; \
case 2: \
idx1 = 0; \
idx2 = _fff_mem_mask(_id); \
break; \
} \
\
/* reverse section from idx1 to idx2 */ \
for (; idx1 < idx2; idx1++, idx2--) \
{ \
typeof(_id.data[0]) tmp; \
tmp = _id.data[idx1]; \
_id.data[idx1] = _id.data[idx2]; \
_id.data[idx2] = tmp; \
} \
} \
\
/* Update data indices */ \
_id.write = _id.write - _id.read; \
_id.read = 0; \
}while(0)
//////////////////////////////////////////////////////////////////////////
// Inline functions
//////////////////////////////////////////////////////////////////////////
// Inline functions MUST be defined in the .h, not in the .c file to work correctly!
// auxiliary functions
static inline fff_index_t fff_wrap(fff_proto_t *fifo, fff_index_t idx)
{
return (idx & fifo->mask);
}
static inline void* fff_data_p(fff_proto_t *fifo, fff_index_t idx)
{
return &(fifo->data[idx * fifo->data_size]);
}
static inline fff_index_t fff_mem_mask(fff_proto_t *fifo)
{
return (fifo->mask);
}
static inline fff_index_t fff_data_size(fff_proto_t *fifo)
{
return fifo->data_size;
}
//
static inline uint8_t fff_is_empty(fff_proto_t *fifo)
{
return (fifo->level == 0);
}
static inline uint8_t fff_is_full(fff_proto_t *fifo)
{
return (fifo->level > fifo->mask);
}
static inline fff_level_t fff_mem_level(fff_proto_t *fifo)
{
return (fifo->level);
}
static inline fff_index_t fff_mem_free(fff_proto_t *fifo)
{
return (fifo->mask - fifo->level + 1);
}
//
static inline void fff_reset(fff_proto_t *fifo)
{
fifo->read = 0;
fifo->write = 0;
fifo->level = 0;
}
static inline void fff_remove(fff_proto_t *fifo, fff_level_t amount)
{
if (amount > fifo->level)
amount = fifo->level;
fff_remove_lite(fifo, amount);
}
static inline void fff_remove_lite(fff_proto_t *fifo, fff_level_t amount)
{
fifo->level -= amount;
fifo->read = fff_wrap(fifo, fifo->read + amount);
}
static inline void fff_write(fff_proto_t *fifo, void *data)
{
if (!fff_is_full(fifo))
fff_write_lite(fifo,data);
}
static inline void fff_write_lite(fff_proto_t *fifo, void *data)
{
memcpy(fff_data_p(fifo, fifo->write), data, fifo->data_size);
fifo->write = fff_wrap(fifo, fifo->write+1);
fifo->level++;
}
// the peek function MUST be split into two to work as a normal c function
// BOTH function STILL refer to the top (read) end of the fifo
static inline void* fff_peek_read(fff_proto_t *fifo, fff_index_t idx)
{
return fff_data_p(fifo, fff_wrap(fifo, fifo->read+idx));
}
static inline void fff_peek_write(fff_proto_t *fifo, fff_index_t idx, void *data)
{
memcpy(fff_data_p(fifo, fff_wrap(fifo, fifo->read+idx)), data, fifo->data_size);
}
#endif /* FIFOFAST_H_ */