Choose the Right RTOS for Embedded Systems
Embedded projects can be very simple and small, with not much going on, but often have a large number of events all looking to happen. To handle all these events, engineers will want to add a Real Time Operating System (RTOS) to their system’s architecture. The purpose of an RTOS is to ensure specific tasks occur within a set time frame. This is very important in embedded devices, as many different tasks must occur for the device to function.
Once an RTOS is needed, the next question is which RTOS to use. Some common RTOS options are FreeRTOS, Mbed, ThreadX, and Zephyr. Many have great uses across various systems, though the highlight of this article is Zephyr, as it uses a different methodology for how it works and how it is used. If you’re reading this article, you may be facing the struggles of Zephyr’s learning curve, but hopefully, by the end, you will gain a greater appreciation for Zephyr.
Understanding Zephyr’s Learning Curve
All things in life need to be learned and dealt with to get the most out of them. Zephyr is the same way, but with a much steeper learning curve for users familiar with other RTOSs. Nearly all users of Zephyr have struggled with it for a reason: the hardware agnosticism, feature set, and continued growth of the RTOS. Most engineers are familiar with how to set up a new project: choose an MCU, define the required features, write drivers, and then test.
On Zephyr, the steps are more akin to establishing needed features, writing drivers, and then testing on a long list of possible MCUs. This flip can be hard for many engineers who are familiar with a certain schema, though once one has sipped from the fountain of the future, they may not want to go back. Talk is only so much, though, so now we’ll get into some actual tips and tricks to hopefully help you become a functional Zephyr user!
Core Zephyr Concepts: Usage, Kconfigs, and Device Trees
Zephyr is primarily used via the CLI and a program called “west” to handle nearly everything, including building, flashing, debugging, repo management, and much more. The Zephyr Foundation has documentation on how to install things and is quite helpful for starting out. Once install is done, Zephyr’s greatest hurdles appear on the horizon:
All three are outlined in greater detail below:
Configuration Files
Zephyr is hardware-agnostic, which means the user must say what they want included in the project. There are two files to edit: `kconfig` and `prj.conf`. Kconfig files may be familiar to those who frequent Linux, as Zephyr is a kernel; these are the “kernel configs”. That said, in most cases, not much should need to be changed here, as the stock file created will serve most users just fine. The prj.conf file is the main location where one would define the software’s capabilities, such as the inclusion of logging, UART, ADCs, or SPI.
The Zephyr project includes the full list of variables that can be added to the prj.conf file to configure a project. This is the part that confuses most people, as they either don’t know what to set or would rather set them in the codebase itself, so they feel more like local variables or #defines. This schema enables Zephyr’s hardware-agnostic nature, since the settings are held outside the driver. To help with the volume of config variables, Zephyr includes a GUI that lets you select settings based on your needs; this helps alleviate the issue of not knowing your options. I wish I had known about this when I was configuring my first project in Zephyr.
Zephyr configuration can be done using either a graphical interface or a terminal-based menu, both exposing the same Kconfig options:


Device Tree
Where prj.conf files define the software, the device tree defines the hardware. There are three main files in play here:
- .dts. This is an MCU-specific configuration.
- .dtsi. It is more general hardware info that can apply to a MCU family or brand of MCUs, potentially
.Overlay
While the first two are often left alone as is, since they define the capabilities of the MCU, which don’t change. User intervention is needed within the .overlay file. This is where the peripherals are set up, along with the pins used to control them. An example of the overlay structure can be found here at the bottom of the section; the example shown is for an SPI and I2C setup. The .overlay will need to be curated for each hardware-related function of the setup. These created peripherals will later be used in the main code.
Moving Forward with Zephyr
In my experience and others’ sentiments, the ability to grasp the purpose and schema of the files above is the biggest hurdle in Zephyr. Once those are understood, the rest of Zephyr is quite similar to other RTOS or general embedded methodologies. It’s highly recommended to follow a guide when setting up a sample project before starting your own, as guidance can be very beneficial when starting out with Zephyr.
Practical Considerations
Currently, it is hard to avoid AI. For a case like Zephyr, it can be quite useful for understanding what to add to one’s prj.conf and .overlay files. Be aware that these config variables may change over time, and new ones may be unfamiliar to the AI model. It’s best to consult official sources if things are not working/building correctly.
Additional Resources
Here are a few reference materials to help get you on the right path in your Zephyr journey:
Have an upcoming project? DMC can help you take the next step.
Take your project to the next level with engineering solutions from DMC. Learn more about our Embedded Development & Programming solutions or contact us to get started today!







