I'm pretty new to microcontrollers and C in general, so I thought asking here would be a good way not to get started with bad habits.
/*
* ringbuffer.h
*
* Created on: Jun 11, 2014
* Author: Silly Freak
*/
#ifndef RINGBUFFER_H_
#define RINGBUFFER_H_
//for size_t
//TODO is it a good idea to force size_t for head and available?
#include <stdlib.h>
//defines a buffer type with the given element type and size
//usage: <modifier> RBUF(type, size) rbufname;
#define RBUF(TYPE, SIZE) \
struct { \
TYPE buf[SIZE]; \
size_t head, available; \
}
//initializes the buffer as being empty
#define RBUF_INIT(NAME) \
do { \
(NAME).head = 0; \
(NAME).available = 0; \
} while(0)
//number of slots in the buffer, i.e. buffer size/element size
#define RBUF_SIZE(NAME) \
(sizeof((NAME).buf)/sizeof((NAME).buf[0]))
//number of filled slots in the buffer
#define RBUF_AVAILABLE(NAME) \
((NAME).available)
//number of free slots in the buffer
#define RBUF_FREE(NAME) \
(RBUF_SIZE(NAME) - RBUF_AVAILABLE(NAME))
//index of the reading head into the ring buffer
#define RBUF_RHEAD(NAME) \
((NAME).head)
//index of the writing head into the ring buffer
#define RBUF_WHEAD(NAME) \
((RBUF_RHEAD(NAME) + RBUF_AVAILABLE(NAME)) % RBUF_SIZE(NAME))
//pushes an entry into the ring buffer
//the client has to ensure that RBUF_FREE > 0
#define RBUF_PUSH(NAME, VALUE) \
do { \
(NAME).buf[RBUF_WHEAD(NAME)] = VALUE; \
(NAME).available++; \
} while(0)
//pops an entry out of the ring buffer
//the client has to ensure that RBUF_AVAILABLE > 0
#define RBUF_POP(NAME, VAR) \
do { \
VAR = (NAME).buf[RBUF_RHEAD(NAME)]; \
(NAME).head = ((NAME.head) + 1) % RBUF_SIZE(NAME); \
(NAME).available--; \
} while(0)
#endif /* RINGBUFFER_H_ */
/*
* main.c
*
* Created on: Jun 11, 2014
* Author: Silly Freak
*/
#include <inttypes.h>
#include "ringbuffer.h"
RBUF(uint8_t, 10) ringbuf1;
int main(void) {
RBUF_INIT(ringbuf1);
if(RBUF_FREE(ringbuf1) > 0) {
RBUF_PUSH(ringbuf1, 1);
}
if(RBUF_AVAILABLE(ringbuf1) > 0) {
uint8_t var;
RBUF_POP(ringbuf1, var);
}
return 0;
}
Well, it's a ringbuffer. A canonical implementation would probably be this, but targeting microcontrollers, I wanted to avoid having to use a dynamic size while still maintaining reusability for different sizes. (A tutor at my university told me that having to dereference pointers for things like rb->buf[(rb->head + rb->available) % rb->size]
could get me point deductions, so I thought it might matter)
Questions:
- Was this decision (likely) worth it, performance wise?
- Regarding the
TODO
(~ line 10), should I- leave it as it is,
- make the user choose a type that fits his
SIZE
, - or make the compiler choose a type of appropriate size (is this possible?)
- Are there any offenses, style-wise? Formatting, naming conventions, comments/documentation, includes?
- Is my use of macros here considered abuse? Should/could they be formulated as inline functions?
- Is my use of macro parameters robust? For example, in
RBUF_POP
, I wroteVAR =
instead of(VAR) =
. Are there situations where this does not work? Would(VAR) =
universally work? - Any other things that would make this code better C?
size_t head, tail, length;
. Then no need for%
operator. 2) interrupt protect operations on these fields. \$\endgroup\$ – chux - Reinstate Monica Jun 13 '14 at 3:33