This commit is contained in:
2026-01-03 18:46:52 -06:00
commit 58665ffb88
9 changed files with 626 additions and 0 deletions

178
.clang-format Normal file
View File

@@ -0,0 +1,178 @@
---
Language: Cpp
# BasedOnStyle: Microsoft
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
#DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Always
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/
**/.cache/

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
CC=gcc
CFLAGS=\
-Wall \
-std=gnu11 \
ifdef $(DEBUG)
CFLAGS+=\
-DDEBUG\
-ffunctions-sections\
-O3\
-g3
endif
MK_DIR=mkdir -p
.PHONY: clean examples tests
all: examples
clean:
rm -rf bin
examples: clean
$(MK_DIR) bin/
$(CC) $(CFLAGS) -I. ./examples/p_queue_example.c -o ./bin/p_queue_example
$(CC) $(CFLAGS) -I. ./examples/p_cbuffer_example.c -o ./bin/p_cbuffer_example

1
README.md Normal file
View File

@@ -0,0 +1 @@
# PenguinBuffer

View File

@@ -0,0 +1,89 @@
/*
Penguin's Circular Buffer Example -- Turning a noisy bell curve into a less
noisy bell curve
Here's an example of using the circular buffer library for sensor data:
Let's say I have a sensor that gives me temperature at 1khz (using ideals so
it is exactly 1khz) If this sensor was only giving me raw analog data, I
might want to do some processing on these values so that I can make sure the
values are as clean as possible. The more samples we have, the closer we are
to the actual value. The faster we gather samples, the closer we get to
representing our data in realtime.
Using the sample rate, I can decide on a sampling window (in seconds) and an
OSR (Oversampling Rate). The larger the buffer size, the more memory I need,
so it is useful to find a happy medium between a large buffer and clean
values.
A sampling window is the window of time in which we can accept values for an
average value. Depending on your application, a sampling window may need to
be extremely small or maybe not so small. A rocket going extremely fast using
some sensor for real time controls will want an extremely small sampling
window as well as a lot of measurements for both clean, near noiseless data
and as close to realtime data as possible.
The sampling window is usually an engineering requirement given that can
match the sampling rate of the sensor with the following: sampling window (in
seconds) = 1 / frequency Here, the OSR is simply 1.
So at 1khz sample rate, let's say I decide I only need a 0.125 second sample
window and I want to clean up some noisy data. We can now use this equation:
OSR = frequency * sample window (in seconds)
All of these have ignored real world slowdowns like the time it takes to do
math on lower end hardware, interrupts slightly delaying the math, etc
Without wanting to do some hard analysis on whatever hardware we're using, I
usually take my frequency and half it to ensure timing requirements are met,
like so:
OSR = frequency / 2 * sample window
Please note: In applications that require real real-time data, this is not a
good way of doing things.
*/
#include "p_cbuffer.h"
DEFINE_CBUFFER(int);
#define CBUFFER_SIZE (6)
void display(cbuffer_int_t* cbuffer)
{
printf("Cbuffer:\n");
for (int ind = 0; ind < cbuffer->msize; ind++)
{
printf("[%d]: %d\n", ind, cbuffer->buffer[ind]);
}
printf("\n");
}
int main(int argc, char** argv)
{
cbuffer_int_t cbuffer;
int data[CBUFFER_SIZE];
cbuffer_int_init(&cbuffer, data, CBUFFER_SIZE);
cbuffer.push(&cbuffer, 1);
cbuffer.push(&cbuffer, 2);
cbuffer.push(&cbuffer, 3);
cbuffer.push(&cbuffer, 4);
cbuffer.push(&cbuffer, 5);
printf("Is cbuffer filled? %s\n",
cbuffer.is_filled(&cbuffer) ? "Yes" : "No");
cbuffer.push(&cbuffer, 6);
printf("Is cbuffer filled? %s\n",
cbuffer.is_filled(&cbuffer) ? "Yes" : "No");
display(&cbuffer);
cbuffer.push(&cbuffer, 7);
display(&cbuffer);
printf("Emptying cbuffer...\n");
printf("Is cbuffer filled? %s\n",
cbuffer.is_filled(&cbuffer) ? "Yes" : "No");
cbuffer.empty(&cbuffer);
cbuffer.push(&cbuffer, 8);
display(&cbuffer);
printf("Is cbuffer filled? %s\n",
cbuffer.is_filled(&cbuffer) ? "Yes" : "No");
}

View File

@@ -0,0 +1,50 @@
#include "p_queue.h"
DEFINE_QUEUE(int);
#define SIZE_QUEUE (256)
queue_int_t queue;
int buffer[SIZE_QUEUE];
void display(queue_int_t* queue)
{
if (queue->csize == 0)
{
printf("queue is empty\n");
}
else
{
printf("Queue:\n");
int i = queue->head;
for (int j = 0; j < queue->csize; j++)
{
printf("[%d]: %d ", i + j, queue->data[(i + j) % queue->msize]);
}
printf("\n");
}
}
int main(int argv, char** argc)
{
queue_int_init(&queue, buffer, SIZE_QUEUE);
// queue.enqueue(&queue, 10);
queue.empty(&queue);
printf("csize: %d\n", queue.csize);
queue.enqueue(&queue, 20);
queue.enqueue(&queue, 30);
queue.enqueue(&queue, 40);
display(&queue);
queue.enqueue(&queue, 10);
int val = 0;
queue.dequeue(&queue, &val);
queue.dequeue(&queue, &val);
display(&queue);
queue.empty(&queue);
display(&queue);
}

22
p_buffer.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef __PBUFFER_H__
#define __PBUFFER_H__
#define MAX_QUEUE_SIZE (512)
#define MAX_CBUFFER_SIZE (512)
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
typedef enum PB_STATUS
{
PB_GOOD = 0,
PB_BAD = 1,
PB_BAD_BUFFER_SIZE = 2,
PB_NULL_BUFFER = 3,
PB_NULL_DATA = 4,
PB_EMPTY_QUEUE = 5,
PB_FULL_QUEUE = 6
} PB_STATUS;
#endif

133
p_cbuffer.h Normal file
View File

@@ -0,0 +1,133 @@
// Resource/Inspiration:
// https://embedjournal.com/implementing-circular-buffer-embedded-c/
/*
Penguin's Circular Buffer -- a simple floating queue designed for low memory
usage (mainly for embedded)
This is a ring buffer with limited capabilities. It is meant as a container
for moving data. Normally included features such as checks to see if the
buffer is full or empty have been omitted because this type of buffer is
being implemented mainly for sensor data usage. Data is almost never read
individually, and even if it is, it isn't meant to be cleared on read. It is
a simple moving buffer that automatically writes over old data for the
purpose of keeping track of the most up to date data.
Notes:
- Initialization is mandatory using p_cb_<type>_init.
- If a circular buffer is not initialized, you will run into a LOT of
hard-to-debug problems. Just initialize it.
- By default, all cb types should be defined as disabled. This is to save
on size. Enable the ones you want to use.
Behavior:
- Oldest data is overwritten
- popping of data isn't implemented. If this feature is required, use a
ring buffer.
Acronyms:
- p: penguin
- pb: penguin buffer
- cb: circular buffer
- uX, where X is bit size: unsigned X bit integer
- iX, where X is bit size: signed X bit integer
*/
#ifndef _PCIRCULARBUFFER_H_
#define _PCIRCULARBUFFER_H_
#include "p_buffer.h"
#define DEFINE_CBUFFER(type) \
typedef struct cbuffer_##type##_t \
{ \
type* buffer; \
int head; \
int csize; \
int msize; \
\
PB_STATUS (*push)(struct cbuffer_##type##_t * cbuffer, type value); \
PB_STATUS (*empty)(struct cbuffer_##type##_t * cbuffer); \
PB_STATUS (*is_filled)(struct cbuffer_##type##_t * cbuffer); \
} cbuffer_##type##_t; \
\
PB_STATUS cbuffer_##type##_is_filled(cbuffer_##type##_t* cbuffer) \
{ \
return (cbuffer->csize == cbuffer->msize); \
} \
\
PB_STATUS cbuffer_##type##_push(cbuffer_##type##_t* cbuffer, type value) \
{ \
PB_STATUS ret = PB_GOOD; \
\
if (!cbuffer) \
{ \
ret = PB_NULL_BUFFER; \
} \
else \
{ \
cbuffer->csize = \
(cbuffer->csize >= cbuffer->msize ? cbuffer->msize \
: cbuffer->csize + 1); \
cbuffer->buffer[cbuffer->head] = value; \
cbuffer->head = (cbuffer->head + 1) % cbuffer->msize; \
} \
\
return ret; \
} \
PB_STATUS cbuffer_##type##_empty(cbuffer_##type##_t* cbuffer) \
{ \
PB_STATUS ret = PB_GOOD; \
do \
{ \
if (!cbuffer) \
{ \
ret = PB_NULL_BUFFER; \
break; \
} \
\
if (!cbuffer->buffer) \
{ \
ret = PB_NULL_DATA; \
break; \
} \
memset(cbuffer->buffer, 0, sizeof(type) * cbuffer->msize); \
cbuffer->head = 0; \
cbuffer->csize = 0; \
} while (0); \
return ret; \
} \
\
PB_STATUS cbuffer_##type##_init(cbuffer_##type##_t* circ_buffer, \
type* buff, int total_length) \
{ \
PB_STATUS ret = PB_GOOD; \
do \
{ \
if (!buff) \
{ \
ret = PB_NULL_DATA; \
break; \
} \
\
if (total_length > MAX_CBUFFER_SIZE || total_length <= 0) \
{ \
ret = PB_BAD_BUFFER_SIZE; \
break; \
} \
memset(circ_buffer, 0, sizeof(cbuffer_##type##_t)); \
memset(buff, 0, sizeof(type) * total_length); \
circ_buffer->buffer = buff; \
circ_buffer->msize = total_length; \
circ_buffer->csize = 0; \
circ_buffer->head = 0; \
circ_buffer->push = cbuffer_##type##_push; \
circ_buffer->empty = cbuffer_##type##_empty; \
circ_buffer->is_filled = cbuffer_##type##_is_filled; \
circ_buffer->empty(circ_buffer); \
} while (0); \
\
return ret; \
}
#endif

119
p_queue.h Normal file
View File

@@ -0,0 +1,119 @@
#ifndef __QUEUE_H__
#define __QUEUE_H__
#include "p_buffer.h"
// This macro defines a queue type. It doesn't make a queue for you.
// See below for an example on how to make a queue.
// Creates a type of queue_${type}_t of size ${size}.
// the size will be fixed for all instances of that type unfortunately.
#define DEFINE_QUEUE(type) \
typedef struct queue_##type##_t \
{ \
type* data; \
int head; \
int tail; \
int msize; \
int csize; \
\
PB_STATUS (*enqueue)(struct queue_##type##_t * queue, type val); \
PB_STATUS (*dequeue)(struct queue_##type##_t * queue, type* val); \
void (*empty)(struct queue_##type##_t * queue); \
bool (*is_full)(struct queue_##type##_t * queue); \
\
} queue_##type##_t; \
\
bool queue_##type##_is_full(queue_##type##_t* queue) \
{ \
return (queue->csize == queue->msize); \
} \
\
void queue_##type##_empty(queue_##type##_t* queue) \
{ \
memset(queue->data, 0, sizeof(type) * queue->msize); \
queue->head = -1; \
queue->tail = -1; \
queue->csize = 0; \
} \
\
PB_STATUS queue_##type##_enqueue(queue_##type##_t* queue, type val) \
{ \
PB_STATUS ret = PB_GOOD; \
if (queue->is_full(queue)) \
{ \
ret = PB_FULL_QUEUE; \
} \
else \
{ \
if (queue->head == -1) \
{ \
queue->head = 0; \
queue->csize = 0; \
queue->tail = queue->msize - 1; \
} \
queue->tail = (queue->tail + 1) % queue->msize; \
queue->data[queue->tail] = val; \
queue->csize++; \
} \
return ret; \
} \
\
PB_STATUS queue_##type##_dequeue(queue_##type##_t* queue, type* val) \
{ \
PB_STATUS ret = PB_GOOD; \
if (queue->csize == 0) \
{ \
ret = PB_EMPTY_QUEUE; \
} \
else \
{ \
*val = queue->data[queue->head]; \
queue->head = (queue->head + 1) % queue->csize; \
queue->csize -= 1; \
} \
return ret; \
} \
PB_STATUS queue_##type##_init(queue_##type##_t* queue, type* buffer, \
int total_length) \
{ \
PB_STATUS ret = PB_GOOD; \
do \
{ \
if (!queue) \
{ \
ret = PB_NULL_BUFFER; \
break; \
} \
\
if (total_length > MAX_QUEUE_SIZE || total_length <= 0) \
{ \
ret = PB_BAD_BUFFER_SIZE; \
break; \
} \
} while (0); \
memset(queue, 0, sizeof(queue_##type##_t)); \
memset(buffer, 0, sizeof(type) * total_length); \
queue->data = buffer; \
queue->msize = total_length; \
queue->head = -1; \
queue->tail = -1; \
queue->csize = 0; \
queue->enqueue = queue_##type##_enqueue; \
queue->dequeue = queue_##type##_dequeue; \
queue->empty = queue_##type##_empty; \
queue->is_full = queue_##type##_is_full; \
return ret; \
}
/*
Example:
DEFINE_QUEUE(int);
// in .c
queue_int_t queue;
const int SIZE_QUEUE = 256;
int buffer[SIZE_QUEUE];
queue_int_init(&queue, buffer, SIZE_QUEUE);
queue_int_enqueue(&queue, 12);
*/
#endif