Embedded

RTOS for Embedded Systems

By CourseTron Team ·  8 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: An RTOS is layered onto an embedded system to turn one CPU into many independent, prioritised "tasks" whose execution the kernel switches between deterministically, so that time-critical work always meets its deadline while slower background work still gets served.

    Ask any firmware engineer why they reached for an RTOS and the honest answer is rarely "I needed threads." It is almost always about time. A bare-metal superloop that polls sensors, services a UART and drives a motor eventually hits a wall: one slow branch blocks everything behind it, and your 1 kHz control loop starts missing its 1 ms window. A real-time operating system is the software layer that solves this by letting you decompose the firmware into concurrent tasks and handing the kernel the job of deciding, every few microseconds, which one runs next.

    Why layer an RTOS onto an embedded system at all?

    Consider a battery-management board on a Cortex-M4 running at 80 MHz. It must sample cell voltages every 100 ms, run a safety shutoff within 2 ms of an over-current interrupt, answer a CAN diagnostic within 50 ms, and log to flash whenever it is idle. These four jobs have wildly different urgencies. In a superloop you would hand-interleave them and pray; with an RTOS you declare four tasks with four priorities and let a preemptive, priority-based scheduler guarantee the 2 ms safety task always wins. That guarantee — bounded, predictable latency — is the entire value proposition. "Real-time" does not mean "fast"; it means on time, every time.

    A hard real-time system (airbag, motor commutation) treats a missed deadline as a system failure; a soft real-time system (audio buffer, UI) merely degrades. The RTOS does not make a system hard — your worst-case analysis does — but it gives you the preemption and prioritisation needed to prove the deadline is met.

    Where the RTOS sits in the stack

    The RTOS is a thin layer — FreeRTOS, Zephyr or ThreadX typically add only 6–12 KB of code — between your application tasks and the silicon. Below it sit the CPU core, the interrupt controller (the NVIC on ARM Cortex-M) and your drivers; above it live your tasks. The kernel owns the stack pointer, the system tick timer and the ready list.

    Where the RTOS sits in the stack Application tasks (sample, safety, CAN, logger) RTOS kernel: scheduler, tick, queues, mutexes HAL / drivers and NVIC interrupts MCU core, timers, peripherals (Cortex-M) CourseTron · coursetron.com
    The RTOS as a thin brokering layer between your tasks and the hardware.

    Tasks: the unit of concurrency

    A task is just a C function that never returns, plus its own stack and a Task Control Block (TCB) the kernel uses to remember its state. Each task is, at any instant, in one of a few states: Running (the one task actually on the CPU), Ready (eligible but out-ranked), Blocked (waiting on a timeout, queue or semaphore), or Suspended. Creating one in FreeRTOS is a single call:

    • xTaskCreate(vSafetyTask, "safety", 256, NULL, 5, NULL); — note the stack depth (256 words = 1 KB) and priority 5.
    • A task should spend most of its life Blocked. A task that busy-waits instead of blocking starves everything beneath it — the single most common RTOS bug for beginners.

    Sizing stacks matters because the kernel cannot grow them: a 256-word stack that overflows corrupts the adjacent TCB silently. Tools like Percepio Tracealyzer, or the high-water-mark API, tell you how close you are running to the edge.

    Context switching: how the CPU changes its mind

    Only one task occupies the single core at a time, so switching is the kernel's central trick. On every system tick (commonly 1 ms, driven by the SysTick timer) — or the instant a higher-priority task becomes Ready — the scheduler performs a context switch: it pushes the current task's CPU registers (R0–R12, the PC, the stack pointer, the status register) onto that task's stack, saves the stack pointer into its TCB, loads the next task's stack pointer, and pops its registers back. On Cortex-M this is done efficiently in the PendSV exception handler, and the hardware even stacks part of the register file for you.

    Context switch: the CPU handed between tasks Task A (prio 2) Task B (prio 5) save R0-R12, PC, SP restore R0-R12, PC, SP PendSV on tick / preempt CPU CourseTron · coursetron.com
    The single CPU is handed back and forth; each handoff saves one task's registers and restores the other's.

    A context switch on a Cortex-M4 typically costs 80–200 CPU cycles — roughly 1–2.5 µs at 80 MHz. That overhead is why you pick a tick rate carefully: a 10 kHz tick wastes cycles switching, while a 100 Hz tick gives coarse timing. 1 kHz is the common compromise.

    Inter-task communication: talking without stepping on each other

    Once work is split across tasks, those tasks must share data — and two tasks touching one buffer is a race condition waiting to corrupt it. An RTOS gives you safe primitives instead of raw globals:

    PrimitiveUse it forClassic hazard it prevents
    Queue / mailboxPassing copies of data (ISR → task)Torn reads of multi-byte values
    Binary semaphoreSignalling an event happenedPolling / busy-waiting
    Counting semaphoreManaging a pool of N resourcesOver-allocation
    Mutex (with priority inheritance)Guarding a shared peripheralPriority inversion

    The mutex deserves a word. If a low-priority task holds a lock the high-priority safety task needs, the high task is blocked by the low one — priority inversion, the fault that famously stalled NASA's Mars Pathfinder in 1997. A proper RTOS mutex applies priority inheritance: the lock-holder is temporarily promoted to the waiter's priority so it finishes and releases quickly. Queues, by contrast, are the workhorse for interrupt-to-task handoff — an ISR does the bare minimum (read the register, xQueueSendFromISR) and defers the real work to a task, keeping interrupt latency tiny.

    Priority inversion timeline H needs lock lock released High (H) Medium (M) Low (L) H blocked on mutex H runs M runs (preempts L) L holds lock L runs Inversion: H waits while M runs CourseTron · coursetron.com
    Priority inversion: M preempts the lock-holding L, so H stays blocked until L finishes and releases the mutex.

    Designing with deadlines in mind

    Layering an RTOS well is a design discipline, not a library call. You assign priorities by urgency (rate-monotonic ordering is a good default: shorter period → higher priority), keep ISRs short, let tasks block rather than spin, and verify worst-case response with trace tools or a logic analyzer. The same rigour that underpins chip-level timing closure in tools like Synopsys PrimeTime applies here in miniature: you are closing timing on software. If you are building production firmware or silicon around these ideas, the design-services team at Avecas works on exactly this embedded-to-silicon boundary.

    To go deeper on scheduler theory, start with our primer what is an RTOS, then compare the trade-offs in bare-metal vs RTOS firmware. Both build directly on the concepts above.

    Ready to write real firmware, not just read about it? The CourseTron Embedded Systems & RTOS course walks you from your first xTaskCreate through priority inheritance and trace-based debugging on real Cortex-M hardware.

    Frequently asked questions

    Do I always need an RTOS for an embedded project?

    No. If your firmware has one dominant loop and no competing deadlines, a well-structured superloop with interrupts is simpler and smaller. Reach for an RTOS when you have multiple independent activities with different timing requirements that are hard to interleave by hand.

    What is the difference between a task and a thread here?

    In most small RTOSes they are the same thing: a schedulable flow of execution with its own stack. The term "thread" (as in Zephyr or ThreadX) and "task" (as in FreeRTOS) are interchangeable. Unlike an OS process, they share one address space — there is usually no memory isolation unless an MPU is configured.

    How does the RTOS decide which task runs?

    A preemptive RTOS always runs the highest-priority Ready task. If two share a priority, it time-slices them round-robin on each tick. A task only yields the CPU when it blocks, is preempted by a higher-priority task becoming Ready, or its slice expires.

    What causes priority inversion and how is it fixed?

    It happens when a high-priority task waits on a resource held by a low-priority task that is itself preempted by a medium task. The standard fix is priority inheritance (temporarily boosting the holder) or the priority-ceiling protocol, both offered by RTOS mutexes.

    Advertisement