diff --git a/.clang-format b/.clang-format
index 38e8a64..b8944e3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,11 +1,13 @@
---
BasedOnStyle: LLVM
AccessModifierOffset: 0
+AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: Left
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveBitFields: Consecutive
AlignConsecutiveMacros: Consecutive
AlignEscapedNewlines: Left
+AllowAllArgumentsOnNextLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: WithoutElse
AlwaysBreakTemplateDeclarations: Yes
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..62840e1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.pio
+.vscode
+logs
\ No newline at end of file
diff --git a/.img/logo.png b/.img/logo.png
new file mode 100644
index 0000000..7b64a81
Binary files /dev/null and b/.img/logo.png differ
diff --git a/README.md b/README.md
index defa1e6..7dd23c0 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,312 @@
-# RTOScppESP32
-C++ FreeRTOS wrapper to use with ESP32
+
+
+
+ RTOScppESP32
+
+
+
+ Make your real-time application development a breeze!
+
+
+[](https://www.ardu-badge.com/RTOScppESP32)
+[](https://registry.platformio.org/libraries/alkonosst/RTOScppESP32)
+
+---
+
+# Table of Contents
+
+- [Description](#description)
+- [Motivation](#motivation)
+- [Documentation](#documentation)
+- [Usage](#usage)
+ - [Adding library to Arduino IDE](#adding-library-to-arduino-ide)
+ - [Adding library to platformio.ini (PlatformIO)](#adding-library-to-platformioini-platformio)
+ - [Using the library](#using-the-library)
+ - [Including the library](#including-the-library)
+ - [Dynamic, static and external memory objects](#dynamic-static-and-external-memory-objects)
+ - [RTOS objects](#rtos-objects)
+ - [Using tasks](#using-tasks)
+ - [Using timers](#using-timers)
+ - [Using locks](#using-locks)
+ - [Using queues](#using-queues)
+ - [Using FreeRTOS buffers](#using-freertos-buffers)
+ - [Using ESP-IDF Ring Buffers](#using-esp-idf-ring-buffers)
+ - [Using queue sets](#using-queue-sets)
+
+# Description
+
+**RTOScppESP32** is your go-to C++ library for the ESP32 platform, designed to make real-time application development fun and easy. With a user-friendly API, it brings the power of FreeRTOS to your fingertips, allowing you to effortlessly manage tasks, timers, queues, buffers, and locks. Key features include:
+
+- Task Management: Simplify task creation and management with intuitive functions.
+- Timers: Use dynamic and static timers for precise timing operations.
+- Queues: Enhance inter-task communication with versatile queue options.
+- Buffers: Efficiently handle data with various buffer types.
+- Locks: Ensure thread safety with robust locking mechanisms.
+
+# Motivation
+
+I've been working with the ESP32 for a while now, and I've always found the FreeRTOS API to be a bit
+_cumbersome_. Maybe it's the fact that I prefer C++ over C. I like the idea that I can create classes
+and objects to encapsulate functionality and make my code more readable and maintainable, and take
+advantage of the IDE features like code completion to see what methods and properties are available.
+I don't like the idea of having to remember function names for all the FreeRTOS API calls, or having
+to look them up in the documentation every time I need to use them.
+
+I wanted to create a library that would simplify the process of working with
+FreeRTOS, making it more intuitive and user-friendly. That's how **RTOScppESP32** was born. I hope
+this library will help you streamline your real-time application development and make your projects
+more enjoyable.
+
+# Documentation
+
+If you are new to FreeRTOS, I recommend you to read the [official
+documentation](https://www.freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/01-RTOS-fundamentals).
+Also you can check the [API
+Reference](https://www.freertos.org/Documentation/02-Kernel/04-API-references/01-Task-creation/00-TaskHandle)
+for more information about the FreeRTOS API.
+
+And if you want to know how the FreeRTOS is implemented in the ESP32, you can check the [ESP-IDF
+documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/freertos.html).
+It is worth reading the [Ring Buffers API
+Reference](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/freertos_additions.html#ring-buffers),
+because that's an additional feature that is not present in the FreeRTOS API and is implemented in
+this library.
+
+Besides that, all the classes and methods are documented in the source code, so you can check the
+comments in the header files to see what each method does and how to use it. Also you can check the
+examples in the `examples` folder to see how to use the library in practice.
+
+# Usage
+
+## Adding library to Arduino IDE
+
+Search for the library in the Library Manager.
+
+## Adding library to platformio.ini (PlatformIO)
+
+```ini
+; Most recent changes
+lib_deps =
+ https://github.com/alkonosst/RTOScppESP32.git
+
+; Release vx.y.z (using an exact version is recommended)
+lib_deps =
+ https://github.com/alkonosst/RTOScppESP32.git#v1.0.0
+```
+
+## Using the library
+
+### Including the library
+
+To use a feature, you need to include the corresponding header file. For example, to use **Tasks**
+you need to include the `RTOScppESP32.h` file:
+
+```cpp
+#include "RTOScppTask.h"
+```
+
+### Dynamic, static and external memory objects
+
+The library provides three types of objects: **Dynamic**, **Static** and some of them have **External**:
+
+- **Dynamic**: The object will be created dynamically in the heap memory. When you compile your
+ program, the size of the object will not be included in the total RAM usage of the program.
+- **Static**: The object will be created statically in the stack memory. When you compile your
+ program, the size of the object will be included in the total RAM usage of the program.
+- **External**: The object will be created externally by the user. The user is responsible for
+ allocating memory for the object and passing the pointer to the corresponding method for creating
+ it. Useful when you want to create the object in a specific memory region (_like the ESP32
+ external PSRAM_).
+
+I recommend you to use the **Static** objects whenever possible, because they are more efficient in
+terms of memory usage and performance. It is a good practice to avoid dynamic memory allocation in a
+microcontroller with limited resources.
+
+## RTOS objects
+
+For now, only the constructors are explained here. You can check the methods using the code
+completion feature of your IDE or checking the source code of each object type.
+
+### Using tasks
+
+```cpp
+// Dynamic version example
+// - The stack size is 4096 bytes
+// - Empty constructor, so you need to call the `create(parameters)` method to create the task
+void task1Function(void* params);
+TaskDynamic*stack size*/ 4096> task_1;
+
+// Static version example
+// - The stack size is 4096 bytes
+// - All parameters are passed to the constructor
+// - You can choose to create the task in the constructor or later with the `create()` method
+void task2Function(void* params);
+TaskStatic*stack size*/ 4096> task_1("Task2Name", task2Function, /*priority*/ 1, /*parameters*/ nullptr, /*core*/ 1, /*create*/ false);
+
+void setup() {
+ // Setup and create the dynamic task
+ task_1.create("Task1Name", task1Function, /*priority*/ 1, /*parameters*/ nullptr, /*core*/ 1);
+
+ // Setup and create the static task
+ task_2.create();
+}
+```
+
+### Using timers
+
+```cpp
+// Dynamic version example
+// - Empty constructor, so you need to call the `create(parameters)` method to create the timer
+void timer1Callback(TimerHandle_t timer);
+TimerDynamic timer_1;
+
+// Static version example
+// - All parameters are passed to the constructor
+// - You can choose to start the timer in the constructor or later with the `start()` method
+void timer2Callback(TimerHandle_t timer);
+TimerStatic timer_2(
+ /*name*/ "Timer2Name",
+ /*callback*/ timer2Callback,
+ /*period*/ 1000,
+ /*id*/ nullptr,
+ /*autoreload*/ true,
+ /*start*/ true);
+
+void setup() {
+ // Create and start the dynamic timer
+ timer_1.create(
+ /*name*/ "Timer1Name",
+ /*callback*/ timer1Callback,
+ /*period*/ 1000,
+ /*id*/ nullptr,
+ /*autoreload*/ true,
+ /*start*/ true);
+}
+```
+
+### Using locks
+
+```cpp
+// Mutexes
+MutexDynamic mutex_1; // Dynamic version
+MutexStatic mutex_2; // Static version
+
+// Recursive mutexes
+MutexRecursiveDynamic mutex_recursive_1; // Dynamic version
+MutexRecursiveStatic mutex_recursive_2; // Static version
+
+// Binary semaphores
+SemBinaryDynamic sem_binary_1; // Dynamic version
+SemBinaryStatic sem_binary_2; // Static version
+
+// Counting semaphores
+SemCountingDynamic*max count*/ 5, /*initial count*/ 0> sem_counting_1; // Dynamic version
+SemCountingStatic*max count*/ 5, /*initial count*/ 0> sem_counting_2; // Static version
+```
+
+### Using queues
+
+```cpp
+// Dynamic version
+QueueDynamic*type*/ uint32_t, /*length*/ 10> queue_1;
+
+// Static version
+QueueStatic*type*/ uint32_t, /*length*/ 10> queue_2;
+
+// External version
+QueueExternal*type*/ uint32_t, /*length*/ 10> queue_3;
+
+void setup() {
+ // Create the external queue
+ static uint8_t* ext_buffer = static_cast(malloc(queue_3.REQUIRED_SIZE));
+ queue_3.create(ext_buffer);
+}
+```
+
+### Using FreeRTOS buffers
+
+```cpp
+// Stream buffers
+StreamBufferDynamic*trigger bytes*/ 5, /*length*/ 10> stream_buffer_1; // Dynamic version
+StreamBufferStatic*trigger bytes*/ 5, /*length*/ 10> stream_buffer_2; // Static version
+StreamBufferExternalStorage*trigger bytes*/ 5, /*length*/ 10> stream_buffer_3; // External version
+
+// Message buffers
+MessageBufferDynamic*length*/ 10> message_buffer_1; // Dynamic version
+MessageBufferStatic*length*/ 10> message_buffer_2; // Static version
+MessageBufferExternalStorage*length*/ 10> message_buffer_3; // External version
+
+void setup() {
+ // Create the external stream buffer
+ static uint8_t* sb_ext_buffer = static_cast(malloc(stream_buffer_3.REQUIRED_SIZE));
+ stream_buffer_3.create(sb_ext_buffer);
+
+ // Create the external message buffer
+ static uint8_t* mb_ext_buffer = static_cast(malloc(message_buffer_3.REQUIRED_SIZE));
+ message_buffer_3.create(mb_ext_buffer);
+}
+```
+
+### Using ESP-IDF Ring Buffers
+
+```cpp
+// No-split ring buffers
+RingBufferNoSplitDynamic*type*/ char, /*length*/ 10> ring_buffer_no_split_1; // Dynamic version
+RingBufferNoSplitStatic*type*/ char, /*length*/ 10> ring_buffer_no_split_2; // Static version
+RingBufferNoSplitExternalStorage*type*/ char, /*length*/ 10> ring_buffer_no_split_3; // External version
+
+// Split ring buffers
+RingBufferSplitDynamic*type*/ char, /*length*/ 10> ring_buffer_split_1; // Dynamic version
+RingBufferSplitStatic*type*/ char, /*length*/ 10> ring_buffer_split_2; // Static version
+RingBufferSplitExternalStorage*type*/ char, /*length*/ 10> ring_buffer_split_3; // External version
+
+// Byte ring buffers
+RingBufferByteDynamic*length*/ 10> ring_buffer_byte_1; // Dynamic version
+RingBufferByteStatic*length*/ 10> ring_buffer_byte_2; // Static version
+RingBufferByteExternalStorage*length*/ 10> ring_buffer_byte_3; // External version
+
+void setup() {
+ // Create the external no-split ring buffer
+ static uint8_t* rbs_ext_buffer = static_cast(malloc(ring_buffer_no_split_3.REQUIRED_SIZE));
+ ring_buffer_no_split_3.create(rbs_ext_buffer);
+
+ // Create the external split ring buffer
+ static uint8_t* rbs_ext_buffer = static_cast(malloc(ring_buffer_split_3.REQUIRED_SIZE));
+ ring_buffer_split_3.create(rbs_ext_buffer);
+
+ // Create the external byte ring buffer
+ static uint8_t* rbs_ext_buffer = static_cast(malloc(ring_buffer_byte_3.REQUIRED_SIZE));
+ ring_buffer_byte_3.create(rbs_ext_buffer);
+}
+```
+
+### Using queue sets
+
+```cpp
+// Binary Semaphore
+SemBinaryStatic sem;
+
+// Queue, type uint32_t, size 10
+QueueStatic queue;
+
+// Queue set, holding 1 event from the semaphore + 10 events from the queue
+QueueSet queue_set(1 + 10);
+
+void setup() {
+ // Add the semaphore and queue to the queue set
+ queue_set.add(sem);
+ queue_set.add(queue);
+}
+
+void loop() {
+ // Block indefinitely until an event occurs in the queue set
+ QueueSetHandle_t member = queue_set.select();
+
+ if (member == sem) {
+ // Semaphore event
+ } else if (member == queue) {
+ // Queue event
+ }
+}
+```
diff --git a/examples/BufferMessage/BufferMessage.ino b/examples/BufferMessage/BufferMessage.ino
new file mode 100644
index 0000000..6cb38d6
--- /dev/null
+++ b/examples/BufferMessage/BufferMessage.ino
@@ -0,0 +1,118 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a message buffer to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the message buffer. The producer task sends data
+ * to the consumer task every second.
+ * - The consumer task waits indefinitely for data to be received and blinks the LED when it
+ * receives the data.
+ * - Compared to StreamBuffers, MessageBuffers do not have a trigger level, so the consumer task
+ * will receive the data as soon as it is available.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received.
+ */
+
+#include "RTOScppBuffer.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Buffers;
+using namespace RTOS::Tasks;
+
+// Message buffer with a size of 10 bytes
+MessageBufferStatic<10> buffer;
+
+// Tasks to test the buffer
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the buffer was created
+ if (!buffer) {
+ Serial.println("Buffer not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+ }
+ }
+}
+
+void producerFn(void* params) {
+ const char* message = "1234";
+ const uint8_t len = strlen(message);
+
+ for (;;) {
+ // Delay to simulate work being done
+ delay(1000);
+
+ Serial.printf("Producer: Sending message: %s\n", message);
+
+ // Send the message to the buffer
+ buffer.send(message, len);
+ }
+}
+
+void consumerFn(void* params) {
+ char message[20];
+ const size_t len = sizeof(message);
+
+ for (;;) {
+ // Receive the message from the buffer and store the number of bytes received
+ uint32_t bytes_recv = buffer.receive(message, len);
+
+ if (bytes_recv == 0) {
+ Serial.println("\tConsumer: Failed to receive!");
+ continue;
+ }
+
+ // Add the null terminator to the message
+ message[bytes_recv] = '\0';
+
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received: %s\n", message);
+ blinkLED();
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/BufferStream/BufferStream.ino b/examples/BufferStream/BufferStream.ino
new file mode 100644
index 0000000..034c38e
--- /dev/null
+++ b/examples/BufferStream/BufferStream.ino
@@ -0,0 +1,133 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a stream buffer to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the stream buffer. The producer task sends data
+ * to the consumer task every second.
+ * - The consumer task waits indefinitely for data to be received and blinks the LED when it
+ * receives the data.
+ * - The consumer task will not receive the data until the trigger level is reached (5 bytes).
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, and
+ * changes the trigger level of the buffer when '1' or '5' is received.
+ */
+
+#include "RTOScppBuffer.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Buffers;
+using namespace RTOS::Tasks;
+
+// Stream buffer with a size of 10 bytes and a trigger level of 5 bytes
+StreamBufferStatic<10, 5> buffer;
+
+// Tasks to test the buffer
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the buffer was created
+ if (!buffer) {
+ Serial.println("Buffer not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Change the trigger level of the buffer
+ case '1':
+ {
+ if (buffer.setTriggerLevel(1)) {
+ Serial.println("Trigger level set to 1");
+ }
+ } break;
+
+ case '5':
+ {
+ if (buffer.setTriggerLevel(5)) {
+ Serial.println("Trigger level set to 5");
+ }
+ } break;
+ }
+ }
+}
+
+void producerFn(void* params) {
+ const char* message = "1234";
+ const uint8_t len = strlen(message);
+
+ for (;;) {
+ // Delay to simulate work being done
+ delay(1000);
+
+ Serial.printf("Producer: Sending message: %s\n", message);
+
+ // Send the message to the buffer
+ buffer.send(message, len);
+ }
+}
+
+void consumerFn(void* params) {
+ char message[20];
+ const size_t len = sizeof(message);
+
+ for (;;) {
+ // Receive the message from the buffer when the trigger level (5) is reached
+ uint32_t bytes_recv = buffer.receive(message, len);
+
+ if (bytes_recv == 0) {
+ Serial.println("\tConsumer: Failed to receive!");
+ continue;
+ }
+
+ // Add the null terminator to the message
+ message[bytes_recv] = '\0';
+
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received: %s\n", message);
+ blinkLED();
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/Mutex/Mutex.ino b/examples/Mutex/Mutex.ino
new file mode 100644
index 0000000..8d0d4a9
--- /dev/null
+++ b/examples/Mutex/Mutex.ino
@@ -0,0 +1,113 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a mutex to protect a shared resource, in this case, the LED.
+ * - Two tasks are created to illustrate the use of the mutex. Both tasks blink the LED,
+ * but one blinks it 5 times and the other 10 times.
+ * - The function blinkLED is used to blink the LED and is protected by the mutex, meaning that only
+ * one task can access the LED at a time.
+ * - When a mutex is taken, another task trying to take it will be blocked until the mutex is given.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received.
+ */
+
+#include "RTOScppLock.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Locks;
+using namespace RTOS::Tasks;
+
+// Mutex object
+MutexStatic mutex;
+
+// Tasks to test the lock
+void task1Fn(void* params);
+void task2Fn(void* params);
+TaskStatic<4 * 1024> task1("Task1", task1Fn, 1);
+TaskStatic<4 * 1024> task2("Task2", task2Fn, 1);
+
+// Shared resource example: blinking LED
+void blinkLED(const uint8_t times);
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the mutex was created
+ if (!mutex) {
+ Serial.println("Mutex not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!task1.create()) {
+ Serial.println("Task 1 not created");
+ while (true)
+ ;
+ }
+
+ if (!task2.create()) {
+ Serial.println("Task 2 not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+ }
+ }
+}
+
+void task1Fn(void* params) {
+ for (;;) {
+ // Blink the LED 5 times
+ blinkLED(5);
+
+ // Small delay to allow the other task to run
+ // If there weren't any delay, the task2 would never run (starvation effect)
+ delay(10);
+ }
+}
+
+void task2Fn(void* params) {
+ for (;;) {
+ // Blink the LED 10 times
+ blinkLED(10);
+
+ // Small delay to allow the other task to run
+ delay(10);
+ }
+}
+
+void blinkLED(const uint8_t times) {
+ // Take the mutex to protect the current blinking
+ mutex.take();
+
+ Serial.printf("Blinking LED %u times... ", times);
+
+ for (uint8_t i = 0; i < times; i++) {
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+ }
+
+ Serial.print("waiting... ");
+ delay(2000);
+ Serial.println("done!");
+
+ // Give the mutex
+ mutex.give();
+}
\ No newline at end of file
diff --git a/examples/MutexRecursive/MutexRecursive.ino b/examples/MutexRecursive/MutexRecursive.ino
new file mode 100644
index 0000000..1ba26e1
--- /dev/null
+++ b/examples/MutexRecursive/MutexRecursive.ino
@@ -0,0 +1,127 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a recursive mutex to protect a shared resource, in this case, the
+ * LED.
+ * - Two tasks are created to illustrate the use of the mutex. Both tasks blink the LED,
+ * but one blinks it 5 times and the other 10 times.
+ * - The function blinkLED is used to blink the LED and is protected by the mutex, meaning that only
+ * one task can access the LED at a time.
+ * - When a mutex is taken, another task trying to take it will be blocked until the mutex is given.
+ * - A recursive mutex allows the same task to take the mutex multiple times without blocking
+ * itself. To release the mutex, the mutex must be given the same number of times it was taken.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received.
+ */
+
+#include "RTOScppLock.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Locks;
+using namespace RTOS::Tasks;
+
+// Mutex recursive object
+MutexRecursiveStatic mutex_recursive;
+
+// Tasks to test the lock
+void task1Fn(void* params);
+void task2Fn(void* params);
+TaskStatic<4 * 1024> task1("Task1", task1Fn, 1);
+TaskStatic<4 * 1024> task2("Task2", task2Fn, 1);
+
+// Shared resource example: blinking LED
+void blinkLED(const uint8_t times);
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the mutex was created
+ if (!mutex_recursive) {
+ Serial.println("Recursive Mutex not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!task1.create()) {
+ Serial.println("Task 1 not created");
+ while (true)
+ ;
+ }
+
+ if (!task2.create()) {
+ Serial.println("Task 2 not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+ }
+ }
+}
+
+void task1Fn(void* params) {
+ for (;;) {
+ // Blink the LED 5 times
+ blinkLED(5);
+
+ // Release the mutex for the second time
+ mutex_recursive.give();
+
+ // Small delay to allow the other task to run
+ // If there weren't any delay, the task2 would never run (starvation effect)
+ delay(10);
+ }
+}
+
+void task2Fn(void* params) {
+ for (;;) {
+ // Blink the LED 10 times
+ blinkLED(10);
+
+ // Release the mutex for the second time
+ mutex_recursive.give();
+
+ // Small delay to allow the other task to run
+ delay(10);
+ }
+}
+
+void blinkLED(const uint8_t times) {
+ // Take the mutex for the first time
+ mutex_recursive.take();
+
+ Serial.printf("Blinking LED %u times... ", times);
+
+ for (uint8_t i = 0; i < times; i++) {
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+ }
+
+ Serial.print("waiting... ");
+ delay(2000);
+ Serial.println("done!");
+
+ // Take the mutex for the second time
+ mutex_recursive.take();
+
+ // Give the mutex one time
+ // To release the mutex, the mutex must be given the same number of times it was taken
+ // So, the mutex will be released after the second call to give
+ mutex_recursive.give();
+}
\ No newline at end of file
diff --git a/examples/Queue/Queue.ino b/examples/Queue/Queue.ino
new file mode 100644
index 0000000..506d0e9
--- /dev/null
+++ b/examples/Queue/Queue.ino
@@ -0,0 +1,139 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a queue to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the queue. The producer task sends data to the
+ * consumer task every second.
+ * - The consumer task waits indefinitely for data to be received and blinks the LED when it
+ * receives the data.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, prints
+ * the number of messages in the queue when 'p' is received, and sends data to the queue when 'a' is
+ * received.
+ */
+
+#include "RTOScppQueue.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Queues;
+using namespace RTOS::Tasks;
+
+// Custom data to send through the queue
+struct CustomData {
+ uint8_t a;
+ uint8_t b;
+};
+
+// Queue object size 10
+QueueStatic queue;
+
+// Tasks to test the queue
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the queue was created
+ if (!queue) {
+ Serial.println("Queue not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Print the queue status
+ case 'p': Serial.printf("Serial: Queue messages: %u\n", queue.getAvailableMessages()); break;
+
+ // Add a message to the queue with a timeout of 100ms
+ case 'a':
+ {
+ static CustomData data{255, 255};
+
+ if (!queue.add(data, pdMS_TO_TICKS(100))) {
+ Serial.println("Serial: Queue full!");
+ } else {
+ Serial.printf("Serial: Data sent: %u, %u\n", data.a, data.b);
+ }
+ } break;
+ }
+ }
+}
+
+void producerFn(void* params) {
+ // Custom data to send
+ CustomData data{};
+
+ for (;;) {
+ // Fill the data
+ data.a++;
+ data.b += 2;
+
+ // Send the data to the queue with a timeout of 100ms
+ if (!queue.add(data, pdMS_TO_TICKS(100))) {
+ Serial.println("Producer: Queue full!");
+ } else {
+ Serial.printf("Producer: Data sent: %u, %u\n", data.a, data.b);
+ }
+
+ // Delay to allow the other task to run
+ delay(1000);
+ }
+}
+
+void consumerFn(void* params) {
+ // Custom data to receive
+ CustomData data{};
+
+ for (;;) {
+ // Block until data is received, then pop it from the queue
+ queue.pop(data);
+
+ // Print the data
+ Serial.printf("\tConsumer: Data received: %u, %u\n", data.a, data.b);
+
+ // Blink the LED
+ blinkLED();
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/QueueSet/QueueSet.ino b/examples/QueueSet/QueueSet.ino
new file mode 100644
index 0000000..d3d4f0e
--- /dev/null
+++ b/examples/QueueSet/QueueSet.ino
@@ -0,0 +1,183 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "RTOScppQueueSet.h"
+#include "RTOScppTask.h"
+using namespace RTOS::QueueSets;
+using namespace RTOS::Locks;
+using namespace RTOS::Queues;
+using namespace RTOS::RingBuffers;
+using namespace RTOS::Tasks;
+
+/** Explanation of the example:
+ * - This example shows how to use a queue set to receive events from a semaphore, a queue, and a
+ * ring buffer.
+ * - A task is created to illustrate the use of the queue set. The task waits indefinitely for an
+ * event to be received and blinks the LED when it receives the event.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, gives the
+ * semaphore when 's' is received, sends data to the queue when 'q' is received, and sends data to
+ * the ring buffer when 'b' is received.
+ * - The queue set is created with a length of 1 + 10 + 3 = 14, which is the sum of the maximum
+ * number of events that can be stored in the semaphore, queue, and buffer.
+ * - The queue set is subscribed to the semaphore, queue, and buffer.
+ * - The task blocks indefinitely until an event is received from the queue set.
+ * - The task checks which event was received and blinks the LED accordingly.
+ * - Try to spam the serial input with 's', 'q', and 'b' to see the LED blink and the messages being
+ * received.
+ */
+
+// Binary Semaphore
+SemBinaryStatic sem;
+
+// Queue, type uint32_t, size 10
+QueueStatic queue;
+
+// No-split Ring Buffer with a size of 32 bytes
+// - The max item size is ((bufferSize / 2) - headerSize), headerSize being 8 bytes
+// - In this case, the max item size is (32 / 2) - 8 = 16 bytes
+// - Remember that internally the buffer will be aligned to the next 4 bytes multiple, so if you use
+// a size of 30, it will be aligned to 32 bytes
+RingBufferNoSplitStatic buffer;
+
+// Queue set to hold events from the semaphore, queue and buffer
+// - The size of the queue set must be the maximum number of events that can be stored
+// - In this case, the maximum number of events is 1:
+// - 1 for the semaphore
+// - 10 for the queue
+// - 3 for the buffer ( (8+4)*3 = 36 bytes ), 3 items of 4 bytes with a header of 8 bytes each. In
+// theory it could hold 2 items, but 3 is selected for safety
+// - Note that an assert will be triggered if the queue set length is less than the sum of the
+// member events
+// - Assert message: (pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength)
+QueueSet queue_set(1 + 10 + 3);
+
+// Tasks to test the locks
+void taskFn(void* params);
+TaskStatic<4 * 1024> task("Task", taskFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the objects were created
+ if (!sem || !queue || !buffer) {
+ Serial.println("One or more objects not created");
+ while (true)
+ ;
+ }
+
+ // Create the task
+ if (!task.create()) {
+ Serial.println("Task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Give the semaphore
+ case 's':
+ Serial.println("Serial: Giving semaphore...");
+ sem.give();
+ break;
+
+ // Send a message to the queue
+ case 'q':
+ {
+ const uint32_t message = 1234;
+ Serial.printf("Serial: Sending message to queue: %u\n", message);
+
+ if (!queue.add(message, pdMS_TO_TICKS(100))) {
+ Serial.println("Serial: Failed to send message to queue");
+ }
+ } break;
+
+ // Send a message to the buffer
+ case 'b':
+ {
+ const char* message = "1234";
+ const uint8_t len = strlen(message);
+
+ Serial.printf("Serial: Sending message: %s\n", message);
+
+ // Send the message to the buffer, including the null terminator, with a timeout of 100ms
+ if (!buffer.send(message, len + 1, pdMS_TO_TICKS(100))) {
+ Serial.println("Serial: Failed to send message to buffer");
+ }
+ } break;
+ }
+ }
+}
+
+void taskFn(void* params) {
+ // Subscribe the semaphore, queue and buffer to the queue set
+ queue_set.add(sem);
+ queue_set.add(queue);
+ queue_set.add(buffer);
+
+ uint32_t queue_msg;
+
+ for (;;) {
+ // Block indefinitely until an event is received
+ auto event = queue_set.select();
+
+ if (!event) {
+ Serial.println("\tTask: Queue set or member not created");
+ while (true)
+ ;
+ }
+
+ // Check which event was received
+
+ // Semaphore
+ if (event == sem) {
+ Serial.println("\tTask: Received semaphore event");
+ sem.take();
+ blinkLED();
+ }
+
+ // Queue
+ else if (event == queue) {
+ queue.pop(queue_msg);
+ Serial.printf("\tTask: Received message from queue: %u\n", queue_msg);
+ blinkLED();
+ }
+
+ // Ring buffer
+ else if (event == buffer) {
+ size_t len;
+ char* msg = buffer.receive(len);
+
+ Serial.printf("\tTask: Received message from buffer: %s\n", msg);
+ blinkLED();
+
+ // Return the item once it's no longer needed
+ buffer.returnItem(msg);
+ }
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/RingBufferByte/RingBufferByte.ino b/examples/RingBufferByte/RingBufferByte.ino
new file mode 100644
index 0000000..aa7e8bd
--- /dev/null
+++ b/examples/RingBufferByte/RingBufferByte.ino
@@ -0,0 +1,147 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "RTOScppRingBuffer.h"
+#include "RTOScppTask.h"
+using namespace RTOS::RingBuffers;
+using namespace RTOS::Tasks;
+
+/** Explanation of the example:
+ * - This example shows how to use a byte ring buffer to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the byte ring buffer. The producer task sends
+ * data to the consumer task every second.
+ * - The consumer task waits for data to be received and blinks the LED when it receives the data.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, prints
+ * the buffer information when 'i' is received, and sends a message to the buffer when 'a' is
+ * received.
+ * - The consumer task will receive up to 4 bytes from the buffer and print them. So, you will see
+ * in the serial monitor that the message is received in chunks.
+ */
+
+// Byte Ring Buffer with a size of 32 bytes
+// - The max item size is 32 bytes and the buffer is aligned to the next 4 bytes multiple
+RingBufferByteStatic<32> buffer;
+
+// Tasks to test the buffer
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the buffer was created
+ if (!buffer) {
+ Serial.println("Buffer not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Print buffer information
+ case 'i':
+ {
+ size_t max_item_size = buffer.getMaxItemSize();
+ size_t free_size = buffer.getFreeSize();
+
+ Serial.printf("Serial: Buffer size: %u/%u\n", free_size, max_item_size);
+ break;
+ }
+
+ // Add a message to the buffer
+ case 'a':
+ {
+ const uint8_t message[] = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
+
+ Serial.printf("Serial: Sending message\n");
+
+ // Send the message to the buffer, with a timeout of 100ms
+ if (!buffer.send(message, sizeof(message), pdMS_TO_TICKS(100))) {
+ Serial.println("Failed to send message to buffer");
+ }
+ break;
+ }
+ }
+ }
+}
+
+void producerFn(void* params) {
+ const uint8_t message[] = {'1', '2', '3', '4', '5', '6', '7', '8'};
+
+ for (;;) {
+ // Delay to simulate work being done
+ delay(1000);
+
+ Serial.printf("Producer: Sending message\n");
+
+ // Send the message to the buffer
+ buffer.send(message, sizeof(message));
+ }
+}
+
+void consumerFn(void* params) {
+ char chunk[5]; // Buffer to receive the message in chunks
+
+ for (;;) {
+ // Receive up to 4 bytes from the buffer
+ size_t bytes_recv;
+ uint8_t* msg = buffer.receiveUpTo(4, bytes_recv);
+
+ if (msg == nullptr) {
+ Serial.println("\tConsumer: Failed to receive!");
+ continue;
+ }
+
+ // Store the message in a buffer and null-terminate it
+ memcpy(chunk, msg, bytes_recv);
+ chunk[bytes_recv] = '\0';
+
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received (%u): %s\n", bytes_recv, chunk);
+ blinkLED();
+
+ // Return the item once it's no longer needed
+ buffer.returnItem(msg);
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/RingBufferNoSplit/RingBufferNoSplit.ino b/examples/RingBufferNoSplit/RingBufferNoSplit.ino
new file mode 100644
index 0000000..093d5e4
--- /dev/null
+++ b/examples/RingBufferNoSplit/RingBufferNoSplit.ino
@@ -0,0 +1,144 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "RTOScppRingBuffer.h"
+#include "RTOScppTask.h"
+using namespace RTOS::RingBuffers;
+using namespace RTOS::Tasks;
+
+/** Explanation of the example:
+ * - This example shows how to use a no-split ring buffer to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the no-split ring buffer. The producer task
+ * sends data to the consumer task every second.
+ * - The consumer task waits indefinitely for data to be received and blinks the LED when it
+ * receives the data.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, prints
+ * the buffer information when 'i' is received, and sends a message to the buffer when 'a' is
+ * received.
+ */
+
+// No-split Ring Buffer with a size of 32 bytes
+// - The max item size is ((bufferSize / 2) - headerSize), headerSize being 8 bytes
+// - In this case, the item is (32 / 2) - 8 = 8 bytes
+// - Remember that internally the buffer will be aligned to the next 4 bytes multiple, so if you use
+// a size of 30, it will be aligned to 32 bytes
+RingBufferNoSplitStatic buffer;
+
+// Tasks to test the buffer
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the buffer was created
+ if (!buffer) {
+ Serial.println("Buffer not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Print buffer information
+ case 'i':
+ {
+ size_t max_item_size = buffer.getMaxItemSize();
+ size_t free_size = buffer.getFreeSize();
+
+ Serial.printf("Serial: Buffer size: %u/%u\n", free_size, max_item_size);
+ break;
+ }
+
+ // Add a message to the buffer
+ case 'a':
+ {
+ const char* message = "Hello!";
+ const uint8_t len = strlen(message);
+
+ Serial.printf("Serial: Sending message: %s\n", message);
+
+ // Send the message to the buffer including the null terminator, with a timeout of 100ms
+ if (!buffer.send(message, len + 1, pdMS_TO_TICKS(100))) {
+ Serial.println("Failed to send message to buffer");
+ }
+ break;
+ }
+ }
+ }
+}
+
+void producerFn(void* params) {
+ const char* message = "1234";
+ const uint8_t len = strlen(message);
+
+ for (;;) {
+ // Delay to simulate work being done
+ delay(1000);
+
+ Serial.printf("Producer: Sending message: %s\n", message);
+
+ // Send the message to the buffer, including the null terminator
+ buffer.send(message, len + 1);
+ }
+}
+
+void consumerFn(void* params) {
+ for (;;) {
+ size_t bytes_recv;
+ char* msg = buffer.receive(bytes_recv);
+
+ if (msg == nullptr) {
+ Serial.println("\tConsumer: Failed to receive!");
+ continue;
+ }
+
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received (%u): %s\n", bytes_recv, msg);
+ blinkLED();
+
+ // Return the item once it's no longer needed
+ buffer.returnItem(msg);
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/RingBufferSplit/RingBufferSplit.ino b/examples/RingBufferSplit/RingBufferSplit.ino
new file mode 100644
index 0000000..2e66b21
--- /dev/null
+++ b/examples/RingBufferSplit/RingBufferSplit.ino
@@ -0,0 +1,180 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "RTOScppRingBuffer.h"
+#include "RTOScppTask.h"
+using namespace RTOS::RingBuffers;
+using namespace RTOS::Tasks;
+
+/** Explanation of the example:
+ * - This example shows how to use a split ring buffer to send data between two tasks.
+ * - Two tasks are created to illustrate the use of the split ring buffer. The producer task sends
+ * data to the consumer task every second.
+ * - The consumer task waits indefinitely for data to be received and blinks the LED when it
+ * receives the data.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, prints
+ * the buffer information when 'i' is received, and sends a message to the buffer when 'a' is
+ * received.
+ * - Try to send a message with serial. Suddenly you will see a split message received. This is
+ * because the message was split into two parts to fit in the buffer. When this happens, the
+ * consumer task will concatenate the two parts and print the complete message.
+ */
+
+// Split Ring Buffer with a size of 32 bytes
+// - The max item size is (bufferSize / 2)
+// - In this case, the max item size is (32 / 2) = 16 bytes
+// - Remember that internally the buffer will be aligned to the next 4 bytes multiple, so if you use
+// a size of 30, it will be aligned to 32 bytes
+RingBufferSplitStatic buffer;
+
+// Tasks to test the buffer
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the buffer was created
+ if (!buffer) {
+ Serial.println("Buffer not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Print buffer information
+ case 'i':
+ {
+ size_t max_item_size = buffer.getMaxItemSize();
+ size_t free_size = buffer.getFreeSize();
+
+ Serial.printf("Serial: Buffer size: %u/%u\n", free_size, max_item_size);
+ break;
+ }
+
+ // Add a message to the buffer
+ case 'a':
+ {
+ const char* message = "Hello!";
+ const uint8_t len = strlen(message);
+
+ Serial.printf("Serial: Sending message: %s\n", message);
+
+ // Send the message to the buffer including the null terminator, with a timeout of 100ms
+ if (!buffer.send(message, len + 1, pdMS_TO_TICKS(100))) {
+ Serial.println("Failed to send message to buffer");
+ }
+ break;
+ }
+ }
+ }
+}
+
+void producerFn(void* params) {
+ const char* message = "12345678";
+ const uint8_t len = strlen(message);
+
+ for (;;) {
+ // Delay to simulate work being done
+ delay(1000);
+
+ Serial.printf("Producer: Sending message: %s\n", message);
+
+ // Send the message to the buffer, including the null terminator
+ buffer.send(message, len + 1);
+ }
+}
+
+void consumerFn(void* params) {
+ char msg[32]; // Buffer to receive the message if it was split
+
+ for (;;) {
+ char* head_msg = nullptr;
+ char* tail_msg = nullptr;
+ size_t head_size = 0;
+ size_t tail_size = 0;
+
+ bool receive = buffer.receive(head_msg, tail_msg, head_size, tail_size);
+
+ if (!receive) {
+ Serial.println("\tConsumer: Failed to receive!");
+ continue;
+ }
+
+ // Message not split
+ if (!tail_msg) {
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received (%u): %s\n", head_size, head_msg);
+ blinkLED();
+
+ // Return the item once it's no longer needed
+ buffer.returnItem(head_msg);
+ continue;
+ }
+
+ // Message split
+ // Ensure that the message fits in the buffer
+ if (head_size + tail_size > sizeof(msg)) {
+ Serial.println("\tConsumer: Message too large to fit in the buffer");
+ continue;
+ }
+
+ // Copy the head and tail messages to the buffer
+ memcpy(msg, head_msg, head_size);
+ memcpy(msg + head_size, tail_msg, tail_size);
+
+ // Print the message received and blink the LED
+ Serial.printf("\tConsumer: Message received [SPLIT - head: %u, tail: %u]: %s\n",
+ head_size,
+ tail_size,
+ msg);
+
+ blinkLED();
+
+ // Return the items once they're no longer needed
+ buffer.returnItem(head_msg);
+ buffer.returnItem(tail_msg);
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/SemaphoreBinary/SemaphoreBinary.ino b/examples/SemaphoreBinary/SemaphoreBinary.ino
new file mode 100644
index 0000000..fe31551
--- /dev/null
+++ b/examples/SemaphoreBinary/SemaphoreBinary.ino
@@ -0,0 +1,110 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a binary semaphore to signal an event between two tasks.
+ * - Two tasks are created to illustrate the use of the semaphore. The producer task sends an event
+ * to the consumer task every second.
+ * - The consumer task waits indefinitely for the semaphore to be signaled and blinks the LED when
+ * it receives the event.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, and
+ * signals the event to the consumer task when 's' is received.
+ */
+
+#include "RTOScppLock.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Locks;
+using namespace RTOS::Tasks;
+
+// Binary Semaphore object
+SemBinaryStatic sem;
+
+// Tasks to test the lock
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer("Producer", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the mutex was created
+ if (!sem) {
+ Serial.println("Semaphore not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ if (!producer.create()) {
+ Serial.println("Producer task not created");
+ while (true)
+ ;
+ }
+
+ if (!consumer.create()) {
+ Serial.println("Consumer task not created");
+ while (true)
+ ;
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Signal a event to the receiver task using the semaphore
+ case 's':
+ Serial.println("Serial: Producing event...");
+ sem.give();
+ break;
+ }
+ }
+}
+
+void producerFn(void* params) {
+ for (;;) {
+ // Delay to simulate some work (e.g. reading a sensor)
+ delay(1000);
+
+ Serial.println("Producer: Producing event...");
+
+ // Signal a event to the consumer task using the semaphore
+ sem.give();
+ }
+}
+
+void consumerFn(void* params) {
+ for (;;) {
+ // Wait indefinitely for the semaphore to be signaled
+ sem.take();
+
+ Serial.println("\tConsumer: Received event!");
+
+ // Blink the LED to indicate the event
+ blinkLED();
+ }
+}
+
+void blinkLED() {
+ Serial.println("\t\tBlinking LED...");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/SemaphoreCounting/SemaphoreCounting.ino b/examples/SemaphoreCounting/SemaphoreCounting.ino
new file mode 100644
index 0000000..ed769c7
--- /dev/null
+++ b/examples/SemaphoreCounting/SemaphoreCounting.ino
@@ -0,0 +1,113 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - This example shows how to use a counting semaphore to signal an event between two tasks.
+ * - Two producer tasks are created to illustrate the use of the semaphore. The producer tasks
+ * send an event to the consumer task every second.
+ * - The consumer task waits indefinitely for the semaphore to be signaled and blinks the LED when
+ * it receives the event.
+ * - If the semaphore is full (5 events), the producer tasks will be blocked until the consumer task
+ * consumes some events. The consumer task will be blocked if there are no events to consume.
+ * - The loop function reads the serial input and restarts the ESP32 when '.' is received, and
+ * signals the event to the consumer task when 's' is received.
+ */
+
+#include "RTOScppLock.h"
+#include "RTOScppTask.h"
+using namespace RTOS::Locks;
+using namespace RTOS::Tasks;
+
+// Counting Semaphore object
+SemCountingStatic<5> sem;
+
+// Tasks to test the lock
+void producerFn(void* params);
+void consumerFn(void* params);
+TaskStatic<4 * 1024> producer1("Producer1", producerFn, 1);
+TaskStatic<4 * 1024> producer2("Producer2", producerFn, 1);
+TaskStatic<4 * 1024> consumer("Consumer", consumerFn, 1);
+
+// Task pointers
+ITask* tasks[] = {&producer1, &producer2, &consumer};
+const size_t tasks_num = sizeof(tasks) / sizeof(tasks[0]);
+
+// Blink the internal LED one time
+void blinkLED();
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Check if the mutex was created
+ if (!sem) {
+ Serial.println("Semaphore not created");
+ while (true)
+ ;
+ }
+
+ // Create the tasks
+ for (size_t i = 0; i < tasks_num; i++) {
+ if (!tasks[i]->create()) {
+ Serial.printf("%s task not created\n", tasks[i]->getName());
+ while (true)
+ ;
+ }
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Signal a event to the consumer task using the semaphore
+ case 's':
+ Serial.println("Serial: Producing event...");
+ sem.give();
+ break;
+ }
+ }
+}
+
+void producerFn(void* params) {
+ for (;;) {
+ // Delay to simulate some work (e.g. reading a sensor)
+ delay(1000);
+
+ Serial.println("Producer: Producing event...");
+
+ // Signal a event to the consumer task using the semaphore
+ sem.give();
+ }
+}
+
+void consumerFn(void* params) {
+ for (;;) {
+ // Wait indefinitely for the semaphore to be signaled
+ sem.take();
+
+ Serial.printf("\tConsumer: Received event! Events left: %u\n", sem.getCount());
+
+ // Blink the LED to indicate the event
+ blinkLED();
+ }
+}
+
+void blinkLED() {
+ Serial.printf("\t\tBlinking LED...\n");
+
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(100);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(100);
+}
\ No newline at end of file
diff --git a/examples/Task/Task.ino b/examples/Task/Task.ino
new file mode 100644
index 0000000..9b3f5cc
--- /dev/null
+++ b/examples/Task/Task.ino
@@ -0,0 +1,136 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - Two tasks are created with different stack sizes, one with dynamic memory allocation and the
+ * other with static memory allocation, having a total of 3 tasks (including the loop task).
+ * - The tasks are created and started in the setup function using pointers. They have the same
+ * priority and a custom struct as a parameter.
+ * - The loop function reads the serial input and performs the following actions:
+ * - Restart the ESP32 when '.' is received.
+ * - Notify task 1 when '1' is received.
+ * - Notify task 2 when '2' is received.
+ * - Notify both tasks when '3' is received.
+ * - Suspend all tasks when 's' is received.
+ * - Resume all tasks when 'r' is received.
+ * - The LED blinks every 100 ms.
+ * - Task 1 waits for a notification for 1 second and increments the 'a' parameter.
+ * - Task 2 waits for a notification for 5 seconds and increments the 'b' parameter.
+ */
+
+#include "RTOScppTask.h"
+using namespace RTOS::Tasks;
+
+// Task functions
+void task1Fn(void* params);
+void task2Fn(void* params);
+
+// Custom task parameters
+struct TaskParams {
+ uint8_t a;
+ uint8_t b;
+} task_params{};
+
+// Task objects
+TaskDynamic<4 * 1024> task1("Task 1", task1Fn, 1, &task_params);
+TaskStatic<8 * 1024> task2("Task 2", task2Fn, 1, &task_params);
+
+// Task pointers
+ITask* tasks[] = {&task1, &task2};
+constexpr uint8_t tasks_num = sizeof(tasks) / sizeof(tasks[0]);
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting tasks...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Create tasks
+ for (uint8_t i = 0; i < tasks_num; i++) {
+ if (!tasks[i]->create()) {
+ Serial.printf("Task %u not created!\n", i + 1);
+ }
+ }
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Notify tasks
+ case '1': task1.notify(0, eNotifyAction::eNoAction); break;
+ case '2': task2.notify(0, eNotifyAction::eNoAction); break;
+
+ // Notify both tasks
+ case '3':
+ task1.notify(0, eNotifyAction::eNoAction);
+ task2.notify(0, eNotifyAction::eNoAction);
+ break;
+
+ // Suspend and resume tasks
+ case 's':
+ {
+ for (uint8_t i = 0; i < tasks_num; i++) {
+ tasks[i]->suspend();
+ }
+ Serial.println("-> Tasks suspended!");
+ } break;
+
+ case 'r':
+ {
+ for (uint8_t i = 0; i < tasks_num; i++) {
+ tasks[i]->resume();
+ }
+ Serial.println("-> Tasks resumed!");
+ } break;
+ }
+ }
+
+ // Blink LED
+ static uint32_t last_blink = 0;
+
+ if (millis() - last_blink > 100) {
+ last_blink = millis();
+ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
+ }
+}
+
+void task1Fn(void* params) {
+ TaskParams* task_params = static_cast(params);
+ uint32_t notif;
+
+ while (true) {
+ // Wait a notification for 1 second
+ if (task1.notifyWait(0, 0, notif, pdMS_TO_TICKS(1000))) {
+ Serial.println("Task 1 notified!");
+ }
+
+ // Print task parameters and increment a
+ Serial.printf("Task 1 (a: %u, b: %u)\n", task_params->a, task_params->b);
+ task_params->a++;
+ }
+}
+
+void task2Fn(void* params) {
+ TaskParams* task_params = static_cast(params);
+ uint32_t notif;
+
+ while (true) {
+ // Wait a notification for 5 seconds
+ if (task2.notifyWait(0, 0, notif, pdMS_TO_TICKS(5000))) {
+ Serial.println("\tTask 2 notified!");
+ }
+
+ // Print task parameters and increment b
+ Serial.printf("\tTask 2 (a: %u, b: %u)\n", task_params->a, task_params->b);
+ task_params->b++;
+ }
+}
\ No newline at end of file
diff --git a/examples/Timer/Timer.ino b/examples/Timer/Timer.ino
new file mode 100644
index 0000000..48b0a6e
--- /dev/null
+++ b/examples/Timer/Timer.ino
@@ -0,0 +1,92 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/** Explanation of the example:
+ * - Two timers are created with different periods, one to blink the LED every 100 ms and the other
+ * to print a message every 1000 ms.
+ * - The timers are created and started in the setup function using static memory allocation.
+ * - The loop function reads the serial input and performs the following actions:
+ * - Restart the ESP32 when '.' is received.
+ * - Reset the blink timer when '1' is received.
+ * - Reset the print timer when '2' is received.
+ * - Change the blink timer period to 100 ms when '3' is received.
+ * - Change the blink timer period to 1000 ms when '4' is received.
+ * - The LED blinks if the blink timer expired and restarts it.
+ * - A message is printed if the print timer expired and restarts it.
+ */
+
+#include "RTOScppTimer.h"
+using namespace RTOS::Timers;
+
+// Flags to indicate if the timer expired
+static bool timer_blink_expired = false;
+static bool timer_print_expired = false;
+
+// Timer callbacks
+void timerBlinkFn(TimerHandle_t timer) { timer_blink_expired = true; }
+void timerPrintFn(TimerHandle_t timer) { timer_print_expired = true; }
+
+TimerStatic timer_blink(
+ /*name*/ "TimerBlink",
+ /*callback*/ timerBlinkFn,
+ /*period*/ pdMS_TO_TICKS(100),
+ /*id*/ nullptr,
+ /*auto-reload*/ false,
+ /*start*/ false);
+
+TimerStatic timer_print(
+ /*name*/ "TimerPrint",
+ /*callback*/ timerPrintFn,
+ /*period*/ pdMS_TO_TICKS(1000),
+ /*id*/ nullptr,
+ /*auto-reload*/ false,
+ /*start*/ false);
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting...");
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // Start timers
+ timer_blink.start();
+ timer_print.start();
+}
+
+void loop() {
+ // Read serial input
+ if (Serial.available()) {
+ char c = Serial.read();
+
+ switch (c) {
+ // Restart ESP32
+ case '.': ESP.restart(); break;
+
+ // Reset timers
+ case '1': timer_blink.reset(); break;
+ case '2': timer_print.reset(); break;
+
+ // Change blink timer period
+ case '3': timer_blink.setPeriod(pdMS_TO_TICKS(100)); break;
+ case '4': timer_blink.setPeriod(pdMS_TO_TICKS(1000)); break;
+ }
+ }
+
+ // Blink LED if the timer expired and restart it
+ if (timer_blink_expired) {
+ timer_blink_expired = false;
+ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
+ timer_blink.start();
+ }
+
+ // Print message if the timer expired and restart it
+ if (timer_print_expired) {
+ static uint32_t counter = 0;
+ timer_print_expired = false;
+ Serial.printf("Timer print expired! Counter: %u\n", counter++);
+ timer_print.start();
+ }
+}
\ No newline at end of file
diff --git a/keywords.txt b/keywords.txt
new file mode 100644
index 0000000..299021a
--- /dev/null
+++ b/keywords.txt
@@ -0,0 +1,134 @@
+######################
+# Syntax Colouring Map
+######################
+
+#######################
+# Data types (KEYWORD1)
+#######################
+# Buffers
+StreamBufferDynamic KEYWORD1
+StreamBufferStatic KEYWORD1
+StreamBufferExternalStorage KEYWORD1
+MessageBufferDynamic KEYWORD1
+MessageBufferStatic KEYWORD1
+MessageBufferExternalStorage KEYWORD1
+# Locks
+MutexDynamic KEYWORD1
+MutexStatic KEYWORD1
+MutexRecursiveDynamic KEYWORD1
+MutexRecursiveStatic KEYWORD1
+SemBinaryDynamic KEYWORD1
+SemBinaryStatic KEYWORD1
+SemCountingDynamic KEYWORD1
+SemCountingStatic KEYWORD1
+# Queues
+QueueDynamic KEYWORD1
+QueueStatic KEYWORD1
+QueueExternalStorage KEYWORD1
+# Queue set
+QueueSet KEYWORD1
+# Ring buffers
+RingBufferNoSplitDynamic KEYWORD1
+RingBufferNoSplitStatic KEYWORD1
+RingBufferNoSplitExternalStorage KEYWORD1
+RingBufferSplitDynamic KEYWORD1
+RingBufferSplitStatic KEYWORD1
+RingBufferSplitExternalStorage KEYWORD1
+RingBufferByteDynamic KEYWORD1
+RingBufferByteStatic KEYWORD1
+RingBufferByteExternalStorage KEYWORD1
+# Tasks
+TaskDynamic KEYWORD1
+TaskStatic KEYWORD1
+# Timers
+TimerDynamic KEYWORD1
+TimerStatic KEYWORD1
+
+##################################
+# Methods and Functions (KEYWORD2)
+##################################
+# Common
+getHandle KEYWORD2
+isCreated KEYWORD2
+send KEYWORD2
+sendFromISR KEYWORD2
+receive KEYWORD2
+receiveFromISR KEYWORD2
+reset KEYWORD2
+isEmpty KEYWORD2
+isFull KEYWORD2
+getAvailableSpaces KEYWORD2
+create KEYWORD2
+add
+addFromISR
+getName
+
+# Buffers
+getAvailableBytes KEYWORD2
+setTriggerLevel KEYWORD2
+# Locks
+take KEYWORD2
+give KEYWORD2
+getCount KEYWORD2
+# Queues
+getAvailableMessages KEYWORD2
+getAvailableMessagesFromISR KEYWORD2
+isFullFromISR KEYWORD2
+isEmptyFromISR KEYWORD2
+push KEYWORD2
+pushFromISR KEYWORD2
+pop KEYWORD2
+popFromISR KEYWORD2
+peek KEYWORD2
+peekFromISR KEYWORD2
+overwrite KEYWORD2
+overwriteFromISR KEYWORD2
+# Queue set
+remove KEYWORD2
+select KEYWORD2
+selectFromISR KEYWORD2
+# Ring buffers
+getMaxItemSize KEYWORD2
+getFreeSize KEYWORD2
+returnItem KEYWORD2
+returnItemFromISR KEYWORD2
+# Tasks
+suspend KEYWORD2
+resume KEYWORD2
+getState KEYWORD2
+abortDelay KEYWORD2
+getParameters KEYWORD2
+getCore KEYWORD2
+setPriority KEYWORD2
+getPriority KEYWORD2
+getPriorityFromISR KEYWORD2
+getStackSize KEYWORD2
+notify KEYWORD2
+notifyFromISR KEYWORD2
+notifyAndQuery KEYWORD2
+notifyAndQueryFromISR KEYWORD2
+notifyGive KEYWORD2
+notifyGiveFromISR KEYWORD2
+notifyTake KEYWORD2
+notifyWait KEYWORD2
+updateStackStats KEYWORD2
+getStackUsed KEYWORD2
+getStackMinUsed KEYWORD2
+getStackMaxUsed KEYWORD2
+setup KEYWORD2
+isValid KEYWORD2
+# Timers
+start KEYWORD2
+startFromISR KEYWORD2
+stop KEYWORD2
+stopFromISR KEYWORD2
+isActive KEYWORD2
+resetFromISR KEYWORD2
+getExpiryTime KEYWORD2
+setPeriod KEYWORD2
+setPeriodFromISR KEYWORD2
+getPeriod KEYWORD2
+setTimerID KEYWORD2
+getTimerID KEYWORD2
+setReloadMode KEYWORD2
+getReloadMode KEYWORD2
diff --git a/library.json b/library.json
index ad48fe5..286e0c0 100644
--- a/library.json
+++ b/library.json
@@ -1,10 +1,16 @@
{
"name": "RTOScppESP32",
- "version": "1.0.0-rc.3",
+ "version": "1.0.0",
+ "authors": {
+ "name": "Maximiliano Ramirez",
+ "email": "maximiliano.ramirezbravo@gmail.com"
+ },
"repository": {
"type": "git",
"url": "https://github.com/alkonosst/RTOScppESP32.git"
},
+ "description": "RTOScppESP32 provides a comprehensive and intuitive interface for FreeRTOS functionalities. It simplifies the creation and management of tasks, timers, queues, buffers and locks, enabling efficient real-time application with clean and maintainable code.",
+ "keywords": "arduino, espressif, esp32, rtos, freertos, cpp, abstraction, tool, framework",
"license": "GPL-3.0-or-later",
"frameworks": "arduino",
"platforms": "espressif32"
diff --git a/library.properties b/library.properties
new file mode 100644
index 0000000..b8cd17f
--- /dev/null
+++ b/library.properties
@@ -0,0 +1,9 @@
+name=RTOScppESP32
+version=1.0.0
+author=Maximiliano Ramirez
+maintainer=Maximiliano Ramirez
+sentence=FreeRTOS abstraction layer for ESP32 with C++ interface.
+paragraph=RTOScppESP32 provides a comprehensive and intuitive interface for FreeRTOS functionalities. It simplifies the creation and management of tasks, timers, queues, buffers and locks, enabling efficient real-time application with clean and maintainable code.
+category=Device Control
+url=https://github.com/alkonosst/RTOScppESP32
+architectures=esp32
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 0000000..bf9862b
--- /dev/null
+++ b/platformio.ini
@@ -0,0 +1,71 @@
+; PlatformIO Project Configuration File
+;
+; Build options: build flags, source filter
+; Upload options: custom upload port, speed and extra flags
+; Library options: dependencies, extra library storages
+; Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[platformio]
+lib_dir = .
+; src_dir = examples/Task
+; src_dir = examples/Timer
+; src_dir = examples/Mutex
+; src_dir = examples/MutexRecursive
+; src_dir = examples/SemaphoreBinary
+; src_dir = examples/SemaphoreCounting
+; src_dir = examples/Queue
+; src_dir = examples/BufferStream
+; src_dir = examples/BufferMessage
+; src_dir = examples/RingBufferNoSplit
+; src_dir = examples/RingBufferSplit
+; src_dir = examples/RingBufferByte
+; src_dir = examples/QueueSet
+
+[env:esp32-s3]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13+github/platform-espressif32.zip
+board = esp32-s3-devkitc-1
+framework = arduino
+
+; Tests
+; Ignore Unity library to avoid conflicts with source code due to lib_dir = .
+lib_ignore = Unity
+
+; test_ignore =
+ ; test_tasks
+ ; test_timers
+ ; test_locks
+ ; test_queues
+ ; test_buffers
+ ; test_ringbuffers
+ ; test_queuesets
+
+; Config ESP32
+board_build.f_flash = 80000000L
+board_build.f_cpu = 240000000L
+board_build.partitions = default_16MB.csv
+board_build.arduino.memory_type = qio_opi
+board_upload.flash_size = 16MB
+board_upload.maximum_size = 16777216
+
+; Serial monitor
+monitor_speed = 115200
+upload_speed = 921600
+monitor_filters =
+ esp32_exception_decoder
+ log2file
+
+; Flags
+build_flags =
+ ; Enable debug (ESP-IDF logs)
+ ; -DUSE_ESP_IDF_LOG
+ ; -DCORE_DEBUG_LEVEL=5
+ ; -DCONFIG_LOG_COLORS
+
+ ; Enable PSRAM
+ -DBOARD_HAS_PSRAM
+
+ ; Enable USB CDC on boot
+ -DARDUINO_USB_CDC_ON_BOOT=1
diff --git a/src/RTOScppBuffer.h b/src/RTOScppBuffer.h
index df90cef..aa9bcd9 100644
--- a/src/RTOScppBuffer.h
+++ b/src/RTOScppBuffer.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,118 +7,422 @@
#pragma once
#include
+
#include
#include
-class DataBufferInterface {
- protected:
- DataBufferInterface(const StreamBufferHandle_t handle)
- : _handle(handle) {}
+namespace RTOS::Buffers {
- StreamBufferHandle_t _handle;
+// Interface for Buffer objects, useful when using pointers
+class IBuffer {
+ protected:
+ IBuffer() = default;
public:
- virtual ~DataBufferInterface() {
- if (_handle) vStreamBufferDelete(_handle);
- }
+ IBuffer(const IBuffer&) = delete;
+ IBuffer& operator=(const IBuffer&) = delete;
+ IBuffer(IBuffer&&) = delete;
+ IBuffer& operator=(IBuffer&&) = delete;
+ virtual ~IBuffer() = default;
- DataBufferInterface(const DataBufferInterface&) = delete;
- DataBufferInterface& operator=(const DataBufferInterface&) = delete;
- DataBufferInterface(DataBufferInterface&&) noexcept = delete;
- DataBufferInterface& operator=(DataBufferInterface&&) noexcept = delete;
+ /**
+ * @brief Get the low-level handle of the buffer. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return StreamBufferHandle_t Buffer handle, nullptr if the buffer is not created.
+ */
+ virtual StreamBufferHandle_t getHandle() const = 0;
- uint32_t send(const void* tx_buffer, const uint32_t bytes,
- const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xStreamBufferSend(_handle, tx_buffer, bytes, ticks_to_wait);
- }
+ /**
+ * @brief Check if the buffer is created.
+ * @return true Buffer is created.
+ */
+ virtual bool isCreated() const = 0;
- uint32_t sendFromISR(const void* tx_buffer, const uint32_t bytes, BaseType_t& task_woken) const {
- return xStreamBufferSendFromISR(_handle, tx_buffer, bytes, &task_woken);
- }
+ /**
+ * @brief Send data to the buffer.
+ * @param tx_buffer Data to send.
+ * @param bytes Number of bytes to send.
+ * @param ticks_to_wait Maximum time to wait for the buffer to be available.
+ * @return uint32_t Number of bytes sent, 0 if the buffer is not created.
+ */
+ virtual uint32_t send(const void* tx_buffer, const uint32_t bytes,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
- uint32_t receive(void* rx_buffer, const uint32_t bytes,
- const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xStreamBufferReceive(_handle, rx_buffer, bytes, ticks_to_wait);
- }
+ /**
+ * @brief Send data to the buffer from an ISR.
+ * @param tx_buffer Data to send.
+ * @param bytes Number of bytes to send.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return uint32_t Number of bytes sent, 0 if the buffer is not created.
+ */
+ virtual uint32_t sendFromISR(const void* tx_buffer, const uint32_t bytes,
+ BaseType_t& task_woken) const = 0;
- uint32_t receiveFromISR(void* rx_buffer, const uint32_t bytes, BaseType_t& task_woken) const {
- return xStreamBufferReceiveFromISR(_handle, rx_buffer, bytes, &task_woken);
- }
+ /**
+ * @brief Receive data from the buffer.
+ * @param rx_buffer Buffer to store the received data.
+ * @param bytes Number of bytes to receive.
+ * @param ticks_to_wait Maximum time to wait for the buffer to have enough data.
+ * @return uint32_t Number of bytes received, 0 if the buffer is not created or failed to receive
+ * the data.
+ */
+ virtual uint32_t receive(void* rx_buffer, const uint32_t bytes,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Receive data from the buffer from an ISR.
+ * @param rx_buffer Buffer to store the received data.
+ * @param bytes Number of bytes to receive.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return uint32_t Number of bytes received, 0 if the buffer is not created or failed to receive
+ * the data.
+ */
+ virtual uint32_t receiveFromISR(void* rx_buffer, const uint32_t bytes,
+ BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Reset the buffer.
+ * @return true Buffer reset successfully, false if the buffer is not created or failed to reset.
+ */
+ virtual bool reset() const = 0;
+
+ /**
+ * @brief Check if the buffer is empty.
+ * @return true Buffer is empty, false if the buffer is not created or has data.
+ */
+ virtual bool isEmpty() const = 0;
+
+ /**
+ * @brief Check if the buffer is full.
+ * @return true Buffer is full, false if the buffer is not created or has available space.
+ */
+ virtual bool isFull() const = 0;
+
+ /**
+ * @brief Get the available spaces in the buffer.
+ * @return uint32_t Available spaces, 0 if the buffer is not created.
+ */
+ virtual uint32_t getAvailableSpaces() const = 0;
+
+ /**
+ * @brief Get the available bytes in the buffer.
+ * @return uint32_t Available bytes, 0 if the buffer is not created.
+ */
+ virtual uint32_t getAvailableBytes() const = 0;
+
+ /**
+ * @brief Check if the buffer is created.
+ * @return true Buffer is created.
+ */
+ virtual explicit operator bool() const = 0;
+};
- bool reset() const { return xStreamBufferReset(_handle); }
- bool isEmpty() const { return xStreamBufferIsEmpty(_handle); }
- bool isFull() const { return xStreamBufferIsFull(_handle); }
- uint32_t availableSpaces() const { return xStreamBufferSpacesAvailable(_handle); }
- uint32_t availableBytes() const { return xStreamBufferBytesAvailable(_handle); }
+namespace Internal {
+// CRTP base policy class
+template
+class Policy {
+ public:
+ StreamBufferHandle_t getHandle() const { return _handle; }
+
+ bool isCreated() const { return _handle != nullptr; }
+
+ protected:
+ Policy()
+ : _handle(nullptr) {}
+
+ StreamBufferHandle_t _handle;
+};
+
+// CRTP stream buffer base policy class
+template
+class StreamBufferPolicy : public Policy> {
+ public:
+ /**
+ * @brief Set the trigger level of the stream buffer. The level must be less than or equal to the
+ * buffer size.
+ * @param trigger_bytes Number of bytes to trigger the buffer.
+ * @return true Trigger level set successfully, false if the buffer is not created.
+ */
bool setTriggerLevel(const uint32_t trigger_bytes) {
- return xStreamBufferSetTriggerLevel(_handle, trigger_bytes);
+ if (!this->isCreated()) return false;
+ return xStreamBufferSetTriggerLevel(this->_handle, trigger_bytes);
}
-
- explicit operator bool() const { return _handle != nullptr; }
};
-class StreamBufferDynamic : public DataBufferInterface {
+// Policy for stream buffer with dynamic memory allocation
+template
+class StreamBufferDynamicPolicy
+ : public StreamBufferPolicy> {
public:
- StreamBufferDynamic(const uint32_t buffer_size, const uint32_t trigger_bytes)
- : DataBufferInterface(xStreamBufferGenericCreate(buffer_size + 1, trigger_bytes, false)) {}
+ StreamBufferDynamicPolicy() {
+ this->_handle = xStreamBufferGenericCreate(BufferSize, TriggerBytes, false, nullptr, nullptr);
+ }
};
-template
-class StreamBufferStatic : public DataBufferInterface {
+// Policy for data buffer with static memory allocation
+template
+class StreamBufferStaticPolicy
+ : public StreamBufferPolicy> {
public:
- StreamBufferStatic(const uint32_t trigger_bytes)
- : DataBufferInterface(xStreamBufferGenericCreateStatic(BUFFER_SIZE + 1, trigger_bytes, false,
- _storage, &_tcb)) {}
+ StreamBufferStaticPolicy() {
+ this->_handle = xStreamBufferGenericCreateStatic(BufferSize + 1,
+ TriggerBytes,
+ false,
+ _storage,
+ &_buf_buffer,
+ nullptr,
+ nullptr);
+ }
private:
- StaticStreamBuffer_t _tcb;
- uint8_t _storage[BUFFER_SIZE + 1];
+ StaticStreamBuffer_t _buf_buffer;
+ uint8_t _storage[BufferSize + 2];
};
-class StreamBufferExternalStorage : public DataBufferInterface {
+// Policy for data buffer with external memory allocation
+template
+class StreamBufferExternalStoragePolicy
+ : public StreamBufferPolicy> {
public:
- StreamBufferExternalStorage(const uint32_t trigger_bytes)
- : DataBufferInterface(nullptr) {}
+ static constexpr uint32_t REQUIRED_SIZE = BufferSize + 2;
+
+ /**
+ * @brief Create the stream buffer with an external memory allocation.
+ * @param buffer External memory buffer.
+ * @return true Buffer created successfully, false if the buffer is nullptr or failed to create.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
+
+ this->_handle = xStreamBufferGenericCreateStatic(BufferSize + 1,
+ TriggerBytes,
+ false,
+ buffer,
+ &_buf_buffer,
+ nullptr,
+ nullptr);
- bool init(const uint32_t trigger_bytes, uint8_t* const buffer, const uint32_t buffer_size) {
- _handle =
- xStreamBufferGenericCreateStatic(buffer_size + 1, trigger_bytes, pdFALSE, buffer, &_tcb);
- return _handle != nullptr ? true : false;
+ return this->_handle != nullptr;
}
private:
- StaticStreamBuffer_t _tcb;
+ StaticStreamBuffer_t _buf_buffer;
};
-class MessageBufferDynamic : public DataBufferInterface {
+// Policy for message buffer with dynamic memory allocation
+template
+class MessageBufferDynamicPolicy : public Policy> {
public:
- MessageBufferDynamic(const uint32_t buffer_size)
- : DataBufferInterface(xStreamBufferGenericCreate(buffer_size + 1, 0, false)) {}
+ MessageBufferDynamicPolicy() {
+ this->_handle = xStreamBufferGenericCreate(BufferSize, 0, true, nullptr, nullptr);
+ }
};
-template
-class MessageBufferStatic : public DataBufferInterface {
+// Policy for message buffer with static memory allocation
+template
+class MessageBufferStaticPolicy : public Policy> {
public:
- MessageBufferStatic()
- : DataBufferInterface(
- xStreamBufferGenericCreateStatic(BUFFER_SIZE + 1, 0, false, _storage, &_tcb)) {}
+ MessageBufferStaticPolicy() {
+ this->_handle = xStreamBufferGenericCreateStatic(BufferSize + 1,
+ 0,
+ true,
+ _storage,
+ &_buf_buffer,
+ nullptr,
+ nullptr);
+ }
private:
- StaticStreamBuffer_t _tcb;
- uint8_t _storage[BUFFER_SIZE + 1];
+ StaticStreamBuffer_t _buf_buffer;
+ uint8_t _storage[BufferSize + 2];
};
-class MessageBufferExternalStorage : public DataBufferInterface {
+// Policy for message buffer with external memory allocation
+template
+class MessageBufferExternalStoragePolicy
+ : public Policy> {
public:
- MessageBufferExternalStorage()
- : DataBufferInterface(nullptr) {}
+ static constexpr uint32_t REQUIRED_SIZE = BufferSize + 2;
+
+ /**
+ * @brief Create the message buffer with an external memory allocation.
+ * @param buffer External memory buffer.
+ * @return true Buffer created successfully, false if the buffer is nullptr or failed to create.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
+
+ this->_handle = xStreamBufferGenericCreateStatic(BufferSize + 1,
+ 0,
+ true,
+ buffer,
+ &_buf_buffer,
+ nullptr,
+ nullptr);
- bool init(const uint32_t trigger_bytes, uint8_t* const buffer, const uint32_t buffer_size) {
- _handle = xStreamBufferGenericCreateStatic(buffer_size + 1, 0, true, buffer, &_tcb);
- return _handle != nullptr ? true : false;
+ return this->_handle != nullptr;
}
private:
- StaticStreamBuffer_t _tcb;
+ StaticStreamBuffer_t _buf_buffer;
};
+
+// Main DataBuffer class. You need to specify the policy used
+template
+class DataBuffer : public IBuffer, public Policy {
+ public:
+ using Policy::Policy; // Inherit constructor
+
+ ~DataBuffer() {
+ if (Policy::isCreated()) vStreamBufferDelete(Policy::getHandle());
+ }
+
+ /**
+ * @brief Get the low-level handle of the buffer. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return StreamBufferHandle_t Buffer handle, nullptr if the buffer is not created.
+ */
+ StreamBufferHandle_t getHandle() const override { return Policy::getHandle(); }
+
+ /**
+ * @brief Check if the buffer is created.
+ * @return true Buffer is created.
+ */
+ bool isCreated() const override { return Policy::isCreated(); }
+
+ /**
+ * @brief Send data to the buffer.
+ * @param tx_buffer Data to send.
+ * @param bytes Number of bytes to send.
+ * @param ticks_to_wait Maximum time to wait for the buffer to be available.
+ * @return uint32_t Number of bytes sent, 0 if the buffer is not created.
+ */
+ uint32_t send(const void* tx_buffer, const uint32_t bytes,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferSend(getHandle(), tx_buffer, bytes, ticks_to_wait);
+ }
+
+ /**
+ * @brief Send data to the buffer from an ISR.
+ * @param tx_buffer Data to send.
+ * @param bytes Number of bytes to send.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return uint32_t Number of bytes sent, 0 if the buffer is not created.
+ */
+ uint32_t sendFromISR(const void* tx_buffer, const uint32_t bytes,
+ BaseType_t& task_woken) const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferSendFromISR(getHandle(), tx_buffer, bytes, &task_woken);
+ }
+
+ /**
+ * @brief Receive data from the buffer.
+ * @param rx_buffer Buffer to store the received data.
+ * @param bytes Number of bytes to receive.
+ * @param ticks_to_wait Maximum time to wait for the buffer to have enough data.
+ * @return uint32_t Number of bytes received, 0 if the buffer is not created or failed to receive
+ * the data.
+ */
+ uint32_t receive(void* rx_buffer, const uint32_t bytes,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferReceive(getHandle(), rx_buffer, bytes, ticks_to_wait);
+ }
+
+ /**
+ * @brief Receive data from the buffer from an ISR.
+ * @param rx_buffer Buffer to store the received data.
+ * @param bytes Number of bytes to receive.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return uint32_t Number of bytes received, 0 if the buffer is not created or failed to receive
+ * the data.
+ */
+ uint32_t receiveFromISR(void* rx_buffer, const uint32_t bytes,
+ BaseType_t& task_woken) const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferReceiveFromISR(getHandle(), rx_buffer, bytes, &task_woken);
+ }
+
+ /**
+ * @brief Reset the buffer.
+ * @return true Buffer reset successfully, false if the buffer is not created or failed to reset.
+ */
+ bool reset() const override {
+ if (!isCreated()) return false;
+ return xStreamBufferReset(getHandle());
+ }
+
+ /**
+ * @brief Check if the buffer is empty.
+ * @return true Buffer is empty, false if the buffer is not created or has data.
+ */
+ bool isEmpty() const override {
+ if (!isCreated()) return false;
+ return xStreamBufferIsEmpty(getHandle());
+ }
+
+ /**
+ * @brief Check if the buffer is full.
+ * @return true Buffer is full, false if the buffer is not created or has available space.
+ */
+ bool isFull() const override {
+ if (!isCreated()) return false;
+ return xStreamBufferIsFull(getHandle());
+ }
+
+ /**
+ * @brief Get the available spaces in the buffer.
+ * @return uint32_t Available spaces, 0 if the buffer is not created.
+ */
+ uint32_t getAvailableSpaces() const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferSpacesAvailable(getHandle());
+ }
+
+ /**
+ * @brief Get the available bytes in the buffer.
+ * @return uint32_t Available bytes, 0 if the buffer is not created.
+ */
+ uint32_t getAvailableBytes() const override {
+ if (!isCreated()) return 0;
+ return xStreamBufferBytesAvailable(getHandle());
+ }
+
+ /**
+ * @brief Check if the buffer is created.
+ * @return true Buffer is created.
+ */
+ explicit operator bool() const override { return isCreated(); }
+};
+
+} // namespace Internal
+
+template
+using StreamBufferDynamic =
+ Internal::DataBuffer>;
+
+template
+using StreamBufferStatic =
+ Internal::DataBuffer>;
+
+template
+using StreamBufferExternalStorage =
+ Internal::DataBuffer>;
+
+template
+using MessageBufferDynamic = Internal::DataBuffer>;
+
+template
+using MessageBufferStatic = Internal::DataBuffer>;
+
+template
+using MessageBufferExternalStorage =
+ Internal::DataBuffer>;
+
+} // namespace RTOS::Buffers
\ No newline at end of file
diff --git a/src/RTOScppLock.h b/src/RTOScppLock.h
index 765b040..164640e 100644
--- a/src/RTOScppLock.h
+++ b/src/RTOScppLock.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,129 +7,292 @@
#pragma once
#include
+
#include
#include
-// Forward declaration of QueueSet
-class QueueSet;
-
-class LockInterface {
- private:
- friend class QueueSet;
- friend bool operator==(const QueueSetMemberHandle_t& queue_set_member, const LockInterface& lock);
+namespace RTOS::Locks {
+// Interface for Lock objects, useful when using pointers
+class ILock {
protected:
- LockInterface(SemaphoreHandle_t handle)
- : _handle(handle) {}
+ ILock() = default;
- SemaphoreHandle_t _handle;
+ public:
+ ILock(const ILock&) = delete;
+ ILock& operator=(const ILock&) = delete;
+ ILock(ILock&&) = delete;
+ ILock& operator=(ILock&&) = delete;
+ virtual ~ILock() = default;
+
+ /**
+ * @brief Get the low-level handle of the lock. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return SemaphoreHandle_t Lock handle, nullptr if the lock is not created.
+ */
+ virtual SemaphoreHandle_t getHandle() const = 0;
+
+ /**
+ * @brief Check if the lock is created.
+ * @return true Lock is created.
+ */
+ virtual bool isCreated() const = 0;
+
+ /**
+ * @brief Take the lock.
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Lock taken successfully, false if the lock is not created or failed to take.
+ */
+ virtual bool take(const TickType_t ticks_to_wait = portMAX_DELAY) = 0;
+
+ /**
+ * @brief Give the lock.
+ * @return true Lock given successfully, false if the lock is not created or failed to give.
+ */
+ virtual bool give() = 0;
+
+ /**
+ * @brief Check if the lock is taken.
+ * @return true Lock is taken.
+ */
+ virtual explicit operator bool() const = 0;
+
+ friend bool operator==(const QueueSetMemberHandle_t& queue_set_member, const ILock& lock);
+};
+
+// Comparison operator for QueueSet
+inline bool operator==(const QueueSetMemberHandle_t& queue_set_member, const ILock& lock) {
+ return queue_set_member == lock.getHandle();
+}
+
+namespace Internal {
+
+// CRTP base policy class
+template
+class Policy {
+ protected:
+ Policy()
+ : _handle(nullptr) {}
public:
- virtual ~LockInterface() {
- if (_handle) vSemaphoreDelete(_handle);
- }
+ SemaphoreHandle_t getHandle() const { return _handle; }
- LockInterface(const LockInterface&) = delete;
- LockInterface& operator=(const LockInterface&) = delete;
- LockInterface(LockInterface&&) noexcept = delete;
- LockInterface& operator=(LockInterface&&) noexcept = delete;
+ bool isCreated() const { return _handle != nullptr; }
- virtual bool take(const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xSemaphoreTake(_handle, ticks_to_wait);
+ bool take(const TickType_t ticks_to_wait = portMAX_DELAY) {
+ if (!isCreated()) return false;
+ return static_cast(this)->takeImpl(ticks_to_wait);
}
- virtual bool give() const { return xSemaphoreGive(_handle); }
+ bool give() {
+ if (!isCreated()) return false;
+ return static_cast(this)->giveImpl();
+ }
- explicit operator bool() const { return _handle != nullptr; }
+ protected:
+ SemaphoreHandle_t _handle;
};
-inline bool operator==(const QueueSetMemberHandle_t& queue_set_member, const LockInterface& lock) {
- return queue_set_member == lock._handle;
-}
+// CRTP mutex base policy class
+template
+class MutexPolicy : public Policy> {
+ protected:
+ friend class Policy>;
+
+ bool takeImpl(const TickType_t ticks_to_wait = portMAX_DELAY) {
+ return xSemaphoreTake(this->_handle, ticks_to_wait);
+ }
-class MutexDynamic : public LockInterface {
+ bool giveImpl() { return xSemaphoreGive(this->_handle); }
+};
+
+// Policy for mutex with dynamic memory allocation
+template
+class MutexDynamicPolicy : public MutexPolicy> {
public:
- MutexDynamic()
- : LockInterface(xSemaphoreCreateMutex()) {}
+ MutexDynamicPolicy() { this->_handle = xSemaphoreCreateMutex(); }
};
-class MutexStatic : public LockInterface {
+// Policy for mutex with static memory allocation
+template
+class MutexStaticPolicy : public MutexPolicy> {
public:
- MutexStatic()
- : LockInterface(xSemaphoreCreateMutexStatic(&_tcb)) {}
+ MutexStaticPolicy() { this->_handle = xSemaphoreCreateMutexStatic(&_mutex_buffer); }
private:
- StaticSemaphore_t _tcb;
+ StaticSemaphore_t _mutex_buffer;
};
-class MutexRecursiveDynamic : public LockInterface {
- public:
- MutexRecursiveDynamic()
- : LockInterface(xSemaphoreCreateRecursiveMutex()) {}
+// CRTP recursive mutex policy base class
+template
+class MutexRecursivePolicy : public Policy> {
+ protected:
+ friend class Policy>;
- bool take(const TickType_t ticks_to_wait = portMAX_DELAY) const override {
- return xSemaphoreTakeRecursive(_handle, ticks_to_wait);
+ bool takeImpl(const TickType_t ticks_to_wait = portMAX_DELAY) {
+ return xSemaphoreTakeRecursive(this->_handle, ticks_to_wait);
}
- bool give() const override { return xSemaphoreGiveRecursive(_handle); }
+ bool giveImpl() { return xSemaphoreGiveRecursive(this->_handle); }
};
-class MutexRecursiveStatic : public LockInterface {
+// Policy for recursive mutex with dynamic memory allocation
+template
+class MutexRecursiveDynamicPolicy : public MutexRecursivePolicy> {
public:
- MutexRecursiveStatic()
- : LockInterface(xSemaphoreCreateRecursiveMutexStatic(&_tcb)) {}
+ MutexRecursiveDynamicPolicy() { this->_handle = xSemaphoreCreateRecursiveMutex(); }
+};
- bool take(const TickType_t ticks_to_wait = portMAX_DELAY) const override {
- return xSemaphoreTakeRecursive(_handle, ticks_to_wait);
+// Policy for recursive mutex with static memory allocation
+template
+class MutexRecursiveStaticPolicy : public MutexRecursivePolicy> {
+ public:
+ MutexRecursiveStaticPolicy() {
+ this->_handle = xSemaphoreCreateRecursiveMutexStatic(&_mutex_recursive_buffer);
}
- bool give() const override { return xSemaphoreGiveRecursive(_handle); }
-
private:
- StaticSemaphore_t _tcb;
+ StaticSemaphore_t _mutex_recursive_buffer;
};
-class Semaphore : public LockInterface {
+// CRTP binary semaphore policy base class
+template
+class SemaphoreBinaryPolicy : public Policy> {
protected:
- Semaphore(const SemaphoreHandle_t handle)
- : LockInterface(handle) {}
+ friend class Policy>;
- public:
- bool takeFromISR(BaseType_t& task_woken) const {
- return xSemaphoreTakeFromISR(_handle, &task_woken);
+ bool takeImpl(const TickType_t ticks_to_wait = portMAX_DELAY) {
+ return xSemaphoreTake(this->_handle, ticks_to_wait);
}
- bool giveFromISR(BaseType_t& task_woken) const {
- return xSemaphoreGiveFromISR(_handle, &task_woken);
- }
- uint8_t getCount() const { return uxSemaphoreGetCount(_handle); }
+
+ bool giveImpl() { return xSemaphoreGive(this->_handle); }
};
-class SemaphoreBinaryDynamic : public Semaphore {
+// Policy for binary semaphore with dynamic memory allocation
+template
+class SemaphoreBinaryDynamicPolicy : public SemaphoreBinaryPolicy> {
public:
- SemaphoreBinaryDynamic()
- : Semaphore(xSemaphoreCreateBinary()) {}
+ SemaphoreBinaryDynamicPolicy() { this->_handle = xSemaphoreCreateBinary(); }
};
-class SemaphoreBinaryStatic : public Semaphore {
+// Policy for binary semaphore with static memory allocation
+template
+class SemaphoreBinaryStaticPolicy : public SemaphoreBinaryPolicy> {
public:
- SemaphoreBinaryStatic()
- : Semaphore(xSemaphoreCreateBinaryStatic(&_tcb)) {}
+ SemaphoreBinaryStaticPolicy() {
+ this->_handle = xSemaphoreCreateBinaryStatic(&_sem_binary_buffer);
+ }
private:
- StaticSemaphore_t _tcb;
+ StaticSemaphore_t _sem_binary_buffer;
+};
+
+// CRTP counting semaphore policy base class
+template
+class SemaphoreCountingPolicy : public Policy> {
+ protected:
+ friend class Policy>;
+
+ bool takeImpl(const TickType_t ticks_to_wait = portMAX_DELAY) {
+ return xSemaphoreTake(this->_handle, ticks_to_wait);
+ }
+
+ bool giveImpl() { return xSemaphoreGive(this->_handle); }
+
+ public:
+ /**
+ * @brief Get the count of the counting semaphore.
+ * @return UBaseType_t Count of the counting semaphore, 0 if the semaphore is not created.
+ */
+ UBaseType_t getCount() const {
+ if (!this->isCreated()) return 0;
+ return uxSemaphoreGetCount(this->_handle);
+ }
};
-class SemaphoreCountingDynamic : public Semaphore {
+// Policy for counting semaphore with dynamic memory allocation
+template
+class SemaphoreCountingDynamicPolicy
+ : public SemaphoreCountingPolicy> {
+
public:
- SemaphoreCountingDynamic(const uint8_t max_count, const uint8_t initial_count = 0)
- : Semaphore(xSemaphoreCreateCounting(max_count, initial_count)) {}
+ SemaphoreCountingDynamicPolicy() {
+ this->_handle = xSemaphoreCreateCounting(MaxCount, InitialCount);
+ }
};
-class SemaphoreCountingStatic : public Semaphore {
+// Policy for counting semaphore with static memory allocation
+template
+class SemaphoreCountingStaticPolicy
+ : public SemaphoreCountingPolicy> {
+
public:
- SemaphoreCountingStatic(const uint8_t max_count, const uint8_t initial_count = 0)
- : Semaphore(xSemaphoreCreateCountingStatic(max_count, initial_count, &_tcb)) {}
+ SemaphoreCountingStaticPolicy() {
+ this->_handle = xSemaphoreCreateCountingStatic(MaxCount, InitialCount, &_sem_counting_buffer);
+ }
private:
- StaticSemaphore_t _tcb;
-};
\ No newline at end of file
+ StaticSemaphore_t _sem_counting_buffer;
+};
+
+// Main Lock class. You need to specify the policy used
+template
+class Lock : public ILock, public Policy {
+ public:
+ using Policy::Policy;
+
+ ~Lock() {
+ if (Policy::isCreated()) vSemaphoreDelete(Policy::getHandle());
+ }
+
+ /**
+ * @brief Get the low-level handle of the lock. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return SemaphoreHandle_t Lock handle, nullptr if the lock is not created.
+ */
+ SemaphoreHandle_t getHandle() const { return Policy::getHandle(); }
+
+ /**
+ * @brief Check if the lock is created.
+ * @return true Lock is created.
+ */
+ bool isCreated() const { return Policy::isCreated(); }
+
+ /**
+ * @brief Take the lock.
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Lock taken successfully, false if the lock is not created or failed to take.
+ */
+ bool take(const TickType_t ticks_to_wait = portMAX_DELAY) { return Policy::take(ticks_to_wait); }
+
+ /**
+ * @brief Give the lock.
+ * @return true Lock given successfully, false if the lock is not created or failed to give.
+ */
+ bool give() { return Policy::give(); }
+
+ /**
+ * @brief Check if the lock is taken.
+ * @return true Lock is taken.
+ */
+ explicit operator bool() const { return isCreated(); }
+};
+
+} // namespace Internal
+
+using MutexDynamic = Internal::Lock>;
+using MutexStatic = Internal::Lock>;
+using MutexRecursiveDynamic = Internal::Lock>;
+using MutexRecursiveStatic = Internal::Lock>;
+using SemBinaryDynamic = Internal::Lock>;
+using SemBinaryStatic = Internal::Lock>;
+
+template
+using SemCountingDynamic =
+ Internal::Lock>;
+
+template
+using SemCountingStatic =
+ Internal::Lock>;
+
+} // namespace RTOS::Locks
\ No newline at end of file
diff --git a/src/RTOScppQueue.h b/src/RTOScppQueue.h
index 759e8db..0073c7b 100644
--- a/src/RTOScppQueue.h
+++ b/src/RTOScppQueue.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,122 +7,382 @@
#pragma once
#include
+
#include
-// Forward declaration of QueueSet
-class QueueSet;
+namespace RTOS::Queues {
-class QueueInterface {
- private:
- friend class QueueSet;
- friend bool operator==(const QueueSetMemberHandle_t& queue_set_member,
- const QueueInterface& queue);
+// Interface for Queue objects, useful when using pointers
+class IQueue {
+ protected:
+ IQueue() = default;
+
+ public:
+ IQueue(const IQueue&) = delete;
+ IQueue& operator=(const IQueue&) = delete;
+ IQueue(IQueue&&) = delete;
+ IQueue& operator=(IQueue&&) = delete;
+ virtual ~IQueue() = default;
+
+ /**
+ * @brief Get the low-level handle of the queue. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return QueueHandle_t Queue handle, nullptr if the queue is not created.
+ */
+ virtual QueueHandle_t getHandle() const = 0;
+
+ /**
+ * @brief Check if the queue is created.
+ * @return true Queue is created.
+ */
+ virtual bool isCreated() const = 0;
+
+ /**
+ * @brief Get the number of available messages to read from the queue.
+ * @return uint32_t Number of available messages.
+ */
+ virtual uint32_t getAvailableMessages() const = 0;
+
+ /**
+ * @brief Get the number of available messages to read from the queue from an ISR.
+ * @return uint32_t Number of available messages.
+ */
+ virtual uint32_t getAvailableMessagesFromISR() const = 0;
+
+ /**
+ * @brief Get the number of available spaces to write to the queue.
+ * @return uint32_t Number of available spaces.
+ */
+ virtual uint32_t getAvailableSpaces() const = 0;
+
+ /**
+ * @brief Reset the queue.
+ * @return true Queue reset successfully, false if the queue is not created.
+ */
+ virtual bool reset() const = 0;
+
+ /**
+ * @brief Check if the queue is full.
+ * @return true Queue is full, false if the queue is not created.
+ */
+ virtual bool isFull() const = 0;
+
+ /**
+ * @brief Check if the queue is empty.
+ * @return true Queue is empty, false if the queue is not created.
+ */
+ virtual bool isEmpty() const = 0;
+
+ /**
+ * @brief Check if the queue is full from an ISR.
+ * @return true Queue is full, false if the queue is not created.
+ */
+ virtual bool isFullFromISR() const = 0;
+
+ /**
+ * @brief Check if the queue is empty from an ISR.
+ * @return true Queue is empty, false if the queue is not created.
+ */
+ virtual bool isEmptyFromISR() const = 0;
+
+ /**
+ * @brief Check if the queue is created.
+ * @return true Queue is created.
+ */
+ virtual explicit operator bool() const = 0;
+
+ friend bool operator==(const QueueSetMemberHandle_t& queue_set_member, const IQueue& queue);
+};
+
+// Comparison operator for QueueSet
+inline bool operator==(const QueueSetMemberHandle_t& queue_set_member, const IQueue& queue) {
+ return queue_set_member == queue.getHandle();
+}
+namespace Internal {
+
+// CRTP base class
+template
+class Policy {
protected:
- QueueInterface(QueueHandle_t handle)
- : _handle(handle) {}
+ Policy()
+ : _handle(nullptr) {}
+
+ QueueHandle_t getHandle() const { return _handle; }
+ bool isCreated() const { return _handle != nullptr; }
+
+ protected:
QueueHandle_t _handle;
+};
+// Policy for queue with dynamic memory allocation
+template
+class DynamicPolicy : public Policy> {
public:
- virtual ~QueueInterface() {
- if (_handle) vTaskDelete(_handle);
+ DynamicPolicy() { this->_handle = xQueueCreate(Length, sizeof(T)); }
+};
+
+// Policy for queue with static memory allocation
+template
+class StaticPolicy : public Policy> {
+ public:
+ StaticPolicy() {
+ this->_handle = xQueueCreateStatic(Length, sizeof(T), _storage, &_queue_buffer);
}
- QueueInterface(const QueueInterface&) = delete;
- QueueInterface& operator=(const QueueInterface&) = delete;
- QueueInterface(QueueInterface&&) noexcept = delete;
- QueueInterface& operator=(QueueInterface&&) noexcept = delete;
+ private:
+ StaticQueue_t _queue_buffer;
+ uint8_t _storage[Length * sizeof(T)];
+};
- QueueHandle_t getHandle() const { return _handle; }
+// Policy for queue with external storage
+template
+class ExternalStoragePolicy : public Policy> {
+ public:
+ static constexpr uint32_t REQUIRED_SIZE = Length * sizeof(T);
- uint32_t getAvailableMessages() const { return uxQueueMessagesWaiting(_handle); }
- uint32_t getAvailableMessagesFromISR() const { return uxQueueMessagesWaitingFromISR(_handle); }
- uint32_t getAvailableSpaces() const { return uxQueueSpacesAvailable(_handle); }
+ ExternalStoragePolicy() { this->_handle = nullptr; }
- void reset() const { xQueueReset(_handle); }
- bool isFull() const { return uxQueueSpacesAvailable(_handle) == 0; }
- bool isEmpty() const { return uxQueueMessagesWaiting(_handle) == 0; }
- bool isFullFromISR() const { return xQueueIsQueueFullFromISR(_handle); }
- bool isEmptyFromISR() const { return xQueueIsQueueEmptyFromISR(_handle); }
+ /**
+ * @brief Create the queue with the specified buffer.
+ * @param buffer Buffer to use.
+ * @return true Queue created, false if the buffer is nullptr or failed to create the queue.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
+ this->_handle = xQueueCreateStatic(Length, sizeof(T), buffer, &_queue_buffer);
+ return (this->_handle != nullptr);
+ }
- explicit operator bool() const { return _handle != nullptr; }
+ private:
+ StaticQueue_t _queue_buffer;
};
-inline bool operator==(const QueueSetMemberHandle_t& queue_set_member,
- const QueueInterface& queue) {
- return queue_set_member == queue._handle;
-}
+// Main Queue class. You need to specify the policy used
+template
+class Queue : public IQueue, public Policy {
+ public:
+ using Policy::Policy; // Inherit constructor
-template
-class _QueueBase : public QueueInterface {
- protected:
- _QueueBase(QueueHandle_t handle)
- : QueueInterface(handle) {}
+ ~Queue() {
+ if (Policy::isCreated()) vQueueDelete(Policy::getHandle());
+ }
- public:
- void overwrite(const T& item) const { xQueueOverwrite(_handle, &item); }
+ /**
+ * @brief Get the low-level handle of the queue. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return QueueHandle_t Queue handle, nullptr if the queue is not created.
+ */
+ QueueHandle_t getHandle() const override { return Policy::getHandle(); }
- bool push(const T& item, const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xQueueSendToFront(_handle, &item, ticks_to_wait);
+ /**
+ * @brief Check if the queue is created.
+ * @return true Queue is created.
+ */
+ bool isCreated() const override { return Policy::isCreated(); }
+
+ /**
+ * @brief Get the number of available messages to read from the queue.
+ * @return uint32_t Number of available messages.
+ */
+ uint32_t getAvailableMessages() const override {
+ if (!isCreated()) return 0;
+ return uxQueueMessagesWaiting(getHandle());
}
- bool add(const T& item, const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xQueueSendToBack(_handle, &item, ticks_to_wait);
+ /**
+ * @brief Get the number of available messages to read from the queue from an ISR.
+ * @return uint32_t Number of available messages.
+ */
+ uint32_t getAvailableMessagesFromISR() const override {
+ if (!isCreated()) return 0;
+ return uxQueueMessagesWaitingFromISR(getHandle());
}
- bool pop(T& var, const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xQueueReceive(_handle, &var, ticks_to_wait);
+ /**
+ * @brief Get the number of available spaces to write to the queue.
+ * @return uint32_t Number of available spaces.
+ */
+ uint32_t getAvailableSpaces() const override {
+ if (!isCreated()) return 0;
+ return uxQueueSpacesAvailable(getHandle());
}
- bool peek(T& var, const TickType_t ticks_to_wait = 0) const {
- return xQueuePeek(_handle, &var, ticks_to_wait);
+ /**
+ * @brief Reset the queue.
+ * @return true Queue reset successfully, false if the queue is not created.
+ */
+ bool reset() const override {
+ if (!isCreated()) return false;
+ xQueueReset(getHandle());
+ }
+
+ /**
+ * @brief Check if the queue is full.
+ * @return true Queue is full, false if the queue is not created.
+ */
+ bool isFull() const override {
+ if (!isCreated()) return false;
+ return uxQueueSpacesAvailable(getHandle()) == 0;
+ }
+
+ /**
+ * @brief Check if the queue is empty.
+ * @return true Queue is empty, false if the queue is not created.
+ */
+ bool isEmpty() const override {
+ if (!isCreated()) return false;
+ return uxQueueMessagesWaiting(getHandle()) == 0;
}
+ /**
+ * @brief Check if the queue is full from an ISR.
+ * @return true Queue is full, false if the queue is not created.
+ */
+ bool isFullFromISR() const override {
+ if (!isCreated()) return false;
+ return xQueueIsQueueFullFromISR(getHandle());
+ }
+
+ /**
+ * @brief Check if the queue is empty from an ISR.
+ * @return true Queue is empty, false if the queue is not created.
+ */
+ bool isEmptyFromISR() const override {
+ if (!isCreated()) return false;
+ return xQueueIsQueueEmptyFromISR(getHandle());
+ }
+
+ /**
+ * @brief Check if the queue is created.
+ * @return true Queue is created.
+ */
+ explicit operator bool() const override { return isCreated(); }
+
+ /**
+ * @brief Push an item to the front of the queue (LIFO order).
+ * @param item Item to push.
+ * @param ticks_to_wait Maximum time to wait for the queue to be available.
+ * @return true Item pushed successfully, false if the queue is not created or the queue is full.
+ */
+ bool push(const T& item, const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ if (!isCreated()) return false;
+ return xQueueSendToFront(getHandle(), &item, ticks_to_wait);
+ }
+
+ /**
+ * @brief Push an item to the front of the queue (LIFO order) from an ISR.
+ * @param item Item to push.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item pushed successfully, false if the queue is not created or the queue is full.
+ */
bool pushFromISR(const T& item, BaseType_t& task_woken) const {
- return xQueueSendToFrontFromISR(_handle, &item, &task_woken);
+ if (!isCreated()) return false;
+ return xQueueSendToFrontFromISR(getHandle(), &item, &task_woken);
+ }
+
+ /**
+ * @brief Add an item to the back of the queue (FIFO order).
+ * @param item Item to add.
+ * @param ticks_to_wait Maximum time to wait for the queue to be available.
+ * @return true Item added successfully, false if the queue is not created or the queue is full.
+ */
+ bool add(const T& item, const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ if (!isCreated()) return false;
+ return xQueueSendToBack(getHandle(), &item, ticks_to_wait);
}
+ /**
+ * @brief Add an item to the back of the queue (FIFO order) from an ISR.
+ * @param item Item to add.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item added successfully, false if the queue is not created or the queue is full.
+ */
bool addFromISR(const T& item, BaseType_t& task_woken) const {
- return xQueueSendToBackFromISR(_handle, &item, &task_woken);
+ if (!isCreated()) return false;
+ return xQueueSendToBackFromISR(getHandle(), &item, &task_woken);
}
+ /**
+ * @brief Pop (remove) an item from the queue.
+ * @param var Variable to store the item.
+ * @param ticks_to_wait Maximum time to wait for the queue to be available.
+ * @return true Item popped successfully, false if the queue is not created or the queue is empty.
+ */
+ bool pop(T& var, const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ if (!isCreated()) return false;
+ return xQueueReceive(getHandle(), &var, ticks_to_wait);
+ }
+
+ /**
+ * @brief Pop (remove) an item from the queue from an ISR.
+ * @param var Variable to store the item.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item popped successfully, false if the queue is not created or the queue is empty.
+ */
bool popFromISR(T& var, BaseType_t& task_woken) const {
- return xQueueReceiveFromISR(_handle, &var, &task_woken);
+ if (!isCreated()) return false;
+ return xQueueReceiveFromISR(getHandle(), &var, &task_woken);
}
- bool peekFromISR(T& var) const { return xQueuePeekFromISR(_handle, &var); }
-};
+ /**
+ * @brief Peek (read) an item from the queue without removing it.
+ * @param var Variable to store the item.
+ * @param ticks_to_wait Maximum time to wait for the queue to be available.
+ * @return true Item read successfully, false if the queue is not created or the queue is empty.
+ */
+ bool peek(T& var, const TickType_t ticks_to_wait = 0) const {
+ if (!isCreated()) return false;
+ return xQueuePeek(getHandle(), &var, ticks_to_wait);
+ }
-template
-class QueueDynamic : public _QueueBase {
- public:
- QueueDynamic(const uint32_t length)
- : _QueueBase(xQueueCreate(length, sizeof(T))) {}
-};
+ /**
+ * @brief Peek (read) an item from the queue without removing it from an ISR.
+ * @param var Variable to store the item.
+ * @return true Item read successfully, false if the queue is not created or the queue is empty.
+ */
+ bool peekFromISR(T& var) const {
+ if (!isCreated()) return false;
+ return xQueuePeekFromISR(getHandle(), &var);
+ }
-template
-class QueueStatic : public _QueueBase {
- public:
- QueueStatic()
- : _QueueBase(xQueueCreateStatic(LENGTH, sizeof(T), _storage, &this->_tcb)) {}
+ /**
+ * @brief Overwrite an item in the queue. Use it only with queues of length 1.
+ * @param item Item to overwrite.
+ * @param ticks_to_wait Maximum time to wait for the queue to be available.
+ * @return true Item overwritten successfully, false if the queue is not created.
+ */
+ bool overwrite(const T& item, const TickType_t ticks_to_wait = 0) const {
+ if (!isCreated()) return false;
+ return xQueueOverwrite(getHandle(), &item);
+ }
- private:
- StaticQueue_t _tcb;
- uint8_t _storage[LENGTH * sizeof(T)];
+ /**
+ * @brief Overwrite an item in the queue from an ISR. Use it only with queues of length 1.
+ * @param item Item to overwrite.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item overwritten successfully, false if the queue is not created.
+ */
+ bool overwriteFromISR(const T& item, BaseType_t& task_woken) const {
+ if (!isCreated()) return false;
+ return xQueueOverwriteFromISR(getHandle(), &item, &task_woken);
+ }
};
-template
-class QueueExternalStorage : public _QueueBase {
- public:
- QueueExternalStorage()
- : _QueueBase(nullptr) {}
+} // namespace Internal
- bool init(uint8_t* const buffer, const uint32_t length) {
- this->_handle = xQueueCreateStatic(length, sizeof(T), buffer, &this->_tcb);
- return (this->_handle != nullptr);
- }
+template
+using QueueDynamic = Internal::Queue, T>;
- private:
- StaticQueue_t _tcb;
-};
\ No newline at end of file
+template
+using QueueStatic = Internal::Queue, T>;
+
+template
+using QueueExternalStorage = Internal::Queue, T>;
+
+} // namespace RTOS::Queues
\ No newline at end of file
diff --git a/src/RTOScppQueueSet.h b/src/RTOScppQueueSet.h
index 28c0883..026f597 100644
--- a/src/RTOScppQueueSet.h
+++ b/src/RTOScppQueueSet.h
@@ -1,49 +1,149 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
+#include
+
#include "RTOScppLock.h"
#include "RTOScppQueue.h"
#include "RTOScppRingBuffer.h"
-#include
+
+namespace RTOS::QueueSets {
class QueueSet {
public:
- QueueSet(const uint32_t queue_length)
+ /**
+ * @brief Instantiate a QueueSet object.
+ * @param queue_length Max number of events that the queue set can hold. Take precautions to avoid
+ * an assert from the FreeRTOS kernel if the queue set length is less than the number of events
+ * that are going to be added. Each lock will use 1 event. Each queue will use events equal up to
+ * the queue length. Each ringbuffer will use the number of messages capable of holding at a
+ * certain time, not the length of it.
+ */
+ QueueSet(const UBaseType_t queue_length)
: _handle(xQueueCreateSet(queue_length)) {}
~QueueSet() {
if (_handle) vQueueDelete(_handle);
}
- QueueSet(const QueueSet&) = delete;
- QueueSet& operator=(const QueueSet&) = delete;
- QueueSet(QueueSet&&) noexcept = delete;
- QueueSet& operator=(QueueSet&&) noexcept = delete;
+ QueueSet(const QueueSet&) = delete;
+ QueueSet& operator=(const QueueSet&) = delete;
+ QueueSet(QueueSet&&) = delete;
+ QueueSet& operator=(QueueSet&&) = delete;
+
+ /**
+ * @brief Get the low-level handle of the queue set. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return QueueSetHandle_t Queue set handle, nullptr if the queue set is not created.
+ */
+ QueueSetHandle_t getHandle() const { return _handle; }
- bool add(LockInterface& lock) const { return xQueueAddToSet(lock._handle, _handle); }
- bool add(QueueInterface& queue) const { return xQueueAddToSet(queue._handle, _handle); }
- bool add(RingBufferInterface& ring_buffer) const {
- return xRingbufferAddToQueueSetRead(ring_buffer._handle, _handle);
+ /**
+ * @brief Check if the queue set is created.
+ * @return true Queue set is created.
+ */
+ bool isCreated() const { return _handle != nullptr; }
+
+ /**
+ * @brief Add a lock to the queue set.
+ * @param lock Lock object.
+ * @return true Lock added successfully, false if the queue set is not created or the lock is not
+ * created.
+ */
+ bool add(RTOS::Locks::ILock& lock) const {
+ if (!isCreated() || !lock) return false;
+ return xQueueAddToSet(lock.getHandle(), _handle);
+ }
+
+ /**
+ * @brief Add a queue to the queue set.
+ * @param queue Queue object.
+ * @return true Queue added successfully, false if the queue set is not created or the queue is
+ * not created.
+ */
+ bool add(RTOS::Queues::IQueue& queue) const {
+ if (!isCreated() || !queue) return false;
+ return xQueueAddToSet(queue.getHandle(), _handle);
}
- bool remove(LockInterface& lock) const { return xQueueRemoveFromSet(lock._handle, _handle); }
- bool remove(QueueInterface& queue) const { return xQueueRemoveFromSet(queue._handle, _handle); }
- bool remove(RingBufferInterface& ring_buffer) const {
- return xRingbufferRemoveFromQueueSetRead(ring_buffer._handle, _handle);
+ /**
+ * @brief Add a ring buffer to the queue set.
+ * @param ring_buffer Ring buffer object.
+ * @return true Ring buffer added successfully, false if the queue set is not created or the ring
+ * buffer is not created.
+ */
+ bool add(RTOS::RingBuffers::IRingBuffer& ring_buffer) const {
+ if (!isCreated() || !ring_buffer) return false;
+ return xRingbufferAddToQueueSetRead(ring_buffer.getHandle(), _handle);
}
+ /**
+ * @brief Remove a lock from the queue set.
+ * @param lock Lock object.
+ * @return true Lock removed successfully, false if the queue set is not created or the lock is
+ * not created.
+ */
+ bool remove(RTOS::Locks::ILock& lock) const {
+ if (!isCreated() || !lock) return false;
+ return xQueueRemoveFromSet(lock.getHandle(), _handle);
+ }
+
+ /**
+ * @brief Remove a queue from the queue set.
+ * @param queue Queue object.
+ * @return true Queue removed successfully, false if the queue set is not created or the queue is
+ * not created.
+ */
+ bool remove(RTOS::Queues::IQueue& queue) const {
+ if (!isCreated() || !queue) return false;
+ return xQueueRemoveFromSet(queue.getHandle(), _handle);
+ }
+
+ /**
+ * @brief Remove a ring buffer from the queue set.
+ * @param ring_buffer Ring buffer object.
+ * @return true Ring buffer removed successfully, false if the queue set is not created or the
+ * ring buffer is not created.
+ */
+ bool remove(RTOS::RingBuffers::IRingBuffer& ring_buffer) const {
+ if (!isCreated() || !ring_buffer) return false;
+ return xRingbufferRemoveFromQueueSetRead(ring_buffer.getHandle(), _handle);
+ }
+
+ /**
+ * @brief Select a member of the queue set which holds an event.
+ * @param ticks_to_wait Number of ticks to wait for a member to be available.
+ * @return QueueSetMemberHandle_t Queue set member handle, nullptr if the queue set is not
+ * created.
+ */
QueueSetMemberHandle_t select(const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ if (!isCreated()) return nullptr;
return xQueueSelectFromSet(_handle, ticks_to_wait);
}
- QueueSetMemberHandle_t selectFromISR() const { return xQueueSelectFromSetFromISR(_handle); }
- explicit operator bool() const { return _handle != nullptr; }
+ /**
+ * @brief Select a member of the queue set which holds an event from an ISR.
+ * @return QueueSetMemberHandle_t Queue set member handle, nullptr if the queue set is not
+ * created.
+ */
+ QueueSetMemberHandle_t selectFromISR() const {
+ if (!isCreated()) return nullptr;
+ return xQueueSelectFromSetFromISR(_handle);
+ }
+
+ /**
+ * @brief Check if the queue set is created.
+ * @return true Queue set is created.
+ */
+ explicit operator bool() const { return isCreated(); }
private:
QueueSetHandle_t _handle;
-};
\ No newline at end of file
+};
+
+} // namespace RTOS::QueueSets
\ No newline at end of file
diff --git a/src/RTOScppRingBuffer.h b/src/RTOScppRingBuffer.h
index 6346e2c..8f3eb06 100644
--- a/src/RTOScppRingBuffer.h
+++ b/src/RTOScppRingBuffer.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,232 +7,480 @@
#pragma once
#include
+
#include
-// Forward declaration of QueueSet
-class QueueSet;
-
-class RingBufferInterface {
- private:
- friend class QueueSet;
- friend bool operator==(const QueueSetMemberHandle_t& queue_set_member,
- const RingBufferInterface& ring_buffer);
+namespace RTOS::RingBuffers {
+// Interface for RingBuffer objects, useful when using pointers
+class IRingBuffer {
protected:
- RingBufferInterface(const RingbufHandle_t handle)
- : _handle(handle) {}
-
- RingbufHandle_t _handle;
+ IRingBuffer() = default;
public:
- virtual ~RingBufferInterface() {
- if (_handle) vRingbufferDelete(_handle);
- }
+ IRingBuffer(const IRingBuffer&) = delete;
+ IRingBuffer& operator=(const IRingBuffer&) = delete;
+ IRingBuffer(IRingBuffer&&) = delete;
+ IRingBuffer& operator=(IRingBuffer&&) = delete;
+ virtual ~IRingBuffer() = default;
+
+ /**
+ * @brief Get the low-level handle of the ringbuffer. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return RingbufHandle_t Ringbuffer handle, nullptr if the Ringbuffer is not created.
+ */
+ virtual RingbufHandle_t getHandle() const = 0;
+
+ /**
+ * @brief Check if the ringbuffer is created.
+ * @return true Ringbuffer is created.
+ */
+ virtual bool isCreated() const = 0;
+
+ /**
+ * @brief Get the maximum size of an item that can be placed in the ring buffer.
+ * @return size_t Maximum size, in bytes, of an item that can be placed in a ring buffer.
+ */
+ virtual size_t getMaxItemSize() const = 0;
+
+ /**
+ * @brief Get the current free size available for an item/data in the buffer.
+ * @return size_t Free size available for an item/data in the buffer.
+ */
+ virtual size_t getFreeSize() const = 0;
+
+ /**
+ * @brief Check if the ringbuffer is created.
+ * @return true Ringbuffer is created.
+ */
+ virtual explicit operator bool() const = 0;
- RingBufferInterface(const RingBufferInterface&) = delete;
- RingBufferInterface& operator=(const RingBufferInterface&) = delete;
- RingBufferInterface(RingBufferInterface&&) noexcept = delete;
- RingBufferInterface& operator=(RingBufferInterface&&) noexcept = delete;
-
- explicit operator bool() const { return _handle != nullptr; }
+ friend bool operator==(const QueueSetMemberHandle_t& queue_set_member,
+ const IRingBuffer& ringbuf);
};
-inline bool operator==(const QueueSetMemberHandle_t& queue_set_member,
- const RingBufferInterface& ring_buffer) {
- return xRingbufferCanRead(ring_buffer._handle, queue_set_member);
+// Comparison operator for QueueSet
+inline bool operator==(const QueueSetMemberHandle_t& queue_set_member, const IRingBuffer& ringbuf) {
+ return xRingbufferCanRead(ringbuf.getHandle(), queue_set_member);
}
-template
-class RingBufferBase : public RingBufferInterface {
- protected:
- RingBufferBase(const RingbufHandle_t handle)
- : RingBufferInterface(handle) {}
- virtual ~RingBufferBase() {}
+namespace Internal {
+// CRTP base policy class
+template
+class Policy {
public:
- bool send(const T* const item, const uint32_t item_size,
- const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xRingbufferSend(_handle, (void*)item, item_size, ticks_to_wait);
+ /**
+ * @brief Get the low-level handle of the ringbuffer. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return RingbufHandle_t Ringbuffer handle, nullptr if the Ringbuffer is not created.
+ */
+ RingbufHandle_t getHandle() const { return _handle; }
+
+ /**
+ * @brief Check if the ringbuffer is created.
+ * @return true Ringbuffer is created.
+ */
+ bool isCreated() const { return _handle != nullptr; }
+
+ /**
+ * @brief Send an item to the ring buffer.
+ * @param item Item to send.
+ * @param item_size Size of the item to send.
+ * @param ticks_to_wait Maximum time to wait for the item to be sent.
+ * @return true Item sent successfully, false if the ring buffer is not created or failed to send
+ * the item.
+ */
+ bool send(const T* const item, const size_t item_size,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ if (!isCreated()) return false;
+ return xRingbufferSend(this->_handle, item, item_size, ticks_to_wait);
}
- bool sendFromISR(const T* const item, const uint32_t item_size,
- BaseType_t& higher_priority_task_woken) const {
- return xRingbufferSendFromISR(_handle, (void*)item, item_size, &higher_priority_task_woken);
+ /**
+ * @brief Send an item to the ring buffer from an ISR.
+ * @param item Item to send.
+ * @param item_size Size of the item to send.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item sent successfully, false if the ring buffer is not created or failed to send
+ * the item.
+ */
+ bool sendFromISR(const T* const item, const size_t item_size, BaseType_t& task_woken) const {
+ return xRingbufferSendFromISR(this->_handle, item, item_size, &task_woken);
}
- void returnItem(const T* const item) const { vRingbufferReturnItem(_handle, (void*)item); }
+ /**
+ * @brief Return an item to the ring buffer after using it.
+ * @param item Item to return.
+ * @return true Item returned successfully, false if the ring buffer is not created.
+ */
+ bool returnItem(T* const item) const {
+ if (!isCreated()) return false;
+ vRingbufferReturnItem(this->_handle, item);
+ return true;
+ }
- void returnItemFromISR(const T* const item, BaseType_t& higher_priority_task_woken) const {
- vRingbufferReturnItemFromISR(_handle, (void*)item, &higher_priority_task_woken);
+ /**
+ * @brief Return an item to the ring buffer after using it from an ISR.
+ * @param item Item to return.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Item returned successfully, false if the ring buffer is not created.
+ */
+ bool returnItemFromISR(T* const item, BaseType_t& task_woken) const {
+ if (!isCreated()) return false;
+ vRingbufferReturnItemFromISR(this->_handle, item, &task_woken);
+ return true;
}
-};
-template
-class RingBufferNoSplitBase : public RingBufferBase {
protected:
- RingBufferNoSplitBase(const RingbufHandle_t handle)
- : RingBufferBase(handle) {}
- virtual ~RingBufferNoSplitBase() {}
+ Policy()
+ : _handle(nullptr) {}
+
+ RingbufHandle_t _handle;
+};
+// CRTP no-split base policy class
+template
+class NoSplitPolicy : public Policy {
public:
- T* receive(uint32_t& item_size, const TickType_t ticks_to_wait = portMAX_DELAY) {
- return (T*)xRingbufferReceive(this->_handle, &item_size, ticks_to_wait);
+ /**
+ * @brief Receive an item from the ring buffer.
+ * @param item_size Size of the item received.
+ * @param ticks_to_wait Maximum time to wait for the item to be received.
+ * @return T* Item received, nullptr if the ring buffer is not created or failed to receive the
+ * item.
+ */
+ T* receive(size_t& item_size, const TickType_t ticks_to_wait = portMAX_DELAY) {
+ if (!this->isCreated()) return nullptr;
+ return static_cast(xRingbufferReceive(this->_handle, &item_size, ticks_to_wait));
}
- T* receiveFromISR(uint32_t& item_size) {
- return (T*)xRingbufferReceiveFromISR(this->_handle, &item_size);
+ /**
+ * @brief Receive an item from the ring buffer from an ISR.
+ * @param item_size Size of the item received.
+ * @return T* Item received, nullptr if the ring buffer is not created or failed to receive the
+ * item.
+ */
+ T* receiveFromISR(size_t& item_size) {
+ if (!this->isCreated()) return nullptr;
+ return static_cast(xRingbufferReceiveFromISR(this->_handle, &item_size));
}
};
-template
-class RingBufferNoSplitDynamic : public RingBufferNoSplitBase {
+// Policy for no-split ring buffer with dynamic memory allocation
+template
+class NoSplitDynamicPolicy : public NoSplitPolicy, T> {
public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
-
- RingBufferNoSplitDynamic(const uint32_t length)
- : RingBufferNoSplitBase(xRingbufferCreate(length * ALIGNED_SIZE, RINGBUF_TYPE_NOSPLIT)) {}
+ NoSplitDynamicPolicy() { this->_handle = xRingbufferCreate(Length, RINGBUF_TYPE_NOSPLIT); }
};
-template
-class RingBufferNoSplitStatic : public RingBufferNoSplitBase {
- public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
+// Policy for no-split ring buffer with static memory allocation
+template
+class NoSplitStaticPolicy : public NoSplitPolicy, T> {
+ private:
+ // Size aligned to nearest 4 bytes
+ static constexpr size_t REQUIRED_SIZE = 4 * ((Length + 3) / 4);
- RingBufferNoSplitStatic()
- : RingBufferNoSplitBase(
- xRingbufferCreateStatic(LENGTH * ALIGNED_SIZE, RINGBUF_TYPE_NOSPLIT, _storage, &_tcb)) {}
+ public:
+ NoSplitStaticPolicy() {
+ this->_handle =
+ xRingbufferCreateStatic(REQUIRED_SIZE, RINGBUF_TYPE_NOSPLIT, _storage, &_ringbuf_buffer);
+ }
private:
- StaticRingbuffer_t _tcb;
- uint8_t _storage[LENGTH * ALIGNED_SIZE];
+ StaticRingbuffer_t _ringbuf_buffer;
+ uint8_t _storage[REQUIRED_SIZE];
};
-template
-class RingBufferNoSplitExternalStorage : public RingBufferNoSplitBase {
+// Policy for no-split ring buffer with external storage
+template
+class NoSplitExternalStoragePolicy
+ : public NoSplitPolicy, T> {
public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
+ // Size aligned to nearest 4 bytes
+ static constexpr size_t REQUIRED_SIZE = 4 * ((Length + 3) / 4);
+
+ /**
+ * @brief Create the ring buffer with external storage.
+ * @param buffer External storage buffer.
+ * @return true Ring buffer created successfully, false if the buffer is nullptr or failed to
+ * create it.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
- RingBufferNoSplitExternalStorage()
- : RingBufferNoSplitBase(nullptr) {}
+ this->_handle =
+ xRingbufferCreateStatic(REQUIRED_SIZE, RINGBUF_TYPE_NOSPLIT, buffer, &_ringbuf_buffer);
- bool create(StaticRingbuffer_t* const tcb, uint8_t* const buffer_storage,
- const uint32_t buffer_size) {
- this->_handle = xRingbufferCreateStatic(buffer_size, RINGBUF_TYPE_NOSPLIT, buffer_storage, tcb);
- return this->_handle != nullptr ? true : false;
+ return this->_handle != nullptr;
}
-};
-template
-class RingBufferSplitBase : public RingBufferBase {
- protected:
- RingBufferSplitBase(const RingbufHandle_t handle)
- : RingBufferBase(handle) {}
- virtual ~RingBufferSplitBase() {}
+ private:
+ StaticRingbuffer_t _ringbuf_buffer;
+};
+// CRTP split base policy class
+template
+class SplitPolicy : public Policy {
public:
- bool receive(T** head, T** tail, uint32_t& head_item_size, uint32_t& tail_item_size,
- const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xRingbufferReceiveSplit(
- this->_handle, head, tail, &head_item_size, &tail_item_size, ticks_to_wait);
+ /**
+ * @brief Receive an item from the ring buffer.
+ * @param head Pointer to the head item received.
+ * @param tail Pointer to the tail item received.
+ * @param head_item_size Size of the head item received.
+ * @param tail_item_size Size of the tail item received.
+ * @param ticks_to_wait Maximum time to wait for the item to be received.
+ * @return true Items received successfully, false if the ring buffer is not created or failed to
+ * receive the items.
+ */
+ bool receive(T*& head, T*& tail, size_t& head_item_size, size_t& tail_item_size,
+ const TickType_t ticks_to_wait = portMAX_DELAY) {
+ if (!this->isCreated()) return false;
+
+ return xRingbufferReceiveSplit(this->_handle,
+ reinterpret_cast(&head),
+ reinterpret_cast(&tail),
+ &head_item_size,
+ &tail_item_size,
+ ticks_to_wait);
}
- bool receiveFromISR(T** head, T** tail, uint32_t& head_item_size,
- uint32_t& tail_item_size) const {
- return xRingbufferReceiveSplitFromISR(
- this->_handle, head, tail, &head_item_size, &tail_item_size);
+ /**
+ * @brief Receive an item from the ring buffer from an ISR.
+ * @param head Pointer to the head item received.
+ * @param tail Pointer to the tail item received.
+ * @param head_item_size Size of the head item received.
+ * @param tail_item_size Size of the tail item received.
+ * @return true Items received successfully, false if the ring buffer is not created or failed to
+ * receive the items.
+ */
+ bool receiveFromISR(T*& head, T*& tail, size_t& head_item_size, size_t& tail_item_size) {
+ if (!this->isCreated()) return false;
+
+ return xRingbufferReceiveSplitFromISR(this->_handle,
+ reinterpret_cast(&head),
+ reinterpret_cast(&tail),
+ &head_item_size,
+ &tail_item_size);
}
};
-template
-class RingBufferSplitDynamic : public RingBufferSplitBase {
- public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
+// Policy for split ring buffer with dynamic memory allocation
+template
+class SplitDynamicPolicy : public SplitPolicy, T> {
+ private:
+ // Size aligned to nearest 4 bytes
+ static constexpr size_t REQUIRED_SIZE = 4 * ((Length + 3) / 4);
- RingBufferSplitDynamic(const uint32_t length)
- : RingBufferSplitBase(xRingbufferCreate(length * ALIGNED_SIZE, RINGBUF_TYPE_ALLOWSPLIT)) {}
+ public:
+ SplitDynamicPolicy() {
+ this->_handle = xRingbufferCreate(REQUIRED_SIZE, RINGBUF_TYPE_ALLOWSPLIT);
+ }
};
-template
-class RingBufferSplitStatic : public RingBufferSplitBase {
- public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
+// Policy for split ring buffer with static memory allocation
+template
+class SplitStaticPolicy : public SplitPolicy, T> {
+ private:
+ // Size aligned to nearest 4 bytes
+ static constexpr size_t REQUIRED_SIZE = 4 * ((Length + 3) / 4);
- RingBufferSplitStatic()
- : RingBufferSplitBase(xRingbufferCreateStatic(LENGTH * ALIGNED_SIZE,
- RINGBUF_TYPE_ALLOWSPLIT, _storage, &_tcb)) {}
+ public:
+ SplitStaticPolicy() {
+ this->_handle =
+ xRingbufferCreateStatic(REQUIRED_SIZE, RINGBUF_TYPE_ALLOWSPLIT, _storage, &_ringbuf_buffer);
+ }
private:
- StaticRingbuffer_t _tcb;
- uint8_t _storage[LENGTH * ALIGNED_SIZE];
+ StaticRingbuffer_t _ringbuf_buffer;
+ uint8_t _storage[REQUIRED_SIZE];
};
-template
-class RingBufferSplitExternalStorage : public RingBufferSplitBase {
+// Policy for split ring buffer with external storage
+template
+class SplitExternalStoragePolicy : public SplitPolicy, T> {
public:
- // Size aligned to nearest 4 bytes + 8 bytes per item
- static constexpr uint32_t ALIGNED_SIZE = 4 * ((sizeof(T) + 3) / 4) + 8;
-
- RingBufferSplitExternalStorage()
- : RingBufferSplitBase(nullptr) {}
+ // Size aligned to nearest 4 bytes
+ static constexpr size_t REQUIRED_SIZE = 4 * ((Length + 3) / 4);
+
+ /**
+ * @brief Create the ring buffer with external storage.
+ * @param buffer External storage buffer.
+ * @return true Ring buffer created successfully, false if the buffer is nullptr or failed to
+ * create it.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
- bool create(StaticRingbuffer_t* tcb, uint8_t* buffer_storage, uint32_t buffer_size) {
this->_handle =
- xRingbufferCreateStatic(buffer_size, RINGBUF_TYPE_ALLOWSPLIT, buffer_storage, tcb);
- return this->_handle != nullptr ? true : false;
+ xRingbufferCreateStatic(REQUIRED_SIZE, RINGBUF_TYPE_ALLOWSPLIT, buffer, &_ringbuf_buffer);
+
+ return this->_handle != nullptr;
}
-};
-template
-class RingBufferByteBase : public RingBufferBase {
- protected:
- RingBufferByteBase(const RingbufHandle_t handle)
- : RingBufferBase(handle) {}
- virtual ~RingBufferByteBase() {}
+ private:
+ StaticRingbuffer_t _ringbuf_buffer;
+};
+// CRTP byte base policy class
+template
+class BytePolicy : public Policy {
public:
- T* receiveUpTo(const uint32_t max_item_size, uint32_t& item_size,
- const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return (T*)xRingbufferReceiveUpTo(this->_handle, &item_size, ticks_to_wait, max_item_size);
+ /**
+ * @brief Receive an item from the ring buffer.
+ * @param max_item_size Maximum size of the item to receive.
+ * @param item_size Size of the item received.
+ * @param ticks_to_wait Maximum time to wait for the item to be received.
+ * @return uint8_t* Item received, nullptr if the ring buffer is not created or failed to receive
+ * the item.
+ */
+ uint8_t* receiveUpTo(const size_t max_item_size, size_t& item_size,
+ const TickType_t ticks_to_wait = portMAX_DELAY) {
+ if (!this->isCreated()) return nullptr;
+
+ return static_cast(
+ xRingbufferReceiveUpTo(this->_handle, &item_size, ticks_to_wait, max_item_size));
}
- T* receiveUpToFromISR(const uint32_t max_item_size, uint32_t& item_size) const {
- return (T*)xRingbufferReceiveUpToFromISR(this->_handle, &item_size, max_item_size);
+ /**
+ * @brief Receive an item from the ring buffer from an ISR.
+ * @param max_item_size Maximum size of the item to receive.
+ * @param item_size Size of the item received.
+ * @return uint8_t* Item received, nullptr if the ring buffer is not created or failed to receive
+ * the item.
+ */
+ uint8_t* receiveUpToFromISR(const size_t max_item_size, size_t& item_size) {
+ if (!this->isCreated()) return nullptr;
+
+ return static_cast(
+ xRingbufferReceiveUpToFromISR(this->_handle, &item_size, max_item_size));
}
};
-template
-class RingBufferByteDynamic : public RingBufferByteBase {
+// Policy for byte ring buffer with dynamic memory allocation
+template
+class ByteDynamicPolicy : public BytePolicy> {
public:
- RingBufferByteDynamic(const uint32_t length)
- : RingBufferByteBase(xRingbufferCreate(length, RINGBUF_TYPE_BYTEBUF)) {}
+ ByteDynamicPolicy() { this->_handle = xRingbufferCreate(Length, RINGBUF_TYPE_BYTEBUF); }
};
-template
-class RingBufferByteStatic : public RingBufferByteBase {
+// Policy for byte ring buffer with static memory allocation
+template
+class ByteStaticPolicy : public BytePolicy> {
public:
- RingBufferByteStatic()
- : RingBufferByteBase(
- xRingbufferCreateStatic(LENGTH, RINGBUF_TYPE_BYTEBUF, _storage, &_tcb)) {}
+ ByteStaticPolicy() {
+ this->_handle =
+ xRingbufferCreateStatic(Length, RINGBUF_TYPE_BYTEBUF, _storage, &_ringbuf_buffer);
+ }
+
+ private:
+ StaticRingbuffer_t _ringbuf_buffer;
+ uint8_t _storage[Length];
+};
+
+// Policy for byte ring buffer with external storage
+template
+class ByteExternalStoragePolicy : public BytePolicy> {
+ public:
+ static constexpr size_t REQUIRED_SIZE = Length;
+
+ /**
+ * @brief Create the ring buffer with external storage.
+ * @param buffer External storage buffer.
+ * @return true Ring buffer created successfully, false if the buffer is nullptr or failed to
+ * create it.
+ */
+ bool create(uint8_t* const buffer) {
+ if (buffer == nullptr) return false;
+
+ this->_handle = xRingbufferCreateStatic(Length, RINGBUF_TYPE_BYTEBUF, buffer, &_ringbuf_buffer);
+ return this->_handle != nullptr;
+ }
private:
- StaticRingbuffer_t _tcb;
- uint8_t _storage[LENGTH];
+ StaticRingbuffer_t _ringbuf_buffer;
};
-template
-class RingBufferByteExternalStorage : public RingBufferByteBase {
+// Main class for RingBuffer objects
+template
+class RingBuffer : public IRingBuffer, public Policy {
public:
- RingBufferByteExternalStorage()
- : RingBufferByteBase(nullptr) {}
+ using Policy::Policy; // Inherit constructor
- bool create(StaticRingbuffer_t* const tcb, uint8_t* const buffer_storage,
- const uint32_t buffer_size) {
- this->_handle = xRingbufferCreateStatic(buffer_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, tcb);
- return this->_handle != nullptr ? true : false;
+ ~RingBuffer() {
+ if (Policy::getHandle()) vRingbufferDelete(Policy::getHandle());
}
-};
\ No newline at end of file
+
+ /**
+ * @brief Get the low-level handle of the ringbuffer. Useful for direct FreeRTOS API calls. Use it
+ * with caution.
+ * @return RingbufHandle_t Ringbuffer handle, nullptr if the Ringbuffer is not created.
+ */
+ RingbufHandle_t getHandle() const override { return Policy::getHandle(); }
+
+ /**
+ * @brief Check if the ringbuffer is created.
+ * @return true Ringbuffer is created.
+ */
+ bool isCreated() const override { return Policy::isCreated(); }
+
+ /**
+ * @brief Get the maximum size of an item that can be placed in the ring buffer.
+ * @return size_t Maximum size, in bytes, of an item that can be placed in a ring buffer.
+ */
+ size_t getMaxItemSize() const override {
+ if (!isCreated()) return 0;
+ return xRingbufferGetMaxItemSize(getHandle());
+ }
+
+ /**
+ * @brief Get the current free size available for an item/data in the buffer.
+ * @return size_t Free size available for an item/data in the buffer.
+ */
+ size_t getFreeSize() const override {
+ if (!isCreated()) return 0;
+ return xRingbufferGetCurFreeSize(getHandle());
+ }
+
+ /**
+ * @brief Check if the ringbuffer is created.
+ * @return true Ringbuffer is created.
+ */
+ explicit operator bool() const override { return isCreated(); }
+};
+
+} // namespace Internal
+
+template
+using RingBufferNoSplitDynamic = Internal::RingBuffer>;
+
+template
+using RingBufferNoSplitStatic = Internal::RingBuffer>;
+
+template
+using RingBufferNoSplitExternalStorage =
+ Internal::RingBuffer>;
+
+template
+using RingBufferSplitDynamic = Internal::RingBuffer>;
+
+template
+using RingBufferSplitStatic = Internal::RingBuffer>;
+
+template
+using RingBufferSplitExternalStorage =
+ Internal::RingBuffer>;
+
+template
+using RingBufferByteDynamic = Internal::RingBuffer>;
+
+template
+using RingBufferByteStatic = Internal::RingBuffer>;
+
+template
+using RingBufferByteExternalStorage =
+ Internal::RingBuffer>;
+
+} // namespace RTOS::RingBuffers
\ No newline at end of file
diff --git a/src/RTOScppTask.h b/src/RTOScppTask.h
index 8c791c6..ee1a483 100644
--- a/src/RTOScppTask.h
+++ b/src/RTOScppTask.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,134 +7,679 @@
#pragma once
#include
+
#include
-class TaskInterface {
+namespace RTOS::Tasks {
+
+// Interface for Task objects, useful when using pointers
+class ITask {
protected:
- TaskInterface(const char* name, const TaskFunction_t function, const uint8_t priority,
- const uint32_t stack_size, const BaseType_t running_core)
- : _name(name)
- , _function(function)
- , _priority(priority)
- , _stack_size(stack_size)
- , _running_core(running_core)
- , _handle(nullptr)
- , _stack_used(0)
- , _stack_min(0xffffffff)
- , _stack_max(0) {}
+ ITask() = default;
+
+ public:
+ ITask(const ITask&) = delete;
+ ITask& operator=(const ITask&) = delete;
+ ITask(ITask&&) = delete;
+ ITask& operator=(ITask&&) = delete;
+ virtual ~ITask() = default;
+
+ /**
+ * @brief Get the low-level handle of the task. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return TaskHandle_t Task handle, nullptr if the task is not created.
+ */
+ virtual TaskHandle_t getHandle() const = 0;
+
+ /**
+ * @brief Create the task with the already set parameters.
+ * @return true Task created.
+ */
+ virtual bool create() = 0;
+
+ /**
+ * @brief Create the task with the specified parameters.
+ * @param name Task name
+ * @param function Task function
+ * @param priority Task priority (0 to configMAX_PRIORITIES - 1)
+ * @param parameters Task parameters
+ * @param running_core Core where the task will run (0 or 1)
+ * @return true Task created.
+ */
+ virtual bool create(const char* name, TaskFunction_t function, uint8_t priority,
+ void* parameters = nullptr, uint8_t running_core = ARDUINO_RUNNING_CORE) = 0;
+
+ /**
+ * @brief Check if the task is created.
+ * @return true Task is created.
+ */
+ virtual bool isCreated() const = 0;
+
+ /**
+ * @brief Suspend the task.
+ * @return true Task suspended successfully, false if the task is not created.
+ */
+ virtual bool suspend() const = 0;
+
+ /**
+ * @brief Resume the task.
+ * @return true Task resumed successfully, false if the task is not created.
+ */
+ virtual bool resume() const = 0;
+
+ /**
+ * @brief Get the state of the task.
+ * @return eTaskState Task state, eTaskState::eInvalid if the task is not created.
+ */
+ virtual eTaskState getState() const = 0;
+
+ /**
+ * @brief Abort the delay of the task.
+ * @return true Delay aborted successfully, false if the task is not created or the task is not in
+ * the Blocked state.
+ */
+ virtual bool abortDelay() const = 0;
+
+ /**
+ * @brief Get the name of the task.
+ * @return const char* Task name, nullptr if the task is not created.
+ */
+ virtual const char* getName() const = 0;
+
+ /**
+ * @brief Get the parameters of the task.
+ * @return void* Task parameters, nullptr if the task is not created or no parameters were set.
+ */
+ virtual void* getParameters() const = 0;
+
+ /**
+ * @brief Get the core where the task is running.
+ * @return uint8_t Core number, 0xFF if the task is not created.
+ */
+ virtual uint8_t getCore() const = 0;
+
+ /**
+ * @brief Set the priority of the task.
+ * @param priority Task priority (0 to configMAX_PRIORITIES - 1)
+ * @return true Priority set successfully, false if the task is not created.
+ */
+ virtual bool setPriority(const uint8_t priority) = 0;
+
+ /**
+ * @brief Get the priority of the task.
+ * @return uint8_t Task priority, 0xFF if the task is not created.
+ */
+ virtual uint8_t getPriority() const = 0;
+
+ /**
+ * @brief Get the priority of the task from an ISR.
+ * @return uint8_t Task priority, 0xFF if the task is not created.
+ */
+ virtual uint8_t getPriorityFromISR() const = 0;
+
+ /**
+ * @brief Get the stack size of the task.
+ * @return uint32_t Stack size.
+ */
+ virtual uint32_t getStackSize() const = 0;
+
+ /**
+ * @brief Notify the task.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
+ virtual bool notify(const uint32_t value, const eNotifyAction action) const = 0;
+
+ /**
+ * @brief Notify the task from an ISR.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
+ virtual bool notifyFromISR(const uint32_t value, const eNotifyAction action,
+ BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Notify the task and query the old value.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param old_value Old value.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
+ virtual bool notifyAndQuery(const uint32_t value, const eNotifyAction action,
+ uint32_t& old_value) const = 0;
+
+ /**
+ * @brief Notify the task and query the old value from an ISR.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param old_value Old value.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
+ virtual bool notifyAndQueryFromISR(const uint32_t value, const eNotifyAction action,
+ uint32_t& old_value, BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Notify the task. This function acts as a counting semaphore, it will increment the
+ * notification value by 1. The task can wait for the notification using notifyTake().
+ * @return true Notification sent successfully, false if the task is not created.
+ */
+ virtual bool notifyGive() const = 0;
+
+ /**
+ * @brief Notify the task from an ISR. This function acts as a counting semaphore, it will
+ * increment the notification value by 1. The task can wait for the notification using
+ * notifyTake().
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created.
+ */
+ virtual bool notifyGiveFromISR(BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Wait for a notification. This function will block the task until a notification is
+ * received or the timeout expires.
+ * @param clear Clear the notification. If true, the notification value will be cleared, acting as
+ * a binary semaphore. If false, the notification value will be decremented by 1.
+ * @param ticks_to_wait Maximum time to wait for the notification.
+ * @return uint32_t Value of the notification.
+ */
+ virtual uint32_t notifyTake(const bool clear,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Wait for a notification. This function will block the task until a notification is
+ * received or the timeout expires.
+ * @param clear_on_entry Bit mask to clear the notification on entry.
+ * @param clear_on_exit Bit mask to clear the notification on exit.
+ * @param value Value of the notification.
+ * @param ticks_to_wait Maximum time to wait for the notification.
+ * @return true Notification received successfully, false if the task is not created or failed to
+ * receive the notification.
+ */
+ virtual bool notifyWait(const uint32_t clear_on_entry, const uint32_t clear_on_exit,
+ uint32_t& value, const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Update the stack statistics of the task. Call this function periodically to get the
+ * stack usage.
+ * @return true Stack statistics updated successfully, false if the task is not created.
+ */
+ virtual bool updateStackStats() = 0;
+
+ /**
+ * @brief Get the stack used by the task.
+ * @return uint32_t Stack used.
+ */
+ virtual uint32_t getStackUsed() const = 0;
+ /**
+ * @brief Get the minimum stack used by the task.
+ * @return uint32_t Minimum stack used.
+ */
+ virtual uint32_t getStackMinUsed() const = 0;
+
+ /**
+ * @brief Get the maximum stack used by the task.
+ * @return uint32_t Maximum stack used.
+ */
+ virtual uint32_t getStackMaxUsed() const = 0;
+
+ /**
+ * @brief Check if the task is created.
+ * @return true Task is created.
+ */
+ virtual explicit operator bool() const = 0;
+};
+
+namespace Internal {
+
+// CRTP base policy class
+template
+class Policy {
+ public:
+ Policy()
+ : _handle(nullptr)
+ , _name(nullptr)
+ , _function(nullptr)
+ , _priority(0)
+ , _parameters(nullptr)
+ , _core(0) {}
+
+ TaskHandle_t getHandle() const { return _handle; }
+ const char* getName() const { return _name; }
+ void* getParameters() const { return _parameters; }
+ uint8_t getCore() const { return _core; }
+
+ bool setup(const char* name, TaskFunction_t function, uint8_t priority,
+ void* parameters = nullptr, BaseType_t core = ARDUINO_RUNNING_CORE) {
+ _name = name;
+ _parameters = parameters;
+ _function = function;
+ _priority = priority;
+ _core = core;
+
+ return isValid();
+ }
+
+ bool isValid() const {
+ if (_name == nullptr || _function == nullptr || _priority >= configMAX_PRIORITIES ||
+ !taskVALID_CORE_ID(_core))
+ return false;
+
+ return true;
+ }
+
+ bool create() {
+ if (!isValid()) return false;
+
+ if (isCreated()) return true;
+
+ return static_cast(this)->createImpl();
+ }
+
+ bool isCreated() const { return _handle != nullptr; }
+
+ protected:
+ TaskHandle_t _handle;
const char* _name;
- const TaskFunction_t _function;
+ TaskFunction_t _function;
uint8_t _priority;
- const uint32_t _stack_size;
- const BaseType_t _running_core;
- TaskHandle_t _handle;
- uint32_t _stack_used;
- uint32_t _stack_min;
- uint32_t _stack_max;
+ void* _parameters;
+ uint8_t _core;
+};
+
+// Policy for task with dynamic memory allocation
+template
+class DynamicPolicy : public Policy> {
+ public:
+ static constexpr uint32_t _stack_size = StackSize;
+
+ bool createImpl() {
+ if (!this->isValid()) return false;
+ return xTaskCreatePinnedToCore(this->_function,
+ this->_name,
+ StackSize,
+ this->_parameters,
+ this->_priority,
+ &this->_handle,
+ this->_core);
+ }
+};
+
+// Policy for task with static memory allocation
+template
+class StaticPolicy : public Policy> {
public:
- virtual ~TaskInterface() {
- if (_handle) vTaskDelete(_handle);
+ static constexpr uint32_t _stack_size = StackSize;
+
+ bool createImpl() {
+ if (!this->isValid()) return false;
+
+ this->_handle = xTaskCreateStaticPinnedToCore(this->_function,
+ this->_name,
+ StackSize,
+ this->_parameters,
+ this->_priority,
+ _stack,
+ &_tcb,
+ this->_core);
+
+ return this->_handle != nullptr;
}
- TaskInterface(const TaskInterface&) = delete;
- TaskInterface& operator=(const TaskInterface&) = delete;
- TaskInterface(TaskInterface&&) noexcept = delete;
- TaskInterface& operator=(TaskInterface&&) noexcept = delete;
+ private:
+ StackType_t _stack[StackSize];
+ StaticTask_t _tcb;
+};
- virtual bool init() = 0;
+// Main task class. You need to specify the policy used
+template
+class Task : public ITask {
+ public:
+ Task()
+ : _policy()
+ , _stack_used(0)
+ , _stack_min(0xffffffff)
+ , _stack_max(0) {}
- void suspend() const { vTaskSuspend(_handle); }
- void resume() const { vTaskResume(_handle); }
- bool abortDelay() const { return xTaskAbortDelay(_handle); }
+ Task(const char* name, TaskFunction_t function, uint8_t priority, void* parameters = nullptr,
+ uint8_t running_core = ARDUINO_RUNNING_CORE, bool create = false)
+ : _policy()
+ , _stack_used(0)
+ , _stack_min(0xffffffff)
+ , _stack_max(0) {
+ if (!_policy.setup(name, function, priority, parameters, running_core)) {
+ return;
+ }
- TaskHandle_t getHandle() const { return _handle; }
- const char* getName() const { return _name; }
+ if (create) {
+ _policy.create();
+ }
+ }
+
+ ~Task() {
+ if (getHandle()) {
+ vTaskDelete(getHandle());
+ }
+ }
+
+ /**
+ * @brief Get the low-level handle of the task. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return TaskHandle_t Task handle, nullptr if the task is not created.
+ */
+ TaskHandle_t getHandle() const { return _policy.getHandle(); }
+
+ /**
+ * @brief Create the task with the already set parameters.
+ * @return true Task created.
+ */
+ bool create() { return _policy.create(); }
+
+ /**
+ * @brief Create the task with the specified parameters.
+ * @param name Task name
+ * @param function Task function
+ * @param priority Task priority (0 to configMAX_PRIORITIES - 1)
+ * @param parameters Task parameters
+ * @param running_core Core where the task will run (0 or 1)
+ * @return true Task created.
+ */
+ bool create(const char* name, TaskFunction_t function, uint8_t priority,
+ void* parameters = nullptr, uint8_t running_core = ARDUINO_RUNNING_CORE) {
+ if (!_policy.setup(name, function, priority, parameters, running_core)) {
+ return false;
+ }
+
+ return _policy.create();
+ }
+
+ /**
+ * @brief Check if the task is created.
+ * @return true Task is created.
+ */
+ bool isCreated() const { return _policy.isCreated(); }
+
+ /**
+ * @brief Suspend the task.
+ * @return true Task suspended successfully, false if the task is not created.
+ */
+ bool suspend() const {
+ if (!isCreated()) return false;
+ vTaskSuspend(getHandle());
+ return true;
+ }
- void setPriority(const uint8_t priority) {
- _priority = priority;
- vTaskPrioritySet(_handle, _priority);
+ /**
+ * @brief Resume the task.
+ * @return true Task resumed successfully, false if the task is not created.
+ */
+ bool resume() const {
+ if (!isCreated()) return false;
+ vTaskResume(getHandle());
+ return true;
}
- uint8_t getPriority() const { return uxTaskPriorityGet(_handle); }
- uint8_t getPriorityFromISR() const { return uxTaskPriorityGetFromISR(_handle); }
+ /**
+ * @brief Get the state of the task.
+ * @return eTaskState Task state, eTaskState::eInvalid if the task is not created.
+ */
+ eTaskState getState() const {
+ if (!isCreated()) return eTaskState::eInvalid;
+ return eTaskGetState(getHandle());
+ }
+
+ /**
+ * @brief Abort the delay of the task.
+ * @return true Delay aborted successfully, false if the task is not created or the task is not in
+ * the Blocked state.
+ */
+ bool abortDelay() const {
+ if (!isCreated()) return false;
+ return xTaskAbortDelay(getHandle());
+ }
- uint32_t getStackSize() const { return _stack_size; }
+ /**
+ * @brief Get the name of the task.
+ * @return const char* Task name, nullptr if the task is not created.
+ */
+ const char* getName() const {
+ if (!isCreated()) return nullptr;
+ return _policy.getName();
+ }
- bool notify(const uint32_t value, const eNotifyAction action) {
- return xTaskNotify(_handle, value, action);
+ /**
+ * @brief Get the parameters of the task.
+ * @return void* Task parameters, nullptr if the task is not created or no parameters were set.
+ */
+ void* getParameters() const {
+ if (!isCreated()) return nullptr;
+ return _policy.getParameters();
}
+ /**
+ * @brief Get the core where the task is running.
+ * @return uint8_t Core number, 0xFF if the task is not created.
+ */
+ uint8_t getCore() const {
+ if (!isCreated()) return 0xFF;
+ return _policy.getCore();
+ }
+
+ /**
+ * @brief Set the priority of the task.
+ * @param priority Task priority (0 to configMAX_PRIORITIES - 1)
+ * @return true Priority set successfully, false if the task is not created.
+ */
+ bool setPriority(const uint8_t priority) {
+ if (!isCreated()) return false;
+ vTaskPrioritySet(getHandle(), priority);
+ return true;
+ }
+
+ /**
+ * @brief Get the priority of the task.
+ * @return uint8_t Task priority, 0xFF if the task is not created.
+ */
+ uint8_t getPriority() const {
+ if (!isCreated()) return 0xFF;
+ return uxTaskPriorityGet(getHandle());
+ }
+
+ /**
+ * @brief Get the priority of the task from an ISR.
+ * @return uint8_t Task priority, 0xFF if the task is not created.
+ */
+ uint8_t getPriorityFromISR() const {
+ if (!isCreated()) return 0xFF;
+ return uxTaskPriorityGetFromISR(getHandle());
+ }
+
+ /**
+ * @brief Get the stack size of the task.
+ * @return uint32_t Stack size.
+ */
+ uint32_t getStackSize() const { return Policy::_stack_size; }
+
+ /**
+ * @brief Notify the task.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
+ bool notify(const uint32_t value, const eNotifyAction action) const {
+ if (!isCreated()) return false;
+ return xTaskNotify(getHandle(), value, action);
+ }
+
+ /**
+ * @brief Notify the task from an ISR.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
bool notifyFromISR(const uint32_t value, const eNotifyAction action,
- BaseType_t& task_woken) const {
- return xTaskNotifyFromISR(_handle, value, action, &task_woken);
+ BaseType_t& task_woken) const {
+ if (!isCreated()) return false;
+ return xTaskNotifyFromISR(getHandle(), value, action, &task_woken);
}
+ /**
+ * @brief Notify the task and query the old value.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param old_value Old value.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
bool notifyAndQuery(const uint32_t value, const eNotifyAction action, uint32_t& old_value) const {
- return xTaskNotifyAndQuery(_handle, value, action, &old_value);
+ if (!isCreated()) return false;
+ return xTaskNotifyAndQuery(getHandle(), value, action, &old_value);
}
+ /**
+ * @brief Notify the task and query the old value from an ISR.
+ * @param value Value to notify.
+ * @param action Action to take.
+ * @param old_value Old value.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created or failed to send
+ * the notification.
+ */
bool notifyAndQueryFromISR(const uint32_t value, const eNotifyAction action, uint32_t& old_value,
- BaseType_t& task_woken) const {
- return xTaskNotifyAndQueryFromISR(_handle, value, action, &old_value, &task_woken);
+ BaseType_t& task_woken) const {
+ if (!isCreated()) return false;
+ return xTaskNotifyAndQueryFromISR(getHandle(), value, action, &old_value, &task_woken);
}
- bool notifyWait(const uint32_t clear_on_entry, const uint32_t clear_on_exit,
- uint32_t* const value, const TickType_t ticks_to_wait = portMAX_DELAY) const {
- return xTaskNotifyWait(clear_on_entry, clear_on_exit, value, ticks_to_wait);
+ /**
+ * @brief Notify the task. This function acts as a counting semaphore, it will increment the
+ * notification value by 1. The task can wait for the notification using notifyTake().
+ * @return true Notification sent successfully, false if the task is not created.
+ */
+ bool notifyGive() const {
+ if (!isCreated()) return false;
+ return xTaskNotifyGive(getHandle());
}
- bool notifyGive() const { return xTaskNotifyGive(_handle); }
-
- void notifyGiveFromISR(BaseType_t& task_woken) const {
- vTaskNotifyGiveFromISR(_handle, &task_woken);
+ /**
+ * @brief Notify the task from an ISR. This function acts as a counting semaphore, it will
+ * increment the notification value by 1. The task can wait for the notification using
+ * notifyTake().
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Notification sent successfully, false if the task is not created.
+ */
+ bool notifyGiveFromISR(BaseType_t& task_woken) const {
+ if (!isCreated()) return false;
+ vTaskNotifyGiveFromISR(getHandle(), &task_woken);
+ return true;
}
- uint32_t notifyTake(const bool clear, const TickType_t ticks_to_wait = portMAX_DELAY) const {
+ /**
+ * @brief Wait for a notification. This function will block the task until a notification is
+ * received or the timeout expires.
+ * @param clear Clear the notification. If true, the notification value will be cleared, acting as
+ * a binary semaphore. If false, the notification value will be decremented by 1.
+ * @param ticks_to_wait Maximum time to wait for the notification.
+ * @return uint32_t Value of the notification.
+ */
+ uint32_t notifyTake(const bool clear, const TickType_t ticks_to_wait) const {
+ if (!isCreated()) return 0;
return ulTaskNotifyTake(clear, ticks_to_wait);
}
- void updateStackStats() {
- _stack_used = _stack_size - uxTaskGetStackHighWaterMark(_handle);
- _stack_min = min(_stack_min, _stack_used);
- _stack_max = max(_stack_max, _stack_used);
+ /**
+ * @brief Wait for a notification. This function will block the task until a notification is
+ * received or the timeout expires.
+ * @param clear_on_entry Bit mask to clear the notification on entry.
+ * @param clear_on_exit Bit mask to clear the notification on exit.
+ * @param value Value of the notification.
+ * @param ticks_to_wait Maximum time to wait for the notification.
+ * @return true Notification received successfully, false if the task is not created or failed to
+ * receive the notification.
+ */
+ bool notifyWait(const uint32_t clear_on_entry, const uint32_t clear_on_exit, uint32_t& value,
+ const TickType_t ticks_to_wait) const {
+ if (!isCreated()) return false;
+ return xTaskNotifyWait(clear_on_entry, clear_on_exit, &value, ticks_to_wait);
}
- uint32_t getStackUsed() const { return _stack_used; }
- uint32_t getStackMinUsed() const { return _stack_min; }
- uint32_t getStackMaxUsed() const { return _stack_max; }
-
- explicit operator bool() const { return _handle != nullptr; }
-};
+ /**
+ * @brief Update the stack statistics of the task. Call this function periodically to get the
+ * stack usage.
+ * @return true Stack statistics updated successfully, false if the task is not created.
+ */
+ bool updateStackStats() {
+ if (!isCreated()) return false;
-class TaskDynamic : public TaskInterface {
- public:
- TaskDynamic(const char* name, TaskFunction_t function, uint8_t priority, uint32_t stack_size,
- BaseType_t running_core = ARDUINO_RUNNING_CORE)
- : TaskInterface(name, function, priority, stack_size, running_core) {}
+ _stack_used = Policy::_stack_size - uxTaskGetStackHighWaterMark(getHandle());
+ _stack_min = min(_stack_min, _stack_used);
+ _stack_max = max(_stack_max, _stack_used);
- bool init() {
- return xTaskCreatePinnedToCore(
- _function, _name, _stack_size, nullptr, _priority, &_handle, _running_core) == pdPASS;
+ return true;
}
-};
-template
-class TaskStatic : public TaskInterface {
- public:
- TaskStatic(const char* name, TaskFunction_t function, uint8_t priority,
- BaseType_t running_core = ARDUINO_RUNNING_CORE)
- : TaskInterface(name, function, priority, STACK_SIZE, running_core) {}
+ /**
+ * @brief Get the stack used by the task.
+ * @return uint32_t Stack used.
+ */
+ uint32_t getStackUsed() const {
+ if (!isCreated()) return 0;
+ return _stack_used;
+ }
- bool init() {
- _handle = xTaskCreateStaticPinnedToCore(
- _function, _name, _stack_size, nullptr, _priority, _stack, &_tcb, _running_core);
+ /**
+ * @brief Get the minimum stack used by the task.
+ * @return uint32_t Minimum stack used.
+ */
+ uint32_t getStackMinUsed() const {
+ if (!isCreated()) return 0;
+ return _stack_min;
+ }
- return _handle != nullptr;
+ /**
+ * @brief Get the maximum stack used by the task.
+ * @return uint32_t Maximum stack used.
+ */
+ uint32_t getStackMaxUsed() const {
+ if (!isCreated()) return 0;
+ return _stack_max;
}
+ /**
+ * @brief Check if the task is created.
+ * @return true Task is created.
+ */
+ explicit operator bool() const { return isCreated(); }
+
private:
- StaticTask_t _tcb;
- StackType_t _stack[STACK_SIZE];
-};
\ No newline at end of file
+ Policy _policy;
+ uint32_t _stack_used;
+ uint32_t _stack_min;
+ uint32_t _stack_max;
+};
+}; // namespace Internal
+
+template
+using TaskDynamic = Internal::Task>;
+
+template
+using TaskStatic = Internal::Task>;
+} // namespace RTOS::Tasks
\ No newline at end of file
diff --git a/src/RTOScppTimer.h b/src/RTOScppTimer.h
index f60a366..7d66a3c 100644
--- a/src/RTOScppTimer.h
+++ b/src/RTOScppTimer.h
@@ -1,5 +1,5 @@
/**
- * SPDX-FileCopyrightText: 2024 Maximiliano Ramirez
+ * SPDX-FileCopyrightText: 2025 Maximiliano Ramirez
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -7,130 +7,460 @@
#pragma once
#include
+
#include
-class TimerInterface {
+namespace RTOS::Timers {
+
+// Interface for Timer objects, useful when using pointers
+class ITimer {
protected:
- TimerInterface(TimerHandle_t handle)
- : _handle(handle) {}
+ ITimer() = default;
+
+ public:
+ ITimer(const ITimer&) = delete;
+ ITimer& operator=(const ITimer&) = delete;
+ ITimer(ITimer&&) = delete;
+ ITimer& operator=(ITimer&&) = delete;
+ virtual ~ITimer() = default;
+
+ /**
+ * @brief Get the low-level handle of the timer. Useful for direct FreeRTOS API calls. Use it with
+ * caution.
+ * @return TimerHandle_t Timer handle, nullptr if the timer is not created.
+ */
+ virtual TimerHandle_t getHandle() const = 0;
+
+ /**
+ * @brief Create the timer with the specified parameters.
+ * @param name Timer name
+ * @param callback Timer callback function
+ * @param period Timer period in ticks (can't be 0)
+ * @param id Timer ID (nullptr if not used)
+ * @param auto_reload Timer auto reload mode
+ * @param start Start the timer after creation
+ * @return true Timer created.
+ */
+ virtual bool create(const char* name, TimerCallbackFunction_t callback, const TickType_t period,
+ void* id, const bool auto_reload, const bool start) = 0;
+
+ /**
+ * @brief Check if the timer is created.
+ * @return true Timer is created.
+ */
+ virtual bool isCreated() const = 0;
+
+ /**
+ * @brief Start the timer.
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Timer started successfully, false if the timer is not created or failed to start.
+ */
+ virtual bool start(const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Start the timer from an ISR.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Timer started successfully, false if the timer is not created or failed to start.
+ */
+ virtual bool startFromISR(BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Stop the timer.
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Timer stopped successfully, false if the timer is not created or failed to stop.
+ */
+ virtual bool stop(const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Stop the timer from an ISR.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Timer stopped successfully, false if the timer is not created or failed to stop.
+ */
+ virtual bool stopFromISR(BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Check if the timer is active.
+ * @return true Timer is active.
+ */
+ virtual bool isActive() const = 0;
+
+ /**
+ * @brief Reset the timer.
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Timer reset successfully, false if the timer is not created or failed to reset.
+ */
+ virtual bool reset(const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Reset the timer from an ISR.
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Timer reset successfully, false if the timer is not created or failed to reset.
+ */
+ virtual bool resetFromISR(BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Get the timer name.
+ * @return const char* Timer name, nullptr if the timer is not created.
+ */
+ virtual const char* getName() const = 0;
+
+ /**
+ * @brief Get the time left for the timer to expire.
+ * @return TickType_t Time left for the timer to expire, 0 if the timer is not created.
+ */
+ virtual TickType_t getExpiryTime() const = 0;
+
+ /**
+ * @brief Set the timer period.
+ * @param period Timer period in ticks (can't be 0)
+ * @param ticks_to_wait Maximum time to wait for the operation to complete.
+ * @return true Timer period set successfully, false if the timer is not created or failed to set
+ * the period.
+ */
+ virtual bool setPeriod(const TickType_t period,
+ const TickType_t ticks_to_wait = portMAX_DELAY) const = 0;
+
+ /**
+ * @brief Set the timer period from an ISR.
+ * @param period Timer period in ticks (can't be 0)
+ * @param task_woken Task woken flag. If true, you need to use portYIELD_FROM_ISR() at the end of
+ * the ISR.
+ * @return true Timer period set successfully, false if the timer is not created or failed to set
+ * the period.
+ */
+ virtual bool setPeriodFromISR(const TickType_t period, BaseType_t& task_woken) const = 0;
+
+ /**
+ * @brief Get the timer period.
+ * @return TickType_t Timer period in ticks, 0 if the timer is not created.
+ */
+ virtual TickType_t getPeriod() const = 0;
+ /**
+ * @brief Set the timer ID.
+ * @param id Timer ID.
+ * @return true Timer ID set successfully, false if the timer is not created.
+ */
+ virtual bool setTimerID(void* id) const = 0;
+
+ /**
+ * @brief Get the timer ID.
+ * @return void* Timer ID, nullptr if the timer is not created.
+ */
+ virtual void* getTimerID() const = 0;
+
+ /**
+ * @brief Set the timer reload mode.
+ * @param auto_reload Timer auto reload mode.
+ * @return true Timer reload mode set successfully, false if the timer is not created.
+ */
+ virtual bool setReloadMode(const bool auto_reload) const = 0;
+
+ /**
+ * @brief Get the timer reload mode.
+ * @return bool Timer auto reload mode, false if the timer is not created.
+ */
+ virtual bool getReloadMode() const = 0;
+
+ /**
+ * @brief Check if the timer is created.
+ * @return true Timer is created.
+ */
+ virtual explicit operator bool() const = 0;
+};
+
+namespace Internal {
+
+// CRTP base class
+template
+class Policy {
+ public:
+ Policy()
+ : _handle(nullptr) {}
+
+ TimerHandle_t getHandle() const { return _handle; }
+
+ bool create(const char* name, TimerCallbackFunction_t callback, const TickType_t period, void* id,
+ const bool auto_reload, const bool start) {
+ if (_handle != nullptr) return true;
+
+ if (name == nullptr || callback == nullptr || period == 0) return false;
+
+ return static_cast(this)->createImpl(name, callback, period, id, auto_reload, start);
+ }
+
+ protected:
TimerHandle_t _handle;
- const char* _name;
- TimerCallbackFunction_t _callback;
- TickType_t _period;
- void* _id;
- bool _auto_reload;
- bool _start;
+};
+
+// Policy for timer with dynamic memory allocation
+class DynamicPolicy : public Policy {
+ public:
+ bool createImpl(const char* name, TimerCallbackFunction_t callback, const TickType_t period,
+ void* id, const bool auto_reload, const bool start) {
+ this->_handle = xTimerCreate(name, period, auto_reload, id, callback);
+
+ if (this->_handle == nullptr) return false;
+ if (start) xTimerStart(this->_handle, 0);
+
+ return true;
+ }
+};
+
+// Policy for timer with static memory allocation
+class StaticPolicy : public Policy {
public:
- virtual ~TimerInterface() {
- if (_handle) vTaskDelete(_handle);
+ bool createImpl(const char* name, TimerCallbackFunction_t callback, const TickType_t period,
+ void* id, const bool auto_reload, const bool start) {
+ this->_handle = xTimerCreateStatic(name, period, auto_reload, id, callback, &_timer_buffer);
+
+ if (this->_handle == nullptr) return false;
+
+ if (start) xTimerStart(this->_handle, 0);
+
+ return true;
}
- TimerInterface(const TimerInterface&) = delete;
- TimerInterface& operator=(const TimerInterface&) = delete;
- TimerInterface(TimerInterface&&) noexcept = delete;
- TimerInterface& operator=(TimerInterface&&) noexcept = delete;
+ private:
+ StaticTimer_t _timer_buffer;
+};
- void setup(const char* name, const TimerCallbackFunction_t callback, const TickType_t period,
- void* id, const bool auto_reload, const bool start) {
- _name = name;
- _callback = callback;
- _period = period;
- _id = id;
- _auto_reload = auto_reload;
- _start = start;
+// Main Timer class. You need to specify the policy used
+template