MoeSimpleRealtimeOS is a lightweight, portable embedded real-time operating system (RTOS) designed specifically for the Arduino platform and similar microcontroller environments.
It provides fundamental task scheduling, priority management, and inter-task communication mechanisms, aiming to help developers organize complex embedded applications in a clearer and more modular way.
- Preemptive Scheduling: Supports priority-based preemptive scheduling. A higher-priority task can immediately preempt a currently running lower-priority task when it transitions to the "Ready" state.
- Time-Slice Round Robin: Tasks with the same priority execute in rotation using a fixed time slice (default 10ms), preventing any single task from monopolizing the CPU for extended periods.
- Multi-Task State Management: Supports five task states:
RUNNING: The task currently being executed.READY: Ready to run, waiting for the scheduler to assign CPU time.BLOCKED: Blocked due to callingvTaskDelay()or waiting for a resource.PAUSED: Explicitly paused and will not participate in scheduling.DEAD: Terminated or unassigned task.
- Dynamic Task Creation and Destruction: Supports dynamically creating, starting, pausing, and terminating tasks at runtime.
- Message Queue
- Provides a type-safe, thread-safe message passing mechanism suitable for data exchange and event notification between tasks.
- Implemented using a circular buffer, supporting arbitrary data types (via templates).
- Supports blocking dequeue operations with timeout, facilitating the implementation of synchronization logic.
- Uses interrupt protection to ensure data consistency in multi-tasking/interrupt environments.
- Resource Exclusive Control (Planned)
- Safe access to shared resources (e.g., serial port, I2C bus) can be achieved through external mutual exclusion mechanisms (e.g., semaphores, critical sections).
- Currently, it is recommended to use
noInterrupts()/interrupts()or FreeRTOS (on ESP32 platforms) to implement resource protection.
Create an instance of MoeTask and configure tasks within setup(); continuously call runScheduler() in loop() to initiate the scheduling loop.
#include "MoeTask.h"
MoeTask os; // Create an OS instance
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial monitor connection (for development only)
// Create tasks
int t1 = os.vTaskCreate(task1, 1); // Priority 1
int t2 = os.vTaskCreate(task2, 2); // Priority 2 (higher)
// Start tasks
os.vTaskStart(t1);
os.vTaskStart(t2);
}
void loop() {
os.runScheduler(); // Must be called continuously in loop
}Task functions are of type void() with no parameters or return value. Implement business logic within these functions, and use vTaskDelay() to achieve non-blocking delays.
void task1() {
Serial.println("Task 1 is running");
os.vTaskDelay(1000); // Delay 1 second (non-blocking)
}
void task2() {
Serial.println(" Task 2 is running");
os.vTaskDelay(500); // Delay 500ms
}| Function | Description |
|---|---|
int vTaskCreate(void (*taskFunc)(void), int priority = 0) |
Creates a task, returns task ID; priority range 0~9 (9 is highest) |
void vTaskStart(int taskId) |
Sets task state to READY, adding it to scheduling |
void vTaskPause(int taskId) |
Pauses task (enters PAUSED state) |
void vTaskKill(int taskId) |
Terminates task (enters DEAD state) |
int vTaskId(void (*taskFunc)(void)) |
Looks up task ID by function pointer |
void vTaskDelay(uint32_t ms) |
Delays current task by specified milliseconds (enters BLOCKED state) |
⚠️ Note:vTaskDelay()can only be called by the task itself, not within interrupts or other non-task contexts.
Use the template MoeQueue<T, Size> to define a queue, supporting any copyable type.
#include "MoeQueue.h"
struct SensorData {
float temperature;
float humidity;
uint32_t timestamp;
};
// Create a queue capable of holding up to 8 SensorData messages
MoeQueue<SensorData, 8> sensorQueue;void loop() {
// Producer: Generate one data point every 2 seconds
static uint32_t lastSend = 0;
if (millis() - lastSend > 2000) {
SensorData data = {25.5f, 60.0f, millis()};
if (sensorQueue.push(data)) {
Serial.println("Data pushed to queue");
} else {
Serial.println("Queue full!");
}
lastSend = millis();
}
// Consumer: Attempt to retrieve data (wait up to 100ms)
SensorData received;
if (sensorQueue.pop(received, 100)) {
Serial.printf("Received: Temp=%.1f°C, Humi=%.1f%%, Time=%u\n",
received.temperature, received.humidity, received.timestamp);
}
delay(10); // Prevent loop from consuming CPU too quickly
}| Function | Description |
|---|---|
bool push(const T& msg) |
Enqueues a message; returns true on success, false if queue is full |
bool pop(T& msg) |
Dequeues a message non-blockingly; returns false if queue is empty |
bool pop(T& msg, uint32_t timeoutMs) |
Dequeues a message with timeout (0 means non-blocking) |
bool empty() const |
Checks if the queue is empty |
bool full() const |
Checks if the queue is full |
size_t size() const |
Gets the current number of messages in the queue |
void clear() |
Clears the queue |
✅ Thread Safety: All operations achieve atomicity by disabling interrupts, ensuring safe usage in multi-tasking or interrupt environments.
System behavior can be adjusted at compile-time via predefined macros:
| Macro Definition | Default Value | Description |
|---|---|---|
MAX_TASKS |
10 |
Maximum number of concurrent tasks; adjustable based on memory |
TASK_TIMESLICE_MS |
10 |
Time-slice length (in milliseconds) for tasks of the same priority |
Example: Defining
#define MAX_TASKS 5before compilation reduces memory usage.
- General Platforms (e.g., Arduino Uno, ESP8266):
- Uses
runScheduler()inloop()for polling-based scheduling. - Runs on a single core, relying on
millis()for time management.
- Uses
- ESP32 Multi-Core Support (Experimental):
- Automatically enables FreeRTOS integration when
ARDUINO_ARCH_ESP32is detected. - Supports running the scheduler on dual cores via
xTaskCreatePinnedToCore(currently not supported; defaults to single-core polling). - True multi-core parallel scheduling may be implemented in future versions.
- Automatically enables FreeRTOS integration when
Design Philosophy:
- Minimalist kernel with clear code, easy to understand and customize.
- Zero dependencies (aside from Arduino core libraries), facilitating portability.
- Suitable for education, prototyping, and small-to-medium projects.
Use Cases:
- Multi-sensor data acquisition and processing
- Separation of user interface and background tasks
- Timed control and event-driven systems
- Educational tool for learning RTOS fundamentals
Refer to CHANGELOG.md for version update history.
📌 Note: This system is currently a lightweight implementation that does not support advanced features such as memory protection or task stack isolation. It is suitable for resource-constrained embedded scenarios requiring basic multitasking capabilities.
- Thanks to all contributors
This project is open-source under the LGPLv2.1 license.