Bare-Metal vs RTOS: Which Should You Use for Your Firmware?
Last updated 26 June 2026 · 6 min read
Direct Answer
Bare-metal firmware runs a single main loop with interrupt handlers and no operating system layer; it is simpler and lower overhead, making it the right choice for single-purpose tasks with clear, simple concurrency. An RTOS adds a task scheduler, queues, and semaphores, which is worthwhile when a system has three or more independent activities with different timing requirements that would be complex to coordinate manually — the overhead is a few kilobytes of RAM and some added scheduling latency.
Detailed Explanation
The choice between bare-metal and RTOS is a firmware architecture decision, not a capability question — both approaches can produce reliable, production-grade embedded software. The right answer depends on how many independent activities your application has, how tightly their timing requirements interact, and how complex the coordination between them is.
What is bare-metal firmware?
Bare-metal firmware runs directly on the hardware with no operating system layer. The typical structure is a superloop — an infinite while(1) loop in main() that polls for work and calls handler functions — combined with interrupt service routines (ISRs) that respond to hardware events asynchronously.
int main(void) {
hardware_init();
while (1) {
if (uart_data_ready()) process_uart();
if (sensor_timer_elapsed()) read_sensor();
if (button_pressed()) handle_button();
enter_low_power_if_idle();
}
}
The superloop is simple, transparent, and uses zero RAM beyond what the application needs. Timing is managed by the programmer explicitly — poll intervals, flags set in ISRs, and careful ordering of operations.
What is RTOS-based firmware?
RTOS firmware replaces the superloop with a task scheduler. Each independent activity becomes a task — a function that runs in its own infinite loop, with its own stack, blocked when it has nothing to do.
void sensor_task(void *params) {
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // blocked until notified
sensor_value = read_sensor_i2c();
xQueueSend(data_queue, &sensor_value, 0);
}
}
The RTOS kernel decides which task runs, suspends blocked tasks so they consume zero CPU, and wakes them when their event arrives. Tasks communicate via thread-safe primitives: queues, semaphores, mutexes, and event groups.
When bare-metal is the right choice
| Indicator | Why bare-metal suits it |
|---|---|
| One or two main activities | A superloop handles them cleanly without scheduling overhead |
| Very small MCU (< 16 kB RAM) | RTOS RAM overhead may not leave enough room for the application |
| All timing driven by a single timer or interrupt | No concurrency coordination required |
| Team unfamiliar with RTOS concepts | Bare-metal is easier to reason about without RTOS debugging experience |
| Safety-critical code needing formal analysis | Bare-metal with deterministic ISRs is often easier to certify than an RTOS |
A battery-powered temperature logger that wakes on a timer, reads a sensor, writes to flash, and sleeps again is a textbook bare-metal application — it does exactly one thing in a predictable sequence.
When an RTOS is the right choice
| Indicator | Why RTOS suits it |
|---|---|
| Three or more independent activities with different timing | Manually coordinating them in a superloop becomes complex and fragile |
| Activities that block (wait for network, flash write, UI input) | RTOS blocking is efficient; polling wastes CPU and complicates timing |
| Wireless protocol stack (BLE, Wi-Fi) | Most BLE/Wi-Fi stacks (ESP-IDF, Zephyr) are RTOS-native and assume task-based concurrency |
| Multiple developers on one firmware codebase | RTOS tasks partition ownership cleanly |
| Power management with multiple wake sources | RTOS idle hooks integrate cleanly with sleep modes |
A BLE-connected device reading three sensors, maintaining a cloud connection, processing user commands, and logging data to flash simultaneously is a strong RTOS candidate — the concurrency and blocking behaviour would be painful to manage in a superloop.
The RAM cost of an RTOS
FreeRTOS on ARM Cortex-M4 with a minimal configuration adds roughly 5–10 kB of flash for the kernel and requires approximately 300–500 bytes of RAM for the kernel itself plus the size of each task's stack. A realistic system with four tasks and 512-byte stacks each adds about 4–6 kB of RAM overhead on top of application data. On an STM32 with 64 kB or more of SRAM, this is comfortable. On a Cortex-M0+ with 8 kB, it may leave too little room — RAM budget is one reason MCU selection and firmware architecture should be decided together.
Practical Examples
Bare-metal suits a smart plug controller: one main loop samples the current sensor at 50 Hz, integrates power usage, and responds to button presses. Two ISRs handle the mains zero-cross detection and a UART for configuration. The entire firmware is 12 kB of flash and 2 kB of RAM — an RTOS would consume more resources than the application logic itself.
RTOS suits a connected industrial monitor: a Modbus RTU reader, a 4G cellular modem driver, a local SD card logger, and a watchdog heartbeat task all run concurrently. The Modbus task blocks waiting for RS-485 responses; the cellular task blocks on HTTP responses. Without an RTOS, ensuring both can block without starving each other requires a complex state machine that the RTOS scheduler handles for free.
Design Considerations
- Don't add an RTOS pre-emptively: start with bare-metal and add an RTOS when you encounter a specific concurrency problem that's genuinely hard to solve without one. Retrofitting an RTOS later is manageable; removing one is less common. The community discussion FreeRTOS vs bare-metal for a simple sensor node explores exactly this decision — when a working super-loop is the right answer, and what actually changes the calculation.
- ISRs remain outside the RTOS: whether you use an RTOS or not, time-critical hardware responses still belong in ISRs at interrupt priority — not in RTOS tasks. The RTOS manages tasks, not interrupts.
- RTOS-aware tooling: FreeRTOS integrates with SEGGER SystemView, Tracealyzer, and J-Link RTT for task-level profiling. These tools are essential for diagnosing priority inversion, stack overflow, and missed deadlines in production firmware.
- Firmware architecture review: choosing the right architecture upfront saves weeks of painful refactoring. Zeus Design's embedded software team regularly advises on and implements both bare-metal and RTOS-based firmware designs across STM32, ESP32, and nRF platforms.
Common Mistakes
- Solving a complexity problem with an RTOS when the real problem is poor code structure: if your bare-metal superloop is unmanageable, the root cause is often tangled logic, not missing task scheduling. Refactoring to a cleaner state-machine-based superloop may be all that's needed.
- Calling blocking RTOS functions from ISRs: ISRs must use ISR-safe RTOS API calls (e.g.
xQueueSendFromISRin FreeRTOS). Calling the standard blocking versions from an ISR corrupts the RTOS scheduler state. - Underestimating stack sizes: each RTOS task needs its own stack. Stack overflows in RTOS systems are a common source of hard-to-reproduce crashes. Enable FreeRTOS stack overflow detection during development and measure high-water marks before finalising stack sizes.
- Treating RTOS priorities as arbitrary: assigning priorities without thinking about which tasks have the tightest timing requirements leads to priority inversion and missed deadlines. Map priority levels to timing requirements explicitly.
Frequently Asked Questions
- Can you mix bare-metal and RTOS approaches in the same project?
- Yes, and it is common. Time-critical ISRs always run outside the RTOS scheduler, at interrupt priority — they never sleep or block. The RTOS manages application-level tasks (sensor reads, comms, logging), while ISRs handle hardware events with the shortest possible response time and signal tasks via queues or semaphores.
- Is an RTOS harder to debug than bare-metal?
- Often yes, initially. Debugging a deadlock caused by two tasks competing for the same mutex, or a priority-inversion problem, requires understanding the RTOS state at the time of the bug. Most RTOS-aware debuggers (Ozone, J-Link, SystemView) can display task states, stack usage, and scheduling history, which makes RTOS debugging tractable once you have the right tools. Bare-metal bugs — usually ISR timing and shared state — can be just as subtle.
- Does using FreeRTOS lock me into ARM?
- No. FreeRTOS has been ported to dozens of processor architectures including Cortex-M, Cortex-A, AVR, PIC32, RISC-V, and Xtensa (ESP32). Zephyr supports an even wider range. Switching RTOS or switching MCU families while keeping the same RTOS is feasible — the application code built on RTOS primitives (tasks, queues, semaphores) is broadly portable.
References
Related Questions
What Is an RTOS (Real-Time Operating System)?
An RTOS is a lightweight operating system that gives embedded firmware deterministic task scheduling. Learn how RTOSes work and when you actually need one.
How Do You Create and Schedule Tasks in FreeRTOS?
Learn how to create FreeRTOS tasks with xTaskCreate, configure task priorities, size stacks safely, and start the scheduler on ARM Cortex-M MCUs.
How Do FreeRTOS Queues, Semaphores, and Mutexes Work?
How to use FreeRTOS queues, semaphores, and mutexes for inter-task communication — including ISR-safe variants, task notifications, and event groups.
What Is a Microcontroller (MCU)?
A microcontroller (MCU) combines a CPU, flash, RAM, and peripherals on one chip. Learn how MCUs work and how they differ from microprocessors and FPGAs.
What Are Interrupts in Embedded Systems and How Do They Work?
Interrupts let a microcontroller respond to hardware events instantly without polling. Learn how ISRs, NVIC priority, and interrupt latency work.
How Do You Choose the Right Microcontroller for Your Project?
Choosing the right MCU comes down to peripherals, memory, power, wireless needs, and toolchain. This guide walks through every factor with concrete examples.