Skip to content

pdlsurya/sanoRTOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

101 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sanoRTOS

sanoRTOS is a minimal Real-Time Operating System (RTOS) designed for ARM Cortex-M and RISC-V microcontrollers. This implementation provides a simple yet effective API for task management, synchronization, and communication, enabling efficient and predictable multitasking in embedded systems.

Features

  • Priority-Based Preemptive Scheduling
    Efficient task management with support for preemptive scheduling based on task priority levels.

  • Optional Priority Inheritance
    Prevents priority inversion during mutex acquisition by temporarily elevating the priority of lower-priority tasks.

  • Symmetric Multiprocessing (SMP) Support
    Fully supports multi-core systems with per-task core affinity configuration for optimal load balancing.

  • Dynamic Stack Overflow Detection
    Runtime monitoring of task stacks to catch and handle stack overflows proactively.

  • Configurable Tick Rate
    Easily adjustable tick frequency to match application-specific timing and power requirements.

  • Task Synchronization Primitives
    Includes mutexes, semaphores, condition variables, and event objects for safe and efficient coordination between tasks.

  • Inter-Task Communication
    Enables message passing between tasks using message queues, stream buffers, message buffers, and mailboxes.

  • Minimalistic and Lightweight Design
    Designed for embedded systems with limited resources — small footprint, fast context switches, and no unnecessary bloat.

Kernel Internals

  • Intrusive Task Queues The ready queue uses bitmap-backed intrusive FIFO buckets with 16 configured priority levels by default, so ready insertion and highest-priority lookup stay constant-time. Blocked, timeout, and object wait queues remain intrusive doubly linked lists, and each task embeds one link for state queues, one for kernel-object wait queues, and one for the global timeout queue.

  • Global vs Object Queue APIs The scheduler exposes singleton ready and blocked queues through the task-queue layer, so ready/blocked operations do not require callers to pass queue pointers. Ready-task selection also respects core affinity by splitting the ready queue into per-priority buckets for each affinity class. Object-owned wait queues still take explicit queue arguments because each kernel object maintains its own waiter list.

  • Kernel Objects Still Own Wait Queues Semaphores, mutexes, events, mailboxes, message queues, stream buffers, message buffers, condition variables, and memory slabs still keep wait queues in their object state. The queue stores head and tail anchors, while the task carries the intrusive links.

  • Wait-Queue Lock Handoff Object wait queues optionally carry a pointer to the object lock that protects them. When a task blocks on one of those queues, the task layer links the task into both the object wait queue and the scheduler blocked/timeout queues before releasing the object lock. Scheduler-side wake, timeout, suspend, and delete paths then use the same queue-owned lock when they need to detach a blocked task from an object wait queue.

  • Static And Dynamic Object Lifetime Kernel objects continue to support zero-overhead static definition through the existing *_DEFINE(...) macros. Dynamic object handles are allocated from internal per-object slab-backed pools, while variable-sized backing buffers still use the heap when needed. Public memSlab objects themselves remain static-only. Dynamic deletion is conservative: it returns RET_BUSY when waiters or active use would make destruction unsafe. Runtime names are now kept only for tasks.

  • Current-Task Blocking Path Internal task blocking resolves the current task inside the task layer, so wait-object call sites stay focused on the blocking reason and timeout instead of passing the current task around. Finite waits are inserted into a global timeout queue ordered by absolute deadline, while TASK_FOREVER_WAIT blocks without consuming a timeout-queue slot. The scheduler owns the monotonic system tick count used to evaluate those deadlines.

  • Delayed Work State Delayed work keeps its own lock for the timer-armed state, while the target work queue lock continues to protect queued work items. Immediate submit, timeout submit, cancel, and pending checks now update delayed-work state before the worker task is notified, so delayed-work callers do not yield while still holding the delayed-work lock.

  • Software Timers Use O(1) Unlink Active software timers are tracked in a doubly linked list so timerStop() can remove a running timer directly without scanning from the head of the timer list.

  • Internal Locking Naming Internal helpers that end with Locked expect the caller to already hold the relevant lock. Helpers without the Locked suffix either acquire the lock internally or do not rely on a caller-held lock as part of their contract.

API Reference

The detailed API reference, including example code for each kernel object, lives in API_REFERENCE.md.

Building and Running

Example for STM32F4 using STM32Cube IDE

  1. Clone the Repository:

    • Open a terminal and clone the sanoRTOS repository git clone https://github.com/pdlsurya/sanoRTOS.git
  2. Open STM32Cube IDE:

    • Launch STM32Cube IDE and create a new STM32F4 project or open an existing one.
  3. Include sanoRTOS in Project:

    • Right-click on your project and select Properties.
    • Go to C/C++ Build > Settings.
    • Under Tool Settings, go to MCU GCC Compiler > Include paths and add the path to sanoRTOS/include and sanoRTOS/ports/arm/stm32f4/include directory.
  4. Add Source Files:

    • Navigate to C/C++ General > Paths and Symbols.
    • In the Source Location tab, click on Link folder and add the path to sanoRTOS/source and sanoRTOS/ports/arm/stm32f4 directory by selecting Link to folder in the filesystem.
  5. Edit stm32xxxx_it.c file:

    • STM32 initializes the SysTick timer during its clock initialization process and defines the SysTick_Handler ISR function for the implementation of the delay function in the Core > Src > stm32xxxx_it.c file. Hence, the SysTick_Handler ISR function cannot be redefined inside the sanoRTOS. Instead, call sanoRTOS_SysTickHook() from the SDK's SysTick_Handler() implementation.

    • Moreover, sanoRTOS includes definition for PendSV_Handler used for task scheduling and context switching. STM32 also defines this ISR in stm32xxxx_it.c file; Hence, we need to remove the definition of this ISR from the stm32xxxx_it.c file to avoid multiple definition error.

Example for RP2350(Raspberry Pi pico 2) using pico-sdk

  1. Clone the Repository:

    • Open a terminal and clone the sanoRTOS repository git clone https://github.com/pdlsurya/sanoRTOS.git
  2. Set Up Your Pico Project:

  • You can either create a new CMake-based Pico SDK project or add sanoRTOS to an existing one. Add sanoRTOS folder to your project folder
  • sanoRTOS supports both ARM and RISC-V cores of the RP2350.
  1. Include sanoRTOS in Your CMakeLists.txt:
  • Update your CMakeLists.txt to include sanoRTOS headers and sources as follows:
file(GLOB_RECURSE SANORTOS_SRCS
    sanoRTOS/source/*.c
    sanoRTOS/ports/arm/rp2350/port.c)
#for Hazard3 RISC-V cores, add source files from risc-v port of rp2350 (sanoRTOS/ports/riscv/rp2350/port.c and  sanoRTOS/ports/riscv/rp2350/port.S)

target_include_directories(project_name PRIVATE
   sanoRTOS/include
   sanoRTOS/ports/arm/rp2350/include #for RISC-V port replace this line  with sanoRTOS/ports/risc-v/rp2350/include 
) 
target_sources(project_name PRIVATE ${SANORTOS_SRCS})

Example for RP2040 (Raspberry Pi Pico) using pico-sdk

  1. Add sanoRTOS to your Pico SDK project and include the RP2040 ARM port files.
file(GLOB_RECURSE SANORTOS_SRCS
    sanoRTOS/source/*.c
    sanoRTOS/ports/arm/rp2040/port.c)

target_include_directories(project_name PRIVATE
   sanoRTOS/include
   sanoRTOS/ports/arm/rp2040/include
)

target_sources(project_name PRIVATE ${SANORTOS_SRCS})
target_link_libraries(project_name PRIVATE pico_stdlib pico_multicore hardware_sync)
  1. The RP2040 port uses:
    • SysTick_Handler() from the sanoRTOS port for the scheduler tick
    • PendSV_Handler() from the sanoRTOS port for context switching
    • multicore_launch_core1() when CONFIG_SMP is enabled
    • RP2040 hardware spin lock PICO_SPINLOCK_ID_OS1 to serialize RTOS CAS operations on Cortex-M0+

Example Code:

#include "sanoRTOS/config.h"
#include "sanoRTOS/scheduler.h"
#include "sanoRTOS/task.h"
//Include MCU-specific header files here

// Define two tasks with 1024-byte stacks.
// These tasks are assigned to any available core using AFFINITY_CORE_ANY.
//
// On multicore MCUs (e.g. RP2350), sanoRTOS can schedule tasks on different cores
// depending on the affinity setting. AFFINITY_CORE_ANY allows the scheduler to choose any core.
// This can help distribute load across both cores and enable true parallel execution.
//
// On single-core MCUs, this setting is safely ignored — all tasks will run on the only available core.
TASK_DEFINE(task1, 1024, firstTask, NULL, 1, AFFINITY_CORE_ANY);
TASK_DEFINE(task2, 1024, secondTask, NULL, 1, AFFINITY_CORE_ANY);
taskHandleType *dynamicTask = NULL;


// Task 1 function – user-defined logic inside this loop
void firstTask(void *args) {
   while (1) {
       // Task1 code to run repeatedly
   }
}

// Task 2 function – user-defined logic inside this loop
void secondTask(void *args) {
   while (1) {
       // Task2 code to run repeatedly
   }
}

// Dynamically created task function
void thirdTask(void *args) {
   while (1) {
       // Dynamic task code to run repeatedly
   }
}

int main() {
   // Perform MCU-specific initializations here
   // Example: initialize GPIO, UART, peripherals, etc.

   // Start tasks
   taskStart(&task1);
   taskStart(&task2);

   // Create and start a dynamic task
   int ret = taskCreate(&dynamicTask,
                        "dynamicTask",
                        1024,
                        thirdTask,
                        NULL,
                        1,
                        AFFINITY_CORE_ANY);
   if (ret != RET_SUCCESS) {
       // Handle task creation failure
       while (1) {
       }
   }

   // Start the sanoRTOS scheduler (will not return)
   schedulerStart();

   // Control should never reach here if scheduler is working correctly
   return 0;
}

ESP32 Ports

This RTOS includes ports for the following Espressif RISC-V based MCUs:

  • ESP32-C6
  • ESP32-P4

Unlike most ESP32 projects, these ports do not use Espressif's ESP-IDF.
Instead, they are built on top of custom ESP32 RISC-V bare-metal SDK developed specifically for low-level control and minimal system overhead.

The bare-metal SDK provides:

  • Startup code and linker scripts
  • Register definitions and peripheral structures
  • Interrupt and exception handling
  • Basic hardware initialization
  • Minimal HAL required for RTOS operation

This SDK allows the RTOS to run without the ESP-IDF framework, making the implementation closer to traditional embedded RTOS ports.

Notes

  • The RTOS ports assume the system is initialized using the corresponding bare-metal SDK.
  • ESP-IDF is not required to build or run these ports.

License

This project is licensed under the MIT License-see the LICENSE file for details.

Releases

No releases published

Packages

 
 
 

Contributors