A small, portable, preemptive RTOS for microcontrollers.
One public header · no editable kernel files · every feature a compile-time switch
Why · Features · Verified · Install · Documentation
Warning
Early and under active development. The kernel is functional and self-testing on every board in the table below, but APIs may still change and it is not yet recommended for production use.
It costs one exception vector. The kernel takes over the lowest-priority
exception - PendSV on Cortex-M, the machine software interrupt on RISC-V - and
nothing else. SVC is left entirely to the application, which keeps it
compatible with everything that legitimately wants it: Nordic's SoftDevice,
TF-M and other secure firmware, vendor bootloaders and ROM APIs. The tick is a
single application call to os_tick_handler(), and its timer is configurable,
so parts whose SysTick stops in low-power modes are first-class rather than
special cases. No HAL, no CMSIS dependency, no linker-script edits.
Misintegration fails loudly. The kernel checks at boot that the vector table
really routes the switch to it, and traps at the cause if not. The usual
alternative is a board that reaches os_start() and stops dead - no fault, no
output, nothing to attach a debugger to. The same principle runs through the
API: missing callbacks are link errors naming the function, an incomplete
os_config.h is a compile error, and a port/-mcpu mismatch fails to compile
rather than producing a subtly wrong context switch.
You never edit a kernel file. One template becomes your os_config.h, and
that is the single source of configuration for both the kernel library and your
application. Updating the kernel is a drop-in replacement of AhuraRTOS/, every
time - there are no local modifications to re-apply, because there are none to
make.
Every feature is a compile-time switch. OS_CONFIG_<FEATURE>_ENABLE removes
code, RAM and API surface - not just runtime behavior. And nothing in the
kernel allocates dynamically: task blocks, ready lists, timer objects, the log
ring and the optional heap are all static, so what a build costs is decided at
compile time and visible in the map file.
The scheduler is O(1), and so is deciding not to run it. One FIFO ready list
per priority plus a 32-bit ready bitmap: the next task is a CLZ and a list
head, whatever the task count. A tick that would not switch anything costs one
bitmap check instead of a full context-switch round trip.
Barriers that fit what you are actually guarding. A scheduler lock that
defers preemption without masking a single interrupt - the barrier a critical
section cannot be. Critical sections that can mask with BASEPRI instead of
PRIMASK, leaving urgent ISRs with zero kernel-induced latency. And a full
atomic set that is lock-free wherever the instruction set allows it.
Priority inheritance is always on. No switch to turn it off while still calling the object a mutex, and the accounting stays correct when one task holds several contended mutexes at once. What inheritance cannot do is prevent a deadlock - that is a lock-ordering fault, not a timing one - so development builds detect that instead: blocking on a mutex whose wait chain leads back to you asserts the moment the cycle would form, while the guilty call stack is still there to read, rather than leaving a board that silently stops.
It proves itself on your board. A built-in self-test suite exercises every
enabled feature over printf, with no application code and no board support,
and finishes with a cycle-accurate benchmark table for every hot kernel path.
Bring-up is: flash it, read the console, then start writing firmware.
Two instruction sets, one kernel. M0 through M85 - ARMv6-M to ARMv8.1-M - across three shared port implementations, plus RV32 on Hazard3. Nothing in the kernel names a vendor, a family, or a HAL, and adding an architecture is a new port rather than a kernel change.
| Scheduling | Preemptive, 31 priority levels, one FIFO ready list per priority, O(1) pick, configurable round-robin |
| Sync & IPC | Mutexes with priority inheritance · counting semaphores · queues, static or heap-backed · variable-length message buffers · event groups · per-task notifications - all with millisecond timeouts |
| Timers | One-shot and periodic software timers, plus caller-owned deferred-call pools, all delivered on one service task so callbacks run in task context |
| Memory | An optional first-fit kernel heap with coalescing. Everything else is static |
| Diagnostics | Stack watermarking · stack-overflow detection · mutex deadlock detection · CPU-load sampling · buffered logging that never stalls the caller |
| Multi-core | SMP scheduling with a per-task core-affinity mask over shared ready lists |
| Security | TrustZone on ARMv8-M: secure, non-secure or disabled, per build |
A complete application, in full:
int main(void)
{
SystemClock_Config();
os_init(); /* idle task, service tasks, your default task, the tick */
os_start(); /* never returns */
}
void os_main(void) /* your application - already a running task */
{
while (1)
{
my_led_toggle();
os_delay_ms(500U);
}
}os_init() creates and starts a default task for you, so there is nothing to
declare just to get moving. Every feature above is described with the mechanism
behind it in the kernel reference.
The self-test suite is not a CI badge - it runs on the board. Every row below is a real part that has run the full suite to completion:
| Board | Cores | Self-test | Dual-core SMP |
|---|---|---|---|
| Raspberry Pi Pico 2 | 2 × Cortex-M33 (RP2350) | ✅ | ✅ |
| Raspberry Pi Pico 2 | 2 × Hazard3 RV32 (RP2350) | ✅ | ✅ |
| Raspberry Pi Pico | 2 × Cortex-M0+ (RP2040) | ✅ | ✅ |
| NUCLEO-H743ZI | Cortex-M7 (STM32H7) | ✅ | single-core part |
| NUCLEO-H503RB | Cortex-M33 (STM32H5) | ✅ | single-core part |
| NUCLEO-G431RB | Cortex-M4 (STM32G4) | ✅ | single-core part |
That covers every port the kernel has - ARMv6-M, ARMv7-M, ARMv8-M and RV32 - and both dual-core models, Arm and RISC-V.
Tickless idle runs on all six, driven by the idle task, and the suite measures it rather than assuming it: it counts tick-interrupt entries across a window of known length and reads 0 or 1 against 40. Deep sleep with an LPTIM is proven on the G431 and H503; the other boards use a SoC wake source or SysTick's own reload.
Not yet run on silicon: TrustZone - it builds and its callbacks are wired, but no part with the Security Extension has run it. It is on the roadmap rather than claimed here.
On a Raspberry Pi Pico SDK or STM32CubeMX project it is one command, which
prints the exact diff it wants to apply and waits for a y before touching
anything. On STM32 that diff now includes the .ioc: the installer clears the two
CubeMX boxes that would generate vectors the kernel owns, so a later Generate
Code agrees with it. One setting stays yours - the HAL time
base. Every installer has an offline twin for machines with no
internet, and the same integration by hand is six steps on any other vendor,
IDE or build system.
→ Installing AhuraRTOS is the table that names all three routes for every packaged chip.
📖 Documentation index - or go straight to a page:
| Page | What is in it |
|---|---|
| Start here | |
| Installing AhuraRTOS | The pick-your-chip table - all three routes per chip - then the general procedure: six steps, any vendor, IDE and build system |
| AhuraRTOS on STM32 | Everything STM32 in one page. Three install routes - automatic, offline, manual with the exact CubeMX checkboxes - then the st/stm32 package |
| AhuraRTOS on Raspberry Pi | Everything RP2040 / RP2350 / RP2354 in one page. Three install routes - automatic, offline, manual - then the three packages, Arm and RISC-V |
| Vendor notes | The one thing that differs per vendor - whether its code generator emits a competing PendSV_Handler - on STM32, Nordic, NXP, TI, Silicon Labs, Renesas, Microchip, Infineon, GD32 and anything else |
| Running the self-test suite | Prove a fresh port before writing anything on top of it: how to enable it, how to read the output, why a silent console is usually the libc, and how much flash to budget |
| The kernel | |
| Kernel reference | The authoritative reference. What the kernel is, and the entry point to the five pages below |
| Using the kernel | Every API: tasks, priorities, mutexes, queues, message buffers, notifications, atomics, timers, deferred calls, the heap, diagnostics and debugging |
| How the kernel works | Boot, the scheduler, the context switch, the tick, blocking and waking, priority inheritance, and where the RAM goes |
| What the kernel needs from a platform | The reference behind the install: non-CMake build inputs, every os_config.h option, and the two-item integration contract |
| Platform support | The callbacks a platform must supply, the clock, TrustZone, multi-core and tickless idle |
| Tickless idle | The whole suppressed-tick contract on one page: who masks, who plans, who clamps, who measures, who may sleep, what each port and each SoC package does, and how to prove it on a board |
| Source layout | What each directory and each kernel/ source file is, and why os_internal.h is reachable from none of them |
| SoC packages | The optional per-silicon layer under soc/: who owns which callback, what may and may not live there, and how to write one. The four packaged parts are documented in the two vendor pages above |
| Testing and examples | |
| Testing and examples | The self-test suite and the runnable examples, together |
| Examples | One runnable os_main.c per kernel feature, and how to run them |
| The project | |
| Platforms | Which cores, vendors and toolchains are supported today, what is planned, and what is experimental |
| Roadmap | The three phases, the known gaps tracked for later, and the current status |
| C style | The style every file in this repository is written to |
AhuraRTOS/
├── CMakeLists.txt <- builds the ahura_kernel library; the application calls
│ add_subdirectory(AhuraRTOS) and links ahura_kernel
├── ahura.h <- the public umbrella header; applications include only this
├── kernel/ <- the portable core: scheduler, sync and IPC, timers, memory,
│ log. Plain C11, no CPU knowledge anywhere in it
├── arch/ <- the port layer, <family>/<core>: arm/ and riscv/ today.
│ Everything a CPU changes is confined here
├── soc/ <- optional per-silicon packages, <vendor>/<family> (see doc/soc.md)
├── template/ <- the files you copy into your project: os_config.h, os_cb.c,
│ os_main.c, plus soc_cb.c when no SoC package covers the part
├── test/ <- the self-test suite, built as its own os_test library
├── examples/ <- one runnable main per feature
├── doc/ <- every documentation page, including the per-SoC ones
├── tools/ <- the four install_*.py, and nothing else you need to open.
│ Each is a small bootstrap: it locates an AhuraRTOS checkout -
│ downloading one if it has to - then loads the engine and the
│ vendor descriptor out of tools/internal/. Adding a platform
│ is one file there; the engine and the bootstraps do not change
├── CSTYLE.md <- the C style every file here is written to
├── LICENSE
└── README.md <- this file
The three axes the layout separates are the three questions a port asks:
kernel/ never changes, arch/ changes with the instruction set, soc/
changes with the chip.
One repository, no submodules - a plain clone gives you everything:
git clone https://github.com/AhuraRTOS/AhuraRTOS.gitContributions are welcome. Kernel work, new ports, testing, and documentation all help. Open an issue or submit a pull request.
GNU General Public License v3.0 or later. See LICENSE.
