From 58665ffb88a7781a9782a423f230de90b0a31367 Mon Sep 17 00:00:00 2001 From: penguin Date: Sat, 3 Jan 2026 18:46:52 -0600 Subject: [PATCH] init --- .clang-format | 178 +++++++++++++++++++++++++++++++++++ .gitignore | 2 + Makefile | 32 +++++++ README.md | 1 + examples/p_cbuffer_example.c | 89 ++++++++++++++++++ examples/p_queue_example.c | 50 ++++++++++ p_buffer.h | 22 +++++ p_cbuffer.h | 133 ++++++++++++++++++++++++++ p_queue.h | 119 +++++++++++++++++++++++ 9 files changed, 626 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/p_cbuffer_example.c create mode 100644 examples/p_queue_example.c create mode 100644 p_buffer.h create mode 100644 p_cbuffer.h create mode 100644 p_queue.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..311ee05 --- /dev/null +++ b/.clang-format @@ -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 +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..beb1251 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +**/.cache/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..272ffd6 --- /dev/null +++ b/Makefile @@ -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 + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..263e790 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# PenguinBuffer diff --git a/examples/p_cbuffer_example.c b/examples/p_cbuffer_example.c new file mode 100644 index 0000000..b797902 --- /dev/null +++ b/examples/p_cbuffer_example.c @@ -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"); +} diff --git a/examples/p_queue_example.c b/examples/p_queue_example.c new file mode 100644 index 0000000..9c547a7 --- /dev/null +++ b/examples/p_queue_example.c @@ -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); +} diff --git a/p_buffer.h b/p_buffer.h new file mode 100644 index 0000000..17f66cf --- /dev/null +++ b/p_buffer.h @@ -0,0 +1,22 @@ +#ifndef __PBUFFER_H__ +#define __PBUFFER_H__ + +#define MAX_QUEUE_SIZE (512) +#define MAX_CBUFFER_SIZE (512) + +#include +#include +#include + +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 diff --git a/p_cbuffer.h b/p_cbuffer.h new file mode 100644 index 0000000..7b0afed --- /dev/null +++ b/p_cbuffer.h @@ -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__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 diff --git a/p_queue.h b/p_queue.h new file mode 100644 index 0000000..e9d7c21 --- /dev/null +++ b/p_queue.h @@ -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