Electronics Design AU
Firmware

RTOS

Real-time operating systems for embedded systems: FreeRTOS, Zephyr, task scheduling, and inter-task communication.

Real-time operating systems bring structured multitasking to embedded firmware, but the implementation details of task priorities, inter-task communication, and memory management determine whether that structure improves reliability or introduces new failure modes. This subtopic covers FreeRTOS, the most widely deployed embedded RTOS, at the implementation level.

What Is an RTOS, Implementation-Wise?

An RTOS turns a single CPU into what looks like several independently scheduled threads (tasks), each with its own stack and priority, coordinated through primitives like queues, semaphores, and mutexes. This subtopic covers how to actually build firmware on top of that model: creating and sizing tasks, choosing the right inter-task communication primitive, managing RTOS heap memory, and debugging the failure modes specific to preemptive multitasking. For the higher-level decision of whether to use an RTOS at all, see What Is an RTOS? and Bare-Metal vs RTOS in the parent Firmware topic.

This subtopic is part of the Firmware topic.

Why RTOS Implementation Details Matter

  • Priority inversion causes real-time deadline misses that are hard to reproduce — a low-priority task holding a resource a high-priority task needs can stall the whole system if the wrong synchronisation primitive is used; the bug often disappears under a debugger's timing.
  • Stack overflow corrupts memory silently — an undersized task stack overwrites adjacent RAM without any hardware fault, producing symptoms far from the actual cause unless overflow detection is enabled from the start.
  • Heap fragmentation surfaces only after days or weeks of uptime — a product that allocates and frees RTOS objects dynamically can pass every short test cycle and still fail in the field once fragmentation accumulates.
  • Choosing the wrong communication primitive adds unnecessary complexity or latency — queues, semaphores, mutexes, task notifications, and event groups solve overlapping problems, and picking the heaviest one by default costs RAM and CPU cycles for no benefit.

Key Concepts

  • Task and priority — an RTOS task is a software thread with its own stack and a priority the scheduler uses to decide which ready task runs. Higher-priority tasks preempt lower-priority ones.
  • Tick and preemption — the scheduler tick (a periodic timer interrupt) drives time-slicing between equal-priority tasks and re-evaluates readiness after every blocking call and interrupt.
  • Queue, semaphore, mutex, task notification, event group — the five FreeRTOS inter-task communication primitives, each suited to a different problem: data transfer (queue), signalling (semaphore), mutual exclusion with priority inheritance (mutex), a lightweight per-task signal (notification), or waiting on multiple conditions (event group). See FreeRTOS queues, semaphores, and mutexes.
  • Priority inheritance — a mutex-specific mechanism where a low-priority task holding the mutex is temporarily boosted to the priority of the highest-priority task blocked on it, preventing unbounded priority inversion. Binary semaphores have no such mechanism.
  • Heap models (heap_1–heap_5) — FreeRTOS ships five reference heap allocators trading off fragmentation handling, code size, and support for multiple discontiguous RAM regions. See FreeRTOS heap memory management.
  • Stack high-water mark — the closest a task's stack has come to overflowing during execution, reported by uxTaskGetStackHighWaterMark(); the standard way to size task stacks correctly rather than guessing.

Common Tools and Software

  • SEGGER SystemView — real-time, non-intrusive task and interrupt timeline tracing over J-Link RTT; the standard tool for diagnosing deadlocks, priority inversion, and unexpected task behaviour with real timing data rather than breakpoint snapshots.
  • vTaskList() / vTaskGetRunTimeStats() — built-in FreeRTOS diagnostics that dump task state (Running/Ready/Blocked/Suspended), stack high-water marks, and CPU usage per task to a debug console.
  • Percepio Tracealyzer — a commercial alternative to SystemView with deeper visualisation and analysis of RTOS-level traces, used on larger projects where the free SystemView workflow is insufficient.
  • STM32CubeMX FreeRTOS configuration — generates FreeRTOSConfig.h and initial task scaffolding for STM32 projects, though heap sizing and stack sizes still require field validation against high-water marks.

Common Questions

Should I use FreeRTOS or a different RTOS like Zephyr?

FreeRTOS is the better default for adding real-time scheduling to an existing bare-metal codebase or a simpler MCU-based product: it is small, well-documented, and available on nearly every MCU vendor's HAL. Zephyr is a fuller embedded OS (RTOS plus networking stack, device driver model, and Bluetooth/Thread stacks) and is the better choice for connectivity-heavy platforms where the SDK already standardises on it (Nordic's nRF Connect SDK, for instance). Migrating between the two mid-project is a substantial rewrite: the choice is easiest to make correctly at project start based on which SDK the target platform's connectivity stack requires. See What Is Zephyr RTOS, and How Is It Different from FreeRTOS? for the device tree, Kconfig, and kernel differences in full.

How many RTOS tasks is too many?

There's no fixed number: the practical limit is RAM (each task's stack is reserved for the life of the task) and design clarity, not a FreeRTOS restriction. A common failure pattern is one task per peripheral or feature "for organisation," which multiplies RAM usage and inter-task communication complexity without a real concurrency need. A task should represent a genuinely independent, concurrently-schedulable unit of work; sequential steps within one workflow usually belong in a single task's state machine, not separate tasks synchronised by more primitives than the problem needs.

Should I use static or dynamic allocation for RTOS objects?

For safety-critical or long-uptime products, prefer static allocation (xTaskCreateStatic(), xQueueCreateStatic(), and equivalents) for tasks, queues, and semaphores created at startup: it removes heap fragmentation as a failure mode entirely and makes worst-case RAM usage knowable at compile time. Dynamic allocation is reasonable for objects created and destroyed at a low, bounded rate during runtime (rare in most embedded designs), but a product that dynamically creates and deletes RTOS objects throughout its operating life should be validated for fragmentation over realistic multi-day soak tests, not just short bench runs. Zeus Design designs FreeRTOS and Zephyr firmware architecture for embedded products.

Knowledge Base

Zephyr Fundamentals

Task Management and Scheduling

Inter-Task Communication

Memory Management

Debugging

Power Management

Forum Discussions

Forum Discussions

Related Topics