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 +

+ Logo +
+ RTOScppESP32 +

+ +

+ Make your real-time application development a breeze! +

+ +[![arduino-library-badge](https://www.ardu-badge.com/badge/RTOScppESP32.svg?)](https://www.ardu-badge.com/RTOScppESP32) +[![PlatformIO +Registry](https://badges.registry.platformio.org/packages/alkonosst/library/RTOScppESP32.svg)](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 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 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 sem_counting_1; // Dynamic version +SemCountingStatic sem_counting_2; // Static version +``` + +### Using queues + +```cpp +// Dynamic version +QueueDynamic queue_1; + +// Static version +QueueStatic queue_2; + +// External version +QueueExternal 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 stream_buffer_1; // Dynamic version +StreamBufferStatic stream_buffer_2; // Static version +StreamBufferExternalStorage stream_buffer_3; // External version + +// Message buffers +MessageBufferDynamic message_buffer_1; // Dynamic version +MessageBufferStatic message_buffer_2; // Static version +MessageBufferExternalStorage 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 ring_buffer_no_split_1; // Dynamic version +RingBufferNoSplitStatic ring_buffer_no_split_2; // Static version +RingBufferNoSplitExternalStorage ring_buffer_no_split_3; // External version + +// Split ring buffers +RingBufferSplitDynamic ring_buffer_split_1; // Dynamic version +RingBufferSplitStatic ring_buffer_split_2; // Static version +RingBufferSplitExternalStorage ring_buffer_split_3; // External version + +// Byte ring buffers +RingBufferByteDynamic ring_buffer_byte_1; // Dynamic version +RingBufferByteStatic ring_buffer_byte_2; // Static version +RingBufferByteExternalStorage 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 +class Timer : public ITimer { + public: + /** + * @brief Instantiate a empty Timer object. You need to call create(parameters) before using it. + */ + Timer() = default; + + /** + * @brief Instantiate a Timer object 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 + */ + Timer(const char* name, TimerCallbackFunction_t callback, const TickType_t period, void* id, + const bool auto_reload, const bool start) { + _policy.create(name, callback, period, id, auto_reload, start); + } + + ~Timer() { + if (getHandle()) xTimerDelete(getHandle(), portMAX_DELAY); } - virtual bool create() = 0; + /** + * @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. + */ + TimerHandle_t getHandle() const { return _policy.getHandle(); } + + /** + * @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. + */ + bool create(const char* name, TimerCallbackFunction_t callback, const TickType_t period, void* id, + const bool auto_reload, const bool start) { + return _policy.create(name, callback, period, id, auto_reload, start); + } + /** + * @brief Check if the timer is created. + * @return true Timer is created. + */ + bool isCreated() const { return getHandle() != nullptr; } + + /** + * @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. + */ bool start(const TickType_t ticks_to_wait = portMAX_DELAY) const { - return xTimerStart(_handle, ticks_to_wait); + if (!isCreated()) return false; + return xTimerStart(getHandle(), ticks_to_wait); } + /** + * @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. + */ bool startFromISR(BaseType_t& task_woken) const { - return xTimerStartFromISR(_handle, &task_woken); + if (!isCreated()) return false; + return xTimerStartFromISR(getHandle(), &task_woken); } + + /** + * @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. + */ bool stop(const TickType_t ticks_to_wait = portMAX_DELAY) const { - return xTimerStop(_handle, ticks_to_wait); + if (!isCreated()) return false; + return xTimerStop(getHandle(), ticks_to_wait); + } + + /** + * @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. + */ + bool stopFromISR(BaseType_t& task_woken) const { + if (!isCreated()) return false; + return xTimerStopFromISR(getHandle(), &task_woken); } - bool stopFromISR(BaseType_t& task_woken) const { return xTimerStopFromISR(_handle, &task_woken); } - bool isActive() const { return xTimerIsTimerActive(_handle); } + /** + * @brief Check if the timer is active. + * @return true Timer is active. + */ + bool isActive() const { + if (!isCreated()) return false; + return xTimerIsTimerActive(getHandle()); + } + /** + * @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. + */ bool reset(const TickType_t ticks_to_wait = portMAX_DELAY) const { - return xTimerReset(_handle, ticks_to_wait); + if (!isCreated()) return false; + return xTimerReset(getHandle(), ticks_to_wait); } + /** + * @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. + */ bool resetFromISR(BaseType_t& task_woken) const { - return xTimerResetFromISR(_handle, &task_woken); + if (!isCreated()) return false; + return xTimerResetFromISR(getHandle(), &task_woken); } - const char* getName() const { return pcTimerGetName(_handle); } - TickType_t getExpiryTime() const { return xTimerGetExpiryTime(_handle); } + /** + * @brief Get the timer name. + * @return const char* Timer name, nullptr if the timer is not created. + */ + const char* getName() const { + if (!isCreated()) return nullptr; + return pcTimerGetName(getHandle()); + } + /** + * @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. + */ + TickType_t getExpiryTime() const { + if (!isCreated()) return 0; + return xTimerGetExpiryTime(getHandle()) - xTaskGetTickCount(); + } + + /** + * @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. + */ bool setPeriod(const TickType_t period, const TickType_t ticks_to_wait = portMAX_DELAY) const { - return xTimerChangePeriod(_handle, period, ticks_to_wait); + if (!isCreated()) return false; + return xTimerChangePeriod(getHandle(), period, ticks_to_wait); } + /** + * @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. + */ bool setPeriodFromISR(const TickType_t period, BaseType_t& task_woken) const { - return xTimerChangePeriodFromISR(_handle, period, &task_woken); + if (!isCreated()) return false; + return xTimerChangePeriodFromISR(getHandle(), period, &task_woken); } - TickType_t getPeriod() const { return xTimerGetPeriod(_handle); } - - void setTimerID() const { vTimerSetTimerID(_handle, _id); } - void* getTimerID() const { return pvTimerGetTimerID(_handle); } - - void setReloadMode(const bool auto_reload) const { vTimerSetReloadMode(_handle, auto_reload); } - bool getReloadMode() const { return uxTimerGetReloadMode(_handle); } - - explicit operator bool() const { return _handle != nullptr; } -}; + /** + * @brief Get the timer period. + * @return TickType_t Timer period in ticks, 0 if the timer is not created. + */ + TickType_t getPeriod() const { + if (!isCreated()) return 0; + return xTimerGetPeriod(getHandle()); + } -class TimerDynamic : public TimerInterface { - public: - TimerDynamic() - : TimerInterface(nullptr) {} + /** + * @brief Set the timer ID. + * @param id Timer ID. + * @return true Timer ID set successfully, false if the timer is not created. + */ + bool setTimerID(void* id) const { + if (!isCreated()) return false; + vTimerSetTimerID(getHandle(), id); + return true; + } - TimerDynamic(const char* name, const TimerCallbackFunction_t callback, const TickType_t period, - void* id, const bool auto_reload, const bool start) - : TimerInterface(xTimerCreate(name, period, auto_reload, id, callback)) { - this->setup(name, callback, period, id, auto_reload, start); + /** + * @brief Get the timer ID. + * @return void* Timer ID, nullptr if the timer is not created. + */ + void* getTimerID() const { + if (!isCreated()) return nullptr; + return pvTimerGetTimerID(getHandle()); + } - if ((_handle != nullptr) && start) { - this->start(); - } + /** + * @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. + */ + bool setReloadMode(const bool auto_reload) const { + if (!isCreated()) return false; + vTimerSetReloadMode(getHandle(), auto_reload); + return true; } - bool create() { - _handle = xTimerCreate(_name, _period, _auto_reload, _id, _callback); - if (_handle == nullptr) return false; - return _start ? this->start() : true; + /** + * @brief Get the timer reload mode. + * @return bool Timer auto reload mode, false if the timer is not created. + */ + bool getReloadMode() const { + if (!isCreated()) return false; + return uxTimerGetReloadMode(getHandle()); } -}; -class TimerStatic : public TimerInterface { - public: - TimerStatic() - : TimerInterface(nullptr) {} + /** + * @brief Check if the timer is created. + * @return true Timer is created. + */ + explicit operator bool() const { return isCreated(); } - TimerStatic(const char* name, const TimerCallbackFunction_t callback, const TickType_t period, - void* id, const bool auto_reload, const bool start) - : TimerInterface(xTimerCreateStatic(name, period, auto_reload, id, callback, &_tcb)) { - this->setup(name, callback, period, id, auto_reload, start); + private: + Policy _policy; +}; - if ((_handle != nullptr) && start) { - this->start(); - } - } +} // namespace Internal - bool create() { - _handle = xTimerCreateStatic(_name, _period, _auto_reload, _id, _callback, &_tcb); - if (_handle == nullptr) return false; - return _start ? this->start() : true; - } +using TimerDynamic = Internal::Timer; +using TimerStatic = Internal::Timer; - private: - StaticTimer_t _tcb; -}; \ No newline at end of file +} // namespace RTOS::Timers \ No newline at end of file diff --git a/test/test_buffers/test_buffers.cpp b/test/test_buffers/test_buffers.cpp new file mode 100644 index 0000000..40c6a7c --- /dev/null +++ b/test/test_buffers/test_buffers.cpp @@ -0,0 +1,181 @@ +#include + +#include "RTOScppBuffer.h" + +using namespace RTOS::Buffers; +static constexpr const char* tag = "test_buffers"; + +/* ---------------------------------------- Data Buffers ---------------------------------------- */ +static char tx_buffer[] = "123456789"; +static constexpr uint8_t tx_len = sizeof(tx_buffer); +static char rx_buffer[tx_len]; // Extra byte for null terminator + +static constexpr uint32_t buffer_size = 100; +static constexpr uint32_t trigger_bytes = 5; + +StreamBufferDynamic sb_dyn; +StreamBufferStatic sb_st; +StreamBufferExternalStorage sb_ext; + +MessageBufferDynamic mb_dyn; +MessageBufferStatic mb_st; +MessageBufferExternalStorage mb_ext; +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_sb_creation() { + TEST_ASSERT_TRUE(sb_dyn); + TEST_ASSERT_TRUE(sb_st); + + static uint8_t* ext_buffer = static_cast(malloc(sb_ext.REQUIRED_SIZE)); + + TEST_ASSERT_NOT_NULL(ext_buffer); + TEST_ASSERT_TRUE(sb_ext.create(ext_buffer)); +} + +void test_sb_send_receive() { + TEST_ASSERT_EQUAL(tx_len, sb_dyn.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(sb_dyn.isFull()); + TEST_ASSERT_FALSE(sb_dyn.isEmpty()); + TEST_ASSERT_EQUAL(tx_len, sb_dyn.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len, sb_dyn.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, sb_dyn.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(sb_dyn.isEmpty()); + + TEST_ASSERT_EQUAL(tx_len, sb_st.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(sb_st.isFull()); + TEST_ASSERT_FALSE(sb_st.isEmpty()); + TEST_ASSERT_EQUAL(tx_len, sb_st.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len, sb_st.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, sb_st.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(sb_st.isEmpty()); + + TEST_ASSERT_EQUAL(tx_len, sb_ext.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(sb_ext.isFull()); + TEST_ASSERT_FALSE(sb_ext.isEmpty()); + TEST_ASSERT_EQUAL(tx_len, sb_ext.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len, sb_ext.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, sb_ext.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(sb_ext.isEmpty()); +} + +void test_sb_send_receive_less_than_trigger() { + static constexpr uint8_t bytes = 2; + + TEST_ASSERT_TRUE(sb_dyn.reset()); + TEST_ASSERT_EQUAL(0, sb_dyn.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_dyn.send(tx_buffer, bytes)); + TEST_ASSERT_EQUAL(bytes, sb_dyn.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_dyn.receive(rx_buffer, tx_len)); + + TEST_ASSERT_TRUE(sb_st.reset()); + TEST_ASSERT_EQUAL(0, sb_st.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_st.send(tx_buffer, bytes)); + TEST_ASSERT_EQUAL(bytes, sb_st.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_st.receive(rx_buffer, tx_len)); + + TEST_ASSERT_TRUE(sb_ext.reset()); + TEST_ASSERT_EQUAL(0, sb_ext.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_ext.send(tx_buffer, bytes)); + TEST_ASSERT_EQUAL(bytes, sb_ext.getAvailableBytes()); + TEST_ASSERT_EQUAL(bytes, sb_ext.receive(rx_buffer, tx_len)); +} + +void test_sb_change_trigger_level() { + static constexpr uint8_t new_trigger = 2; + + TEST_ASSERT_TRUE(sb_dyn.setTriggerLevel(new_trigger)); + TEST_ASSERT_TRUE(sb_st.setTriggerLevel(new_trigger)); + TEST_ASSERT_TRUE(sb_ext.setTriggerLevel(new_trigger)); + + static constexpr uint32_t bad_trigger = buffer_size * 2; + + TEST_ASSERT_FALSE(sb_dyn.setTriggerLevel(bad_trigger)); + TEST_ASSERT_FALSE(sb_st.setTriggerLevel(bad_trigger)); + TEST_ASSERT_FALSE(sb_ext.setTriggerLevel(bad_trigger)); +} + +void test_mb_creation() { + TEST_ASSERT_TRUE(mb_dyn); + TEST_ASSERT_TRUE(mb_st); + + static uint8_t* ext_buffer = static_cast(malloc(mb_ext.REQUIRED_SIZE)); + + TEST_ASSERT_NOT_NULL(ext_buffer); + TEST_ASSERT_TRUE(mb_ext.create(ext_buffer)); +} + +void test_mb_send_receive() { + static constexpr uint32_t tx_len_plus_size = tx_len + sizeof(uint32_t); + + TEST_ASSERT_EQUAL(tx_len, mb_dyn.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(mb_dyn.isFull()); + TEST_ASSERT_FALSE(mb_dyn.isEmpty()); + TEST_ASSERT_EQUAL(tx_len_plus_size, mb_dyn.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len_plus_size, mb_dyn.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, mb_dyn.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(mb_dyn.isEmpty()); + + TEST_ASSERT_EQUAL(tx_len, mb_st.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(mb_st.isFull()); + TEST_ASSERT_FALSE(mb_st.isEmpty()); + TEST_ASSERT_EQUAL(tx_len_plus_size, mb_st.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len_plus_size, mb_st.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, mb_st.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(mb_st.isEmpty()); + + TEST_ASSERT_EQUAL(tx_len, mb_ext.send(tx_buffer, tx_len)); + TEST_ASSERT_FALSE(mb_ext.isFull()); + TEST_ASSERT_FALSE(mb_ext.isEmpty()); + TEST_ASSERT_EQUAL(tx_len_plus_size, mb_ext.getAvailableBytes()); + TEST_ASSERT_EQUAL(buffer_size - tx_len_plus_size, mb_ext.getAvailableSpaces()); + TEST_ASSERT_EQUAL(tx_len, mb_ext.receive(rx_buffer, tx_len)); + TEST_ASSERT_EQUAL_STRING(tx_buffer, rx_buffer); + TEST_ASSERT_TRUE(mb_ext.isEmpty()); +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_sb_creation); + RUN_TEST(test_sb_send_receive); + RUN_TEST(test_sb_send_receive_less_than_trigger); + RUN_TEST(test_sb_change_trigger_level); + RUN_TEST(test_mb_creation); + RUN_TEST(test_mb_send_receive); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_locks/test_locks.cpp b/test/test_locks/test_locks.cpp new file mode 100644 index 0000000..1a7d636 --- /dev/null +++ b/test/test_locks/test_locks.cpp @@ -0,0 +1,155 @@ +#include +#include + +#include "RTOScppLock.h" + +#include + +using namespace RTOS::Locks; +static constexpr const char* tag = "test_locks"; + +/* -------------------------------------------- Locks ------------------------------------------- */ +MutexDynamic mutex_dyn; +MutexStatic mutex_st; +MutexRecursiveDynamic mutex_rec_dyn; +MutexRecursiveStatic mutex_rec_st; +SemBinaryDynamic sem_bin_dyn; +SemBinaryStatic sem_bin_st; + +static constexpr uint8_t sem_count = 2; +SemCountingDynamic sem_count_dyn; +SemCountingStatic sem_count_st; + +static constexpr uint8_t take_wait = 10; +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_locks_creation() { + TEST_ASSERT_TRUE(mutex_dyn); + TEST_ASSERT_TRUE(mutex_dyn.isCreated()); + + TEST_ASSERT_TRUE(mutex_st); + TEST_ASSERT_TRUE(mutex_st.isCreated()); + + TEST_ASSERT_TRUE(mutex_rec_dyn); + TEST_ASSERT_TRUE(mutex_rec_dyn.isCreated()); + + TEST_ASSERT_TRUE(mutex_rec_st); + TEST_ASSERT_TRUE(mutex_rec_st.isCreated()); + + TEST_ASSERT_TRUE(sem_bin_dyn); + TEST_ASSERT_TRUE(sem_bin_dyn.isCreated()); + + TEST_ASSERT_TRUE(sem_bin_st); + TEST_ASSERT_TRUE(sem_bin_st.isCreated()); + + TEST_ASSERT_TRUE(sem_count_dyn); + TEST_ASSERT_TRUE(sem_count_dyn.isCreated()); + + TEST_ASSERT_TRUE(sem_count_st); + TEST_ASSERT_TRUE(sem_count_st.isCreated()); +} + +void test_mutex() { + TEST_ASSERT_TRUE(mutex_dyn.take(take_wait)); + TEST_ASSERT_TRUE(mutex_st.take(take_wait)); + + TEST_ASSERT_TRUE(mutex_dyn.give()); + TEST_ASSERT_TRUE(mutex_st.give()); +} + +void test_mutex_recursive() { + TEST_ASSERT_TRUE(mutex_rec_dyn.take(take_wait)); + TEST_ASSERT_TRUE(mutex_rec_st.take(take_wait)); + + TEST_ASSERT_TRUE(mutex_rec_dyn.take()); + TEST_ASSERT_TRUE(mutex_rec_st.take()); + + TEST_ASSERT_TRUE(mutex_rec_dyn.give()); + TEST_ASSERT_TRUE(mutex_rec_st.give()); + + TEST_ASSERT_TRUE(mutex_rec_dyn.give()); + TEST_ASSERT_TRUE(mutex_rec_st.give()); +} + +void test_semaphore_binary() { + TEST_ASSERT_TRUE(sem_bin_dyn.give()); + TEST_ASSERT_TRUE(sem_bin_st.give()); + + TEST_ASSERT_TRUE(sem_bin_dyn.take(take_wait)); + TEST_ASSERT_TRUE(sem_bin_st.take(take_wait)); + + TEST_ASSERT_TRUE(sem_bin_dyn.give()); + TEST_ASSERT_TRUE(sem_bin_st.give()); +} + +void test_semaphore_counting() { + UBaseType_t count_dyn = sem_count_dyn.getCount(); + UBaseType_t count_st = sem_count_dyn.getCount(); + + TEST_ASSERT_EQUAL(0, count_dyn); + TEST_ASSERT_EQUAL(0, count_st); + + for (uint8_t i = 0; i < sem_count; i++) { + TEST_ASSERT_TRUE(sem_count_dyn.give()); + TEST_ASSERT_TRUE(sem_count_st.give()); + } + + TEST_ASSERT_TRUE(sem_count_dyn.take(take_wait)); + TEST_ASSERT_TRUE(sem_count_st.take(take_wait)); + + count_dyn = sem_count_dyn.getCount(); + count_st = sem_count_st.getCount(); + + TEST_ASSERT_EQUAL(sem_count - 1, count_dyn); + TEST_ASSERT_EQUAL(sem_count - 1, count_st); + + TEST_ASSERT_TRUE(sem_count_dyn.give()); + TEST_ASSERT_TRUE(sem_count_st.give()); + + count_dyn = sem_count_dyn.getCount(); + count_st = sem_count_st.getCount(); + + TEST_ASSERT_EQUAL(sem_count, count_dyn); + TEST_ASSERT_EQUAL(sem_count, count_st); +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_locks_creation); + RUN_TEST(test_mutex); + RUN_TEST(test_mutex_recursive); + RUN_TEST(test_semaphore_binary); + RUN_TEST(test_semaphore_counting); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_queues/test_queues.cpp b/test/test_queues/test_queues.cpp new file mode 100644 index 0000000..047993f --- /dev/null +++ b/test/test_queues/test_queues.cpp @@ -0,0 +1,244 @@ +#include + +#include "RTOScppQueue.h" + +using namespace RTOS::Queues; +static constexpr const char* tag = "test_queues"; + +/* ------------------------------------------- Queues ------------------------------------------- */ +static constexpr uint32_t queue_size = 3; +static constexpr uint32_t item_to_add = 123456789; + +QueueDynamic q_dyn; +QueueStatic q_st; +QueueExternalStorage q_ext; + +static uint32_t available_msgs; +static uint32_t available_spaces; + +// Queue of length 1 to test overwrite +QueueStatic q_overwrite; +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_queues_creation() { + TEST_ASSERT_TRUE(q_dyn); + TEST_ASSERT_TRUE(q_st); + + static uint8_t* ext_buffer = static_cast(malloc(q_ext.REQUIRED_SIZE)); + TEST_ASSERT_NOT_NULL(ext_buffer); + TEST_ASSERT_TRUE(q_ext.create(ext_buffer)); + + TEST_ASSERT_TRUE(q_overwrite); +} + +void test_queues_full_empty() { + TEST_ASSERT_TRUE(q_dyn.isEmpty()); + TEST_ASSERT_TRUE(q_st.isEmpty()); + TEST_ASSERT_TRUE(q_ext.isEmpty()); + + TEST_ASSERT_FALSE(q_dyn.isFull()); + TEST_ASSERT_FALSE(q_st.isFull()); + TEST_ASSERT_FALSE(q_ext.isFull()); + + for (uint32_t i = 0; i < queue_size; i++) { + TEST_ASSERT_TRUE(q_dyn.add(item_to_add)); + TEST_ASSERT_TRUE(q_st.add(item_to_add)); + TEST_ASSERT_TRUE(q_ext.add(item_to_add)); + } + + TEST_ASSERT_FALSE(q_dyn.isEmpty()); + TEST_ASSERT_FALSE(q_st.isEmpty()); + TEST_ASSERT_FALSE(q_ext.isEmpty()); + + TEST_ASSERT_TRUE(q_dyn.isFull()); + TEST_ASSERT_TRUE(q_st.isFull()); + TEST_ASSERT_TRUE(q_ext.isFull()); + + q_dyn.reset(); + q_st.reset(); + q_ext.reset(); + + TEST_ASSERT_TRUE(q_dyn.isEmpty()); + TEST_ASSERT_TRUE(q_st.isEmpty()); + TEST_ASSERT_TRUE(q_ext.isEmpty()); + + TEST_ASSERT_FALSE(q_dyn.isFull()); + TEST_ASSERT_FALSE(q_st.isFull()); + TEST_ASSERT_FALSE(q_ext.isFull()); +} + +void test_queues_add() { + uint32_t values[queue_size] = {1, 2, 3}; + + for (uint32_t i = 0; i < queue_size; i++) { + TEST_ASSERT_TRUE(q_dyn.add(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_dyn.getAvailableSpaces()); + + TEST_ASSERT_TRUE(q_st.add(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_st.getAvailableSpaces()); + + TEST_ASSERT_TRUE(q_ext.add(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_ext.getAvailableSpaces()); + } + + TEST_ASSERT_TRUE(q_dyn.isFull()); + TEST_ASSERT_TRUE(q_st.isFull()); + TEST_ASSERT_TRUE(q_ext.isFull()); + + for (uint32_t i = 0; i < queue_size; i++) { + uint32_t item_to_pop = 0; + TEST_ASSERT_TRUE(q_dyn.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_dyn.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[i], item_to_pop); + item_to_pop = 0; + + TEST_ASSERT_TRUE(q_st.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_st.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[i], item_to_pop); + item_to_pop = 0; + + TEST_ASSERT_TRUE(q_ext.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_ext.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[i], item_to_pop); + } +} + +void test_queues_push() { + uint32_t values[queue_size] = {1, 2, 3}; + + for (uint32_t i = 0; i < queue_size; i++) { + TEST_ASSERT_TRUE(q_dyn.push(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_dyn.getAvailableSpaces()); + + TEST_ASSERT_TRUE(q_st.push(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_st.getAvailableSpaces()); + + TEST_ASSERT_TRUE(q_ext.push(values[i])); + TEST_ASSERT_EQUAL(i + 1, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_ext.getAvailableSpaces()); + } + + TEST_ASSERT_TRUE(q_dyn.isFull()); + TEST_ASSERT_TRUE(q_st.isFull()); + TEST_ASSERT_TRUE(q_ext.isFull()); + + for (uint32_t i = 0; i < queue_size; i++) { + uint32_t item_to_pop = 0; + TEST_ASSERT_TRUE(q_dyn.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_dyn.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[queue_size - i - 1], item_to_pop); + item_to_pop = 0; + + TEST_ASSERT_TRUE(q_st.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_st.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[queue_size - i - 1], item_to_pop); + item_to_pop = 0; + + TEST_ASSERT_TRUE(q_ext.pop(item_to_pop)); + TEST_ASSERT_EQUAL(queue_size - i - 1, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(i + 1, q_ext.getAvailableSpaces()); + TEST_ASSERT_EQUAL(values[queue_size - i - 1], item_to_pop); + } +} + +void test_queues_peek() { + uint32_t item_to_peek = 0; + + TEST_ASSERT_TRUE(q_dyn.add(item_to_add)); + TEST_ASSERT_TRUE(q_dyn.peek(item_to_peek)); + TEST_ASSERT_EQUAL(1, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - 1, q_dyn.getAvailableSpaces()); + TEST_ASSERT_EQUAL(item_to_add, item_to_peek); + TEST_ASSERT_TRUE(q_dyn.pop(item_to_peek)); + TEST_ASSERT_EQUAL(0, q_dyn.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size, q_dyn.getAvailableSpaces()); + item_to_peek = 0; + + TEST_ASSERT_TRUE(q_st.add(item_to_add)); + TEST_ASSERT_TRUE(q_st.peek(item_to_peek)); + TEST_ASSERT_EQUAL(1, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - 1, q_st.getAvailableSpaces()); + TEST_ASSERT_EQUAL(item_to_add, item_to_peek); + TEST_ASSERT_TRUE(q_st.pop(item_to_peek)); + TEST_ASSERT_EQUAL(0, q_st.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size, q_st.getAvailableSpaces()); + item_to_peek = 0; + + TEST_ASSERT_TRUE(q_ext.add(item_to_add)); + TEST_ASSERT_TRUE(q_ext.peek(item_to_peek)); + TEST_ASSERT_EQUAL(1, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size - 1, q_ext.getAvailableSpaces()); + TEST_ASSERT_EQUAL(item_to_add, item_to_peek); + TEST_ASSERT_TRUE(q_ext.pop(item_to_peek)); + TEST_ASSERT_EQUAL(0, q_ext.getAvailableMessages()); + TEST_ASSERT_EQUAL(queue_size, q_ext.getAvailableSpaces()); + item_to_peek = 0; +} + +void test_queue_overwrite() { + uint32_t item_to_peek = 0; + + TEST_ASSERT_TRUE(q_overwrite.overwrite(1)); + TEST_ASSERT_TRUE(q_overwrite.peek(item_to_peek)); + TEST_ASSERT_EQUAL(1, q_overwrite.getAvailableMessages()); + TEST_ASSERT_EQUAL(0, q_overwrite.getAvailableSpaces()); + TEST_ASSERT_EQUAL(1, item_to_peek); + + TEST_ASSERT_TRUE(q_overwrite.overwrite(2)); + TEST_ASSERT_TRUE(q_overwrite.peek(item_to_peek)); + TEST_ASSERT_EQUAL(1, q_overwrite.getAvailableMessages()); + TEST_ASSERT_EQUAL(0, q_overwrite.getAvailableSpaces()); + TEST_ASSERT_EQUAL(2, item_to_peek); +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_queues_creation); + RUN_TEST(test_queues_full_empty); + RUN_TEST(test_queues_add); + RUN_TEST(test_queues_push); + RUN_TEST(test_queues_peek); + RUN_TEST(test_queue_overwrite); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_queuesets/test_queuesets.cpp b/test/test_queuesets/test_queuesets.cpp new file mode 100644 index 0000000..0a5b510 --- /dev/null +++ b/test/test_queuesets/test_queuesets.cpp @@ -0,0 +1,118 @@ +#include +#include + +#include "RTOScppQueueSet.h" + +using namespace RTOS::QueueSets; +using namespace RTOS::Locks; +using namespace RTOS::Queues; +using namespace RTOS::RingBuffers; +static constexpr const char* tag = "test_queue_set"; + +/* ------------------------------------- Items for Queue Set ------------------------------------ */ +QueueStatic queue; +RingBufferNoSplitStatic rbuffer; // Header + 1 uint32_t element +SemBinaryStatic sem; + +// 3 queue items + 1 ring buffer item + 1 semaphore item +QueueSet q_set(3 + 1 + 1); +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_objects_creation() { + TEST_ASSERT_TRUE(queue); + TEST_ASSERT_TRUE(rbuffer); + TEST_ASSERT_TRUE(sem); + TEST_ASSERT_TRUE(q_set); +} + +void test_add_objects_to_queueset() { + TEST_ASSERT_TRUE(q_set.add(queue)); + TEST_ASSERT_TRUE(q_set.add(rbuffer)); + TEST_ASSERT_TRUE(q_set.add(sem)); +} + +void test_select_queue_from_queueset() { + TEST_ASSERT_TRUE(queue.add(1)); + + auto member = q_set.select(); + TEST_ASSERT_NOT_NULL(member); + TEST_ASSERT_TRUE(member == queue); +} + +void test_select_ringbuffer_from_queueset() { + uint32_t data = 1; + TEST_ASSERT_TRUE(rbuffer.send(&data, sizeof(data))); + + auto member = q_set.select(); + TEST_ASSERT_NOT_NULL(member); + TEST_ASSERT_TRUE(member == rbuffer); +} + +void test_select_semaphore_from_queueset() { + TEST_ASSERT_TRUE(sem.give()); + + auto member = q_set.select(); + TEST_ASSERT_NOT_NULL(member); + TEST_ASSERT_TRUE(member == sem); +} + +void test_remove_objects_from_queueset() { + // Need to empty the queue and ring buffer + queue.reset(); + + size_t size; + uint32_t* data = rbuffer.receive(size); + + TEST_ASSERT_NOT_NULL(data); + rbuffer.returnItem(data); + + TEST_ASSERT_TRUE(sem.take()); + + TEST_ASSERT_TRUE(q_set.remove(queue)); + TEST_ASSERT_TRUE(q_set.remove(rbuffer)); + TEST_ASSERT_TRUE(q_set.remove(sem)); +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + Serial.begin(115200); + delay(2000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_objects_creation); + + RUN_TEST(test_add_objects_to_queueset); + RUN_TEST(test_select_queue_from_queueset); + RUN_TEST(test_select_ringbuffer_from_queueset); + RUN_TEST(test_select_semaphore_from_queueset); + RUN_TEST(test_remove_objects_from_queueset); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_ringbuffers/test_ringbuffers.cpp b/test/test_ringbuffers/test_ringbuffers.cpp new file mode 100644 index 0000000..59b920a --- /dev/null +++ b/test/test_ringbuffers/test_ringbuffers.cpp @@ -0,0 +1,359 @@ +#include +#include + +#include "RTOScppRingBuffer.h" + +using namespace RTOS::RingBuffers; +static constexpr const char* tag = "test_ringbufs"; + +/* ----------------------------------------- RingBuffers ---------------------------------------- */ +static constexpr uint8_t rb_len = 64; + +RingBufferNoSplitDynamic rb_nosp_dyn; +RingBufferNoSplitStatic rb_nosp_st; +RingBufferNoSplitExternalStorage rb_nosp_ext; + +RingBufferSplitDynamic rb_sp_dyn; +RingBufferSplitStatic rb_sp_st; +RingBufferSplitExternalStorage rb_sp_ext; + +RingBufferByteDynamic rb_byte_dyn; +RingBufferByteStatic rb_byte_st; +RingBufferByteExternalStorage rb_byte_ext; +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_rb_creation() { + static uint8_t* rb_nosp_ext_buffer = nullptr; + static uint8_t* rb_sp_ext_buffer = nullptr; + static uint8_t* rb_byte_ext_buffer = nullptr; + + TEST_ASSERT_TRUE(rb_nosp_dyn); + TEST_ASSERT_TRUE(rb_nosp_st); + + rb_nosp_ext_buffer = static_cast(malloc(rb_nosp_ext.REQUIRED_SIZE)); + TEST_ASSERT_NOT_NULL(rb_nosp_ext_buffer); + TEST_ASSERT_TRUE(rb_nosp_ext.create(rb_nosp_ext_buffer)); + + TEST_ASSERT_TRUE(rb_sp_dyn); + TEST_ASSERT_TRUE(rb_sp_st); + + rb_sp_ext_buffer = static_cast(malloc(rb_sp_ext.REQUIRED_SIZE)); + TEST_ASSERT_NOT_NULL(rb_sp_ext_buffer); + TEST_ASSERT_TRUE(rb_sp_ext.create(rb_sp_ext_buffer)); + + TEST_ASSERT_TRUE(rb_byte_dyn); + TEST_ASSERT_TRUE(rb_byte_st); + + rb_byte_ext_buffer = static_cast(malloc(rb_byte_ext.REQUIRED_SIZE)); + TEST_ASSERT_NOT_NULL(rb_byte_ext_buffer); + TEST_ASSERT_TRUE(rb_byte_ext.create(rb_byte_ext_buffer)); +} + +void test_rb_nosplit_send_recv() { + static char item_to_send = 'a'; + static const uint8_t item_to_send_size = sizeof(item_to_send); + + char* item_recv; + size_t item_recv_size; + + TEST_ASSERT_TRUE(rb_nosp_dyn.send(&item_to_send, item_to_send_size)); + item_recv = rb_nosp_dyn.receive(item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(item_to_send, *item_recv); + rb_nosp_dyn.returnItem(item_recv); + + TEST_ASSERT_TRUE(rb_nosp_st.send(&item_to_send, item_to_send_size)); + item_recv = rb_nosp_st.receive(item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(item_to_send, *item_recv); + rb_nosp_st.returnItem(item_recv); + + TEST_ASSERT_TRUE(rb_nosp_ext.send(&item_to_send, item_to_send_size)); + item_recv = rb_nosp_ext.receive(item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(item_to_send, *item_recv); + rb_nosp_ext.returnItem(item_recv); +} + +void test_rb_split_send_recv() { + // Here we will leave a gap on the middle of the buffer, to force the split. + static char small_item[8]; + memset(small_item, 's', sizeof(small_item)); + + static char large_item[20]; + memset(large_item, 'l', sizeof(large_item)); + + static char split_item[20]; + memset(split_item, 'g', sizeof(split_item)); + + char* item_small_head = nullptr; + char* item_small_tail = nullptr; + size_t item_small_head_size = 0; + size_t item_small_tail_size = 0; + + char* item_large_head = nullptr; + char* item_large_tail = nullptr; + size_t item_large_head_size = 0; + size_t item_large_tail_size = 0; + + char* item_split_head = nullptr; + char* item_split_tail = nullptr; + size_t item_split_head_size = 0; + size_t item_split_tail_size = 0; + + // Dynamic version ---------------------------------------------------------- + // Send items to the buffer + TEST_ASSERT_TRUE(rb_sp_dyn.send(small_item, sizeof(small_item))); + TEST_ASSERT_TRUE(rb_sp_dyn.send(large_item, sizeof(large_item))); + + // Receive them + TEST_ASSERT_TRUE(rb_sp_dyn.receive(item_small_head, + item_small_tail, + item_small_head_size, + item_small_tail_size)); + TEST_ASSERT_TRUE(rb_sp_dyn.receive(item_large_head, + item_large_tail, + item_large_head_size, + item_large_tail_size)); + + // Check the small item + TEST_ASSERT_NOT_NULL(item_small_head); + TEST_ASSERT_NULL(item_small_tail); + TEST_ASSERT_EQUAL_MEMORY(small_item, item_small_head, item_small_head_size); + + // Check the large item + TEST_ASSERT_NOT_NULL(item_large_head); + TEST_ASSERT_NULL(item_large_tail); + TEST_ASSERT_EQUAL_MEMORY(large_item, item_large_head, item_large_head_size); + + // Return them + rb_sp_dyn.returnItem(item_small_head); + rb_sp_dyn.returnItem(item_large_head); + + // Add a large item to force the split and fill the ends + TEST_ASSERT_TRUE(rb_sp_dyn.send(split_item, sizeof(split_item))); + + // Receive the split item + TEST_ASSERT_TRUE(rb_sp_dyn.receive(item_split_head, + item_split_tail, + item_split_head_size, + item_split_tail_size)); + + // Check the split item + TEST_ASSERT_NOT_NULL(item_split_head); + TEST_ASSERT_NOT_NULL(item_split_tail); + + for (uint32_t i = 0; i < item_split_head_size; i++) { + TEST_ASSERT_EQUAL(split_item[i], item_split_head[i]); + } + + for (uint32_t i = 0; i < item_split_tail_size; i++) { + TEST_ASSERT_EQUAL(split_item[i + item_split_head_size], item_split_tail[i]); + } + + // Return the split item + rb_sp_dyn.returnItem(item_split_head); + rb_sp_dyn.returnItem(item_split_tail); + + // Static version ----------------------------------------------------------- + item_small_head = nullptr; + item_small_tail = nullptr; + item_small_head_size = 0; + item_small_tail_size = 0; + + item_large_head = nullptr; + item_large_tail = nullptr; + item_large_head_size = 0; + item_large_tail_size = 0; + + item_split_head = nullptr; + item_split_tail = nullptr; + item_split_head_size = 0; + item_split_tail_size = 0; + + // Send items to the buffer + TEST_ASSERT_TRUE(rb_sp_st.send(small_item, sizeof(small_item))); + TEST_ASSERT_TRUE(rb_sp_st.send(large_item, sizeof(large_item))); + + // Receive them + TEST_ASSERT_TRUE( + rb_sp_st.receive(item_small_head, item_small_tail, item_small_head_size, item_small_tail_size)); + TEST_ASSERT_TRUE( + rb_sp_st.receive(item_large_head, item_large_tail, item_large_head_size, item_large_tail_size)); + + // Check the small item + TEST_ASSERT_NOT_NULL(item_small_head); + TEST_ASSERT_NULL(item_small_tail); + TEST_ASSERT_EQUAL_MEMORY(small_item, item_small_head, item_small_head_size); + + // Check the large item + TEST_ASSERT_NOT_NULL(item_large_head); + TEST_ASSERT_NULL(item_large_tail); + TEST_ASSERT_EQUAL_MEMORY(large_item, item_large_head, item_large_head_size); + + // Return them + rb_sp_st.returnItem(item_small_head); + rb_sp_st.returnItem(item_large_head); + + // Add a large item to force the split and fill the ends + TEST_ASSERT_TRUE(rb_sp_st.send(split_item, sizeof(split_item))); + + // Receive the split item + TEST_ASSERT_TRUE( + rb_sp_st.receive(item_split_head, item_split_tail, item_split_head_size, item_split_tail_size)); + + // Check the split item + TEST_ASSERT_NOT_NULL(item_split_head); + TEST_ASSERT_NOT_NULL(item_split_tail); + + for (uint32_t i = 0; i < item_split_head_size; i++) { + TEST_ASSERT_EQUAL(split_item[i], item_split_head[i]); + } + + for (uint32_t i = 0; i < item_split_tail_size; i++) { + TEST_ASSERT_EQUAL(split_item[i + item_split_head_size], item_split_tail[i]); + } + + // Return the split item + rb_sp_st.returnItem(item_split_head); + rb_sp_st.returnItem(item_split_tail); + + // External storage version ------------------------------------------------- + item_small_head = nullptr; + item_small_tail = nullptr; + item_small_head_size = 0; + item_small_tail_size = 0; + + item_large_head = nullptr; + item_large_tail = nullptr; + item_large_head_size = 0; + item_large_tail_size = 0; + + item_split_head = nullptr; + item_split_tail = nullptr; + item_split_head_size = 0; + item_split_tail_size = 0; + + // Send items to the buffer + TEST_ASSERT_TRUE(rb_sp_ext.send(small_item, sizeof(small_item))); + TEST_ASSERT_TRUE(rb_sp_ext.send(large_item, sizeof(large_item))); + + // Receive them + TEST_ASSERT_TRUE(rb_sp_ext.receive(item_small_head, + item_small_tail, + item_small_head_size, + item_small_tail_size)); + TEST_ASSERT_TRUE(rb_sp_ext.receive(item_large_head, + item_large_tail, + item_large_head_size, + item_large_tail_size)); + + // Check the small item + TEST_ASSERT_NOT_NULL(item_small_head); + TEST_ASSERT_NULL(item_small_tail); + TEST_ASSERT_EQUAL_MEMORY(small_item, item_small_head, item_small_head_size); + + // Check the large item + TEST_ASSERT_NOT_NULL(item_large_head); + TEST_ASSERT_NULL(item_large_tail); + TEST_ASSERT_EQUAL_MEMORY(large_item, item_large_head, item_large_head_size); + + // Return them + rb_sp_ext.returnItem(item_small_head); + rb_sp_ext.returnItem(item_large_head); + + // Add a large item to force the split and fill the ends + TEST_ASSERT_TRUE(rb_sp_ext.send(split_item, sizeof(split_item))); + + // Receive the split item + TEST_ASSERT_TRUE(rb_sp_ext.receive(item_split_head, + item_split_tail, + item_split_head_size, + item_split_tail_size)); + + // Check the split item + TEST_ASSERT_NOT_NULL(item_split_head); + TEST_ASSERT_NOT_NULL(item_split_tail); + + for (uint32_t i = 0; i < item_split_head_size; i++) { + TEST_ASSERT_EQUAL(split_item[i], item_split_head[i]); + } + + for (uint32_t i = 0; i < item_split_tail_size; i++) { + TEST_ASSERT_EQUAL(split_item[i + item_split_head_size], item_split_tail[i]); + } + + // Return the split item + rb_sp_ext.returnItem(item_split_head); + rb_sp_ext.returnItem(item_split_tail); +} + +void test_rb_byte_send_recv() { + static uint8_t item_to_send[16]; + memset(item_to_send, 'b', sizeof(item_to_send)); + static uint8_t recv_up_to = 8; + + uint8_t* item_recv; + size_t item_recv_size; + + TEST_ASSERT_TRUE(rb_byte_dyn.send(item_to_send, sizeof(item_to_send))); + item_recv = rb_byte_dyn.receiveUpTo(recv_up_to, item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(recv_up_to, item_recv_size); + TEST_ASSERT_EQUAL_MEMORY(item_to_send, item_recv, item_recv_size); + rb_byte_dyn.returnItem(item_recv); + + TEST_ASSERT_TRUE(rb_byte_st.send(item_to_send, sizeof(item_to_send))); + item_recv = rb_byte_st.receiveUpTo(recv_up_to, item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(recv_up_to, item_recv_size); + TEST_ASSERT_EQUAL_MEMORY(item_to_send, item_recv, item_recv_size); + rb_byte_st.returnItem(item_recv); + + TEST_ASSERT_TRUE(rb_byte_ext.send(item_to_send, sizeof(item_to_send))); + item_recv = rb_byte_ext.receiveUpTo(recv_up_to, item_recv_size); + TEST_ASSERT_NOT_NULL(item_recv); + TEST_ASSERT_EQUAL(recv_up_to, item_recv_size); + TEST_ASSERT_EQUAL_MEMORY(item_to_send, item_recv, item_recv_size); + rb_byte_ext.returnItem(item_recv); +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_rb_creation); + RUN_TEST(test_rb_nosplit_send_recv); + RUN_TEST(test_rb_split_send_recv); + RUN_TEST(test_rb_byte_send_recv); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_tasks/test_tasks.cpp b/test/test_tasks/test_tasks.cpp new file mode 100644 index 0000000..ed478e9 --- /dev/null +++ b/test/test_tasks/test_tasks.cpp @@ -0,0 +1,312 @@ +#include + +#include "RTOScppTask.h" + +using namespace RTOS::Tasks; +static constexpr const char* tag = "test_tasks"; + +/* -------------------------------------------- Tasks ------------------------------------------- */ +static constexpr uint32_t STACK_SIZE = 4096; + +// Task function prototypes +void taskDynamicCtor(void* params); +void taskDynamic(void* params); +void taskStaticCtor(void* params); +void taskStatic(void* params); + +// Dynamic tasks +TaskDynamic task_dyn_ctor("TaskDynCtor", taskDynamicCtor, 1, nullptr); +TaskDynamic task_dyn; + +// Static tasks +TaskStatic task_st_ctor("TaskStCtor", taskStaticCtor, 1, nullptr); +TaskStatic task_st; + +// Invalid task (uninitialized) +TaskStatic task_invalid; + +// Custom struct to pass as parameter +struct MyParams { + uint32_t value; +} my_params{}; + +// Testing task +void taskFunction(void* params); +TaskStatic task("task", taskFunction, 1, &my_params, ARDUINO_RUNNING_CORE); +static bool notify_received = false; +static uint32_t notify_value_received = 0; +static uint32_t notify_old_value = 0; +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_create_dynamic_ctor(); +void test_create_dynamic(); +void test_create_static_ctor(); +void test_create_static(); +void test_create_testing_task(); + +void test_invalid_task(); + +void test_get_task_info(); +void test_change_priority(); +void test_block_state(); +void test_suspend(); +void test_resume(); +void test_abort_delay(); +void test_notify(); +void test_check_notify(); +void test_notify_and_query(); +void test_check_notify_and_query(); +void test_notify_give(); +void test_check_notify_take(); +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_create_dynamic_ctor); + RUN_TEST(test_create_dynamic); + RUN_TEST(test_create_static_ctor); + RUN_TEST(test_create_static); + RUN_TEST(test_create_testing_task); + + RUN_TEST(test_invalid_task); + + RUN_TEST(test_get_task_info); + RUN_TEST(test_change_priority); + RUN_TEST(test_block_state); + RUN_TEST(test_abort_delay); + RUN_TEST(test_suspend); + RUN_TEST(test_resume); + RUN_TEST(test_notify); + RUN_TEST(test_check_notify); + RUN_TEST(test_notify_and_query); + RUN_TEST(test_check_notify_and_query); + RUN_TEST(test_notify_give); + RUN_TEST(test_check_notify_take); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } + +void taskDynamicCtor(void* params) { + for (;;) { + vTaskDelay(portMAX_DELAY); + } +} + +void taskDynamic(void* params) { + for (;;) { + vTaskDelay(portMAX_DELAY); + } +} + +void taskStaticCtor(void* params) { + for (;;) { + vTaskDelay(portMAX_DELAY); + } +} + +void taskStatic(void* params) { + for (;;) { + vTaskDelay(portMAX_DELAY); + } +} + +void taskFunction(void* params) { + // Change parameter value to check it later + MyParams* p = static_cast(params); + p->value = 123; + + for (;;) { + // Check blocked state test + ESP_LOGD(tag, "Task is going to block for 10s"); + vTaskDelay(10000); + + // Suspend and resume test + ESP_LOGD(tag, "Task unblocked, blocking again for 10s"); + vTaskDelay(10000); + + ESP_LOGD(tag, "Task resumed, waiting for notify"); + + // Notify test + notify_received = task.notifyWait(0, 0, notify_value_received, portMAX_DELAY); + ESP_LOGD(tag, + "Notify received (%s): %u", + notify_received ? "true" : "false", + notify_value_received); + + // Notify and query test + notify_received = task.notifyWait(0, 0, notify_value_received, portMAX_DELAY); + ESP_LOGD(tag, + "Notify received (%s): %u", + notify_received ? "true" : "false", + notify_value_received); + + // Notify take test + notify_value_received = task.notifyTake(true, portMAX_DELAY); + ESP_LOGD(tag, "Notify take passed"); + + ESP_LOGD(tag, "Blocking indefinitely"); + vTaskDelay(portMAX_DELAY); + } +} +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_create_dynamic_ctor() { + TEST_ASSERT_FALSE(task_dyn_ctor.isCreated()); + TEST_ASSERT_TRUE(task_dyn_ctor.create()); + TEST_ASSERT_TRUE(task_dyn_ctor.isCreated()); +} + +void test_create_dynamic() { + TEST_ASSERT_FALSE(task_dyn.isCreated()); + TEST_ASSERT_TRUE(task_dyn.create("TaskDyn", taskDynamic, 1, nullptr, ARDUINO_RUNNING_CORE)); + TEST_ASSERT_TRUE(task_dyn.isCreated()); +} + +void test_create_static_ctor() { + TEST_ASSERT_FALSE(task_st_ctor.isCreated()); + TEST_ASSERT_TRUE(task_st_ctor.create()); + TEST_ASSERT_TRUE(task_st_ctor.isCreated()); +} + +void test_create_static() { + TEST_ASSERT_FALSE(task_st.isCreated()); + TEST_ASSERT_TRUE(task_st.create("TaskSt", taskStatic, 1, nullptr, ARDUINO_RUNNING_CORE)); + TEST_ASSERT_TRUE(task_st.isCreated()); +} + +void test_create_testing_task() { + TEST_ASSERT_FALSE(task.isCreated()); + TEST_ASSERT_TRUE(task.create()); + TEST_ASSERT_TRUE(task.isCreated()); +} + +void test_invalid_task() { + TEST_ASSERT_FALSE(task_invalid.isCreated()); + TEST_ASSERT_FALSE(task_invalid.create(nullptr, nullptr, 0)); + TEST_ASSERT_FALSE(task_invalid.isCreated()); + + const char* name = task_invalid.getName(); + void* params = task_invalid.getParameters(); + uint8_t core = task_invalid.getCore(); + uint8_t priority = task_invalid.getPriority(); + + TEST_ASSERT_NULL(name); + TEST_ASSERT_NULL(params); + TEST_ASSERT_EQUAL(0xFF, core); + TEST_ASSERT_EQUAL(0xFF, priority); +} + +void test_get_task_info() { + const char* name = task.getName(); + TEST_ASSERT_NOT_NULL(name); + TEST_ASSERT_EQUAL_STRING("task", name); + + void* params = task.getParameters(); + TEST_ASSERT_NOT_NULL(params); + TEST_ASSERT_EQUAL(123, static_cast(params)->value); + + uint8_t core = task.getCore(); + TEST_ASSERT_EQUAL(1, task.getCore()); + + uint8_t priority = task.getPriority(); + TEST_ASSERT_EQUAL(1, priority); + + TEST_ASSERT_EQUAL(STACK_SIZE, task.getStackSize()); + + TEST_ASSERT_TRUE(task.updateStackStats()); + + uint32_t stack_used = task.getStackUsed(); + TEST_ASSERT_GREATER_THAN_UINT32(0, stack_used); + + uint32_t stack_min = task.getStackMinUsed(); + TEST_ASSERT_LESS_THAN_UINT32(0xffffffff, stack_min); + + uint32_t stack_max = task.getStackMaxUsed(); + TEST_ASSERT_GREATER_THAN_UINT32(0, stack_max); +} + +void test_change_priority() { + TEST_ASSERT_TRUE(task.setPriority(2)); + + uint8_t priority = task.getPriority(); + TEST_ASSERT_EQUAL(2, priority); +} + +void test_block_state() { TEST_ASSERT_EQUAL(eTaskState::eBlocked, task.getState()); } + +void test_abort_delay() { + TEST_ASSERT_TRUE(task.abortDelay()); + TEST_ASSERT_EQUAL(eTaskState::eReady, task.getState()); +} + +void test_suspend() { + TEST_ASSERT_TRUE(task.suspend()); + TEST_ASSERT_EQUAL(eTaskState::eSuspended, task.getState()); +} + +void test_resume() { + TEST_ASSERT_TRUE(task.resume()); + TEST_ASSERT_NOT_EQUAL(eTaskState::eSuspended, task.getState()); +} + +void test_notify() { TEST_ASSERT_TRUE(task.notify(1, eNotifyAction::eSetValueWithOverwrite)); } + +void test_check_notify() { + TEST_ASSERT_TRUE(notify_received); + notify_received = false; + + TEST_ASSERT_EQUAL(1, notify_value_received); + notify_value_received = 0; +} + +void test_notify_and_query() { + TEST_ASSERT_TRUE(task.notifyAndQuery(2, eNotifyAction::eSetValueWithOverwrite, notify_old_value)); +} + +void test_check_notify_and_query() { + TEST_ASSERT_TRUE(notify_received); + notify_received = false; + + TEST_ASSERT_EQUAL(1, notify_old_value); + notify_old_value = 0; + + TEST_ASSERT_EQUAL(2, notify_value_received); + notify_value_received = 0; +} + +void test_notify_give() { TEST_ASSERT_TRUE(task.notifyGive()); } + +void test_check_notify_take() { + TEST_ASSERT_EQUAL(0, notify_value_received); + notify_value_received = 0; +} +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/test/test_timers/test_timers.cpp b/test/test_timers/test_timers.cpp new file mode 100644 index 0000000..a776802 --- /dev/null +++ b/test/test_timers/test_timers.cpp @@ -0,0 +1,171 @@ +#include + +#include "RTOScppTimer.h" + +using namespace RTOS::Timers; +static constexpr const char* tag = "test_timers"; + +/* ------------------------------------------- Timers ------------------------------------------- */ +static constexpr uint32_t timer_period = pdMS_TO_TICKS(1000); + +// Dynamic timers +TimerDynamic timer_dyn_ctor( + "TimerDynCtor", [](TimerHandle_t) {}, timer_period, nullptr, false, false); +TimerDynamic timer_dyn; + +// Static timers +TimerStatic timer_st_ctor( + "TimerStCtor", [](TimerHandle_t) {}, timer_period, nullptr, false, false); +TimerStatic timer_st; + +// Invalid timer +TimerStatic timer_invalid; + +// Timer and parameters for testing methods +static bool timer_expired = false; + +// Custom struct to pass as timer id +struct MyTimerId { + uint32_t value; +} my_timer_id{}; + +void timerCb(TimerHandle_t timer) { + ESP_LOGI(tag, "Timer expired"); + timer_expired = true; + + // Change timer id value + MyTimerId* id = static_cast(pvTimerGetTimerID(timer)); + id->value = 123; +} + +TimerStatic timer("Timer", timerCb, timer_period, nullptr, false, false); +/* ---------------------------------------------------------------------------------------------- */ + +/* -------------------------------------------- Tests ------------------------------------------- */ +void test_timers_creation() { + // Dynamic timers + TEST_ASSERT_TRUE(timer_dyn_ctor); + TEST_ASSERT_TRUE(timer_dyn_ctor.isCreated()); + + TEST_ASSERT_FALSE(timer_dyn); + TEST_ASSERT_FALSE(timer_dyn.isCreated()); + TEST_ASSERT_TRUE(timer_dyn.create( + "TimerSt", + [](TimerHandle_t) {}, + timer_period, + nullptr, + false, + false)); + TEST_ASSERT_TRUE(timer_dyn); + TEST_ASSERT_TRUE(timer_dyn.isCreated()); + + // Static timers + TEST_ASSERT_TRUE(timer_st_ctor); + TEST_ASSERT_TRUE(timer_st_ctor.isCreated()); + + TEST_ASSERT_FALSE(timer_st); + TEST_ASSERT_FALSE(timer_st.isCreated()); + TEST_ASSERT_TRUE(timer_st.create( + "TimerSt", + [](TimerHandle_t) {}, + timer_period, + nullptr, + false, + false)); + TEST_ASSERT_TRUE(timer_st); + TEST_ASSERT_TRUE(timer_st.isCreated()); + + // Testing timer + TEST_ASSERT_TRUE(timer); + TEST_ASSERT_TRUE(timer.isCreated()); +} + +void test_invalid_timer() { + TEST_ASSERT_FALSE(timer_invalid.isCreated()); + TEST_ASSERT_FALSE(timer_invalid.create(nullptr, nullptr, 0, nullptr, false, false)); + TEST_ASSERT_FALSE(timer_invalid.isCreated()); +} + +void test_get_timer_info() { + const char* name = timer.getName(); + TEST_ASSERT_EQUAL_STRING("Timer", name); + + TickType_t period = timer.getPeriod(); + TEST_ASSERT_EQUAL(timer_period, period); + + bool reload_mode = timer.getReloadMode(); + TEST_ASSERT_EQUAL(false, reload_mode); + + void* timer_id = timer.getTimerID(); + TEST_ASSERT_EQUAL(nullptr, timer_id); + + TEST_ASSERT_TRUE(timer.setTimerID(&my_timer_id)); + timer_id = timer.getTimerID(); + TEST_ASSERT_EQUAL(&my_timer_id, timer_id); +} + +void test_control() { + TEST_ASSERT_TRUE(timer.start()); + vTaskDelay(pdMS_TO_TICKS(10)); + + TEST_ASSERT_TRUE(timer.isActive()); + + TickType_t expiry_time = timer.getExpiryTime(); + TEST_ASSERT_LESS_THAN_UINT32(timer_period, expiry_time); + + TEST_ASSERT_EQUAL(false, timer_expired); + vTaskDelay(pdMS_TO_TICKS(10)); + + TEST_ASSERT_TRUE(timer.stop()); + vTaskDelay(pdMS_TO_TICKS(10)); + TEST_ASSERT_FALSE(timer.isActive()); + TEST_ASSERT_EQUAL(false, timer_expired); + + TEST_ASSERT_TRUE(timer.reset()); + vTaskDelay(pdMS_TO_TICKS(10)); + TEST_ASSERT_TRUE(timer.isActive()); + vTaskDelay(timer_period + pdMS_TO_TICKS(100)); + + TEST_ASSERT_TRUE(timer_expired); + TEST_ASSERT_EQUAL(123, my_timer_id.value); + timer_expired = false; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ------------------------------------------ USB Logs ------------------------------------------ */ +SemaphoreHandle_t log_mutex = nullptr; + +int redirectLogs(const char* str, va_list list) { + if (log_mutex != nullptr) xSemaphoreTake(log_mutex, portMAX_DELAY); + + static char buffer[2048]; + int ret = vsnprintf(buffer, sizeof(buffer), str, list); + Serial.write(buffer); + + if (log_mutex != nullptr) xSemaphoreGive(log_mutex); + + return ret; +} +/* ---------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------- */ +void setup() { + log_mutex = xSemaphoreCreateMutex(); + esp_log_set_vprintf(redirectLogs); + esp_log_level_set("*", ESP_LOG_VERBOSE); + delay(3000); + + ESP_LOGI(tag, "Running tests..."); + UNITY_BEGIN(); + + RUN_TEST(test_timers_creation); + RUN_TEST(test_invalid_timer); + RUN_TEST(test_get_timer_info); + RUN_TEST(test_control); + + ESP_LOGI(tag, "Finishing tests..."); + UNITY_END(); +} + +void loop() { vTaskDelete(nullptr); } +/* ---------------------------------------------------------------------------------------------- */ \ No newline at end of file