Bare Metal vs RTOS

Bare-Metal Applications 

Embedded applications monitor and respond to those events based on the functional requirements of the system.  One of the key choices the designer has in designing an application is to choose between using an operating system to manage the application or to forego the operating system and develop what is called a bare-metal application.   

A bare-metal application is written without the aid of an operating system like FreeRTOS or Linux.  This type of application has no scheduling support or constructs such as queues, semaphores, or tasks.  In a bare-metal application, there is a minimal amount of abstraction used to configure and interact with hardware such as timers, GPIO ports, and serial devices. 

The main() function of a bare-metal application is commonly an infinite loop that detects events in the system and responds to each event based on the functional requirements of the system. This is a form of what could be called Manual Scheduling: the deliberate structuring of code to control the timing and sequencing of events.  A developer uses constructs like loops, flags, and state machines to control the behavior of the application. This approach offers full transparency: the flow of execution is entirely predictable and dictated by the logic you implement in your code.  The code fragment below is an example of how to implement Manual Scheduling in what is commonly called a ‘mega-loop’. 

Using Manual Scheduling in a bare-metal application offers several distinct advantages.   

  • Performance: With no operating system overhead, the application runs with efficient execution and precise timing control. 
  • Memory Footprint: Ideal for ultra-low-power or resource-constrained devices, this approach reduces flash and RAM usage. 
  • Simplicity: Your code behaves exactly as designed. You maintain full control over execution without the need to interface with complex software layers or abstractions. 

While bare-metal applications are an attractive option, they does have some disadvantages that become more pronounced as the complexity of an application grows. 

  • Scalability Issues: As your application grows, maintaining a mega loop with many conditional branches becomes difficult. Adding new events often means reorganizing large portions of the loop. 
  • Responsiveness Constraints: Long-running activities can delay time-sensitive operations unless carefully managed with timers or interrupt-driven logic. 
  • Code Readability & Maintainability: Manual scheduling often relies on flags, state machines, and nested conditionals, which can become hard to follow  
  • Poor Modularity: Activities are tightly coupled within the loop, making reuse across projects or platforms more difficult. 
  • Manual Timing Control: All timing (delays, timeouts, periodic execution) must be crafted manually, often via hardware timers — leaving more room for subtle timing bugs. 

 Operating System (OS) 

An operating system (OS) is a collection of low-level software that manages the hardware resources of a computing device. It provides well-defined interfaces for controlling physical components within a microprocessor, including memory management, access to peripheral devices, and task scheduling. By exposing a standardized set of libraries and functions, the OS enables software developers to interact with hardware without delving into low-level implementation details. This abstraction allows designers to focus on their project’s specific software requirements, streamlining development and improving maintainability. 

An operating system (OS) enables applications to be composed of multiple tasks, each with its own execution context. A task’s context includes the general-purpose registers (used for calculations and data manipulation), the stack pointer (which tracks the top of the call stack), the flags register (used to guide conditional execution and control flow), and the program counter (which holds the address of the next instruction to execute). 

To support multitasking, the OS can pause a task by saving its context—typically into SRAM—and resume another by restoring its previously saved context into the processor’s core registers. This allows the system to rapidly switch between tasks, giving the illusion of concurrent execution even on a single-core microcontroller. 

Tasks can interact using synchronization constructs such as semaphores, message queues, and shared memory. This task-based architecture promotes modularity: developers can implement smaller, well-defined tasks that are easier to understand, test, and maintain. It also facilitates collaboration, as larger, more complex applications can be partitioned and developed in parallel by multiple engineers. 

Real-Time Operating System (RTOS) 

A real-time operating system (RTOS) is designed to enforce strict timing guarantees for task execution. It’s commonly used in applications where responses to events must be completed within a predefined timeframe—otherwise, the system could fail or behave unpredictably. 

In contrast, general-purpose operating systems like Windows or iOS do not guarantee response times. When launching an application, the startup duration can vary widely depending on system load, memory usage, and competing processes. In everyday use—say, waiting a few extra seconds for a word processor to open—is tolerable.  These systems are often considered to be soft real-time. In soft real-time systems, meeting timing requirements is important, but occasional delays are tolerable. These systems prioritize responsiveness but don’t fail catastrophically if a deadline is missed. 

But in time-critical scenarios, such as deploying an airbag in a vehicle during a collision, delays are unacceptable. Commercial operating systems aren’t equipped to meet such deterministic timing constraints. That’s where an RTOS becomes essential: it provides guarantees that the system will respond within strict, predefined limits, ensuring reliable operation under all conditions. 

Manual Scheduling vs RTOS 

One of the most critical design choices for embedded firmware developers is determining how to manage task execution. Given the resource constraints of microcontrollers, full-featured operating systems like Windows or Linux are unsuitable—they demand more computational power and memory than these platforms can provide. Instead, developers typically choose between Manual Scheduling (often via a “mega loop”) and a Real-Time Operating System (RTOS) such as FreeRTOS. 

The table below summarizes the strengths and trade-offs of both approaches: 

Feature  Manual Scheduling (Mega Loop)  RTOS (e.g., FreeRTOS) 
Execution Control  Developer explicitly controls operation order and timing  RTOS uses scheduler to manage task execution 
Multitasking  Operations run sequentially  Multitasking with priority-based scheduling 
Responsiveness  Depends on careful design and interrupt use  High responsiveness with deterministic task handling 
Code Complexity  Grows quickly with more features  Better task modularity
Timing Precision  Manual delays and counters Built-in tick handling and timer APIs 
Resource Footprint  Minimal; no OS overhead  Higher; needs RAM/ROM for kernel and task stacks 
Modularity & Reuse  Limited; logic is tightly coupled  High; tasks and services can be reused 
Learning Curve  Easier to start; ideal for simple systems  Steeper, but better for scaling and real-time control 

For most of the applications developed in this course, we’ll be using FreeRTOS. While it introduces some additional complexity, the advantages in modularity, responsiveness, and real-time control make it the preferred choice for real-world embedded systems.