Side-by-side comparison
| Parameter | Bare Metal | RTOS Programming |
|---|---|---|
| Execution Model | Single super-loop or state machine; one thread | Multiple tasks with separate stacks; preemptive/cooperative scheduling |
| Scheduler | None — developer controls all timing | Priority-based preemptive (FreeRTOS, Zephyr) or cooperative |
| Flash Overhead | Zero OS overhead | FreeRTOS kernel: ~5–10 kB flash (Cortex-M minimal config) |
| RAM Overhead | Global/stack variables only | Each task needs stack (min 128 words); FreeRTOS heap ~2 kB minimum |
| Task Isolation | None — all code shares one stack | Each task has independent stack; stack overflow detection optional |
| Inter-task Communication | Global variables (no synchronisation primitives) | Queues, semaphores, mutexes, event groups (FreeRTOS API) |
| Real-Time Determinism | Deterministic if single task; shared ISR disrupts loop timing | Priority-based — highest-priority ready task always runs within tick period |
| Debugging Complexity | Simple — linear execution, UART printf works | Harder — race conditions, stack overflow, priority inversion possible |
| Tick Resolution | Software delay loops or hardware timer | Configurable: FreeRTOS default 1 ms; configurable to 100 µs |
| Example Target | PIC16F877A, ATmega328 (Arduino), STM32F0 | STM32F4/H7 with FreeRTOS, ESP32 with FreeRTOS, Zephyr on nRF52840 |
Key differences
Bare-metal super-loop gives zero scheduling overhead — the entire chip resources run your code. The problem: if two tasks need different time slices (IMU at 1 kHz, Bluetooth at 10 Hz), hand-rolled timing with flags and timers becomes fragile and unmaintainable above about four concurrent activities. FreeRTOS on an STM32F4 uses 5 kB flash and 2 kB RAM for the kernel, creates tasks with independent 512-word stacks, and ensures a 100 µs-period IMU task always preempts a 10 Hz Bluetooth task deterministically. Priority inversion — where a low-priority task blocks a high-priority one via a shared resource — is a real RTOS pitfall that bare-metal avoids entirely by having no scheduler, but also avoids the problem by having no concurrent tasks at all.
When to use Bare Metal
Use bare-metal programming for simple, single-concern devices with low task count and tight resource constraints. Example: a gas sensor alarm on a PIC16F1937 (8 kB flash, 512 B RAM) reads an ADC, compares to a threshold, and drives a buzzer — a 150-line super-loop with no OS overhead is the right and only sensible choice.
When to use RTOS Programming
Use RTOS when three or more concurrent activities with different timing requirements must be managed deterministically. Example: an STM32F407 drone ESC controller runs four FreeRTOS tasks: IMU sampling (1 kHz, priority 4), PID control (500 Hz, priority 3), telemetry (10 Hz, priority 2), and CLI (priority 1), all managed by FreeRTOS with 10 µs tick resolution.
Recommendation
For fewer than three concurrent tasks or severely resource-constrained MCUs (< 8 kB RAM), choose bare-metal — it is faster to code, easier to debug, and wastes nothing. For three or more concurrent activities with different timing requirements, choose FreeRTOS or Zephyr. The overhead of FreeRTOS (5 kB flash, 2 kB RAM) pays back immediately in maintainability and correctness once complexity crosses that threshold.
Exam tip: Examiners ask students to list three FreeRTOS inter-task communication primitives and their use cases — know: queue (data transfer), binary semaphore (event signalling), mutex (shared resource exclusive access with priority inheritance).
Interview tip: An embedded systems interviewer will ask you to explain priority inversion and how FreeRTOS handles it — state that a low-priority task L holds a mutex needed by high-priority task H; medium-priority M runs instead of L because M > L, starving H; FreeRTOS mutexes implement priority inheritance, temporarily raising L's priority to H's until the mutex is released.