Choosing an Operating System for Your Embedded Project: Bare-Metal, RTOS, or Linux?

When starting to work on a new embedded system, you often face a dilemma: what kind of operating system should be used? Or should the operating system be skipped and just go bare-metal in the project? In this blog post, I’ll go through the common alternatives with an example scenario to give an idea of what kinds of things to consider when deciding the base of an embedded system.

 

The operating system types

First, let’s define the common operating system types. What different kinds of approaches to operating systems exist for embedded devices?

 

General-purpose operating systems

In practice, “general-purpose operating system” in an embedded system means some flavor of Linux. Whether you’re self-building a distro from scratch with Yocto, or utilizing a pre-existing project like Android Open Source Project, Linux is the go-to choice for general-purpose operating systems. It is open source, configurable, and modular, meaning that the general-purpose OS can be tailored into quite a specific operating system if required.

General-purpose operating systems are the same operating systems that run on desktops, laptops and smartphones. They usually don’t have a single feature they’d be great at, but their strength is the fact that they can be used for many types of tasks simultaneously. A general-purpose operating system is a good choice for an embedded system that has a lot of processing power and is expected to handle a wide variety of tasks. There are plenty of pre-existing libraries and binaries for almost any problem imaginable. The downside is that maintaining such an operating system takes a lot of effort as it is complex, and it requires really powerful hardware to run.

Real-time operating systems

With real-time operating systems you have more to choose from. With Linux systems you can choose the distribution, but with real-time operating systems there are plenty of different operating systems that can be quite different from one another. There are free open-source solutions, like minimal FreeRTOS and more feature-rich Zephyr, and closed-source options like VxWorks and QNX. The different alternatives should be carefully considered, as operating systems have different features, certifications, and licenses, and changing from one to another is not always straightforward.
The strength of the RTOS is in its scheduling. As the name implies, real-time operating systems usually focus on meeting timing requirements, ensuring that critical tasks get handled within given deadlines. Compared to general-purpose operating systems, the rest of the feature set is usually quite limited, but often still sufficient for an embedded system.

Bare-metal

Finally, there is the bare-metal option that technically is not an operating system at all. As the name implies, it consists of programming the functionality directly without an operating system. There may still be a hardware abstraction layer so that the developer won’t have to write everything at a hardware level. Also, additional software stacks may be used to add functionality like networking or wireless protocols. In general, the system is still developed from scratch.

A bare-metal system can be more cumbersome to develop, especially if the project grows, but it also allows an extremely focused system. It is most suitable for devices with tight size, computing power, or energy constraints. Bare-metal also works well for projects with really simple and limited functionality, as there is no complexity of an operating system. It is also a good option if you are developing brand-new hardware, as it may be simpler to go bare-metal than to try to port an entire RTOS (not to mention Linux) to run on the hardware.

 

Example to highlight the differences

The differences between the different types of systems are perhaps best illustrated with an example. Let’s assume that we have a temperature sensor, and we want to connect this temperature sensor to our embedded system using some form of serial communication. Then, we want to read the values from the sensor. Let’s consider how this could be done with different kinds of systems. We shall go through the system types in reverse order starting from the least complex approach, bare-metal.

 

Bare-metal

In the bare-metal system, the developer would create a separate driver file for the sensor, and do the sensor interfacing there. Depending on the used stacks, hardware abstraction layer, and the hardware itself, they can either use simple function calls to read and write to the serial, or in the worst case they will have to implement the serial communication down to the physical level themselves.
In addition to the driver, the developer would also want to make a main program file that uses the driver to read the sensor values. The interface between the main program and driver is not pre-defined, so it’s the developer’s task to define and create this interface.
There is a lot of work and decisions to be made with the bare-metal approach. However, it allows complete freedom with the system, allowing the most efficient choices to be made. The developer can, at least in theory, understand the workings of the whole system, but this depends on the amount of used software stacks and how much magic happens under the hood of the hardware abstraction layer. Also, while the bare-metal approach theoretically allows the most efficient choices, it also allows making big mistakes that may be difficult to fix later on.

 

Real-time operating system

With real-time operating systems, the development process depends heavily on the chosen RTOS. For example, with FreeRTOS, the process is close to bare-metal development. On the other hand, development with Zephyr is quite similar to Linux development.
So if the developer is using FreeRTOS, the biggest changes would be in task scheduling functions. The driver itself would still be using the same method for serial communication as it did before, and there is still no pre-defined interface for communication with the driver. With Zephyr, the developer would write the driver to fit the driver framework of Zephyr, and then write the main program to communicate with the driver using interfaces defined by Zephyr.
This illustrates that the real-time operating systems can be quite different from one another. The major defining feature is the deterministic timing and scheduling promises it makes. Usually, there is some additional learning and integration compared to bare-metal systems. Depending on the chosen RTOS, the amount of extra work varies.
With an RTOS that defines the APIs like Zephyr, some freedom is lost (and OS overhead is increased). On the other hand, standardized and reviewed functionality is gained. The developer can’t define the interfaces and the code may be a bit less efficient, but porting is easier and the risk of disastrous design choices is smaller. With an RTOS like FreeRTOS the pros and cons are mostly the same as with bare-metal programming, with the additional benefit of better task scheduling.

 

General-purpose operating system

The general-purpose OS development is quite similar to the Zephyr example above. The developer creates a driver that fits the driver framework(s) of the operating system, and then separately creates the program that utilizes the driver interface to read values from the driver. This program then runs as its own process in the operating system.
The main difference compared to the other types of systems once again is not really in the development flow itself. It is the fact that in a general-purpose operating system you may have dozens of processes running simultaneously. The “main program” the developer creates is just a small cog in a huge machine. In the real-time operating systems, and especially in bare-metal, the main code is tightly coupled with the OS, and it is the driving force of the machine.
As a side note, it is worth mentioning that Linux can now be built to provide real-time capabilities, further blurring the lines between the different types of operating systems.

 

Choosing the system type

 

Then comes the difficult choice: selecting the type of operating system. Which one is the most suitable for my embedded project?

It may be helpful first to consider the bare-metal system. Does my project have special requirements that warrant selecting it? Is it novel hardware, or does it have some requirements that cannot be satisfied with an RTOS? Or perhaps it is a simple, one-off project that could be quickly done with bare-metal? Usually, if there is no reason to go bare-metal, it is better to use some kind of operating system, as the operating system can provide some guard rails.

If you ruled out bare-metal, then it’s worth considering the other extreme: general-purpose OS. Does my system really need a feature-rich operating system? Are there complex software requirements, or maybe a need for some specific third-party library? Does the device have the processing power to run such an operating system? Is there enough workforce to maintain it? For certain systems, a general-purpose OS is simply too heavy an alternative, and gaining thorough knowledge of the system is difficult.

If you ruled out general-purpose operating systems as well, then a real-time operating system may be a good choice. However, you should still consider if it is truly the best choice. Real-time operating systems still require learning and maintenance effort, and they come with their own overhead and quirks. You should consider whether the system is closer to bare-metal or general-purpose system, and if there are some future requirements that may affect the choice.

After selecting the operating system type, there are more hard decisions: what stacks to integrate into a bare-metal system, how to ensure the security of a Linux system, which RTOS to choose? The list of questions in the beginning of a project is a long one, and the answers vary from system to system. You should carefully research and consider the alternatives before starting the actual work. It may be expensive to change the answers later on.

All in all, the operating system selection comes down to finding the special and complex hardware and software requirements of your system, and figuring out the best solution to fulfill these requirements. Perhaps your system has complex software requirements that are best satisfied with Linux, or special hardware that can be reliably controlled only with bare-metal software. The simple sensor example from the previous chapter can be implemented on any type of OS, but without complete understanding of the requirements it is impossible to say which is the correct choice. Whatever your situation might be, it’s best to spend enough time trying to find the requirements that guide you to the correct decision.