Getting started with Zephyr OS using CMake

The goal of this tutorial is to lay out a basic folder structure of Zephyr OS application project, introduce you to a build system based on CMake and run your first “Hello world” using Zephyr OS.

Why Zephyr?

For the better part of my Embedded Developer career I’ve been developing bare-metal firmware. This, of course, has its advantages such as speed and full control of execution time. Yet, when you get to work on more complex projects, you have to be extra concentrated and know all aspects of a system in order to be able to add new features or maintain the project. That’s where you pay the price. When working in teams you need to have a ‘specialist’ who knows everything about, well… everything involved in the project. A basic super-loop will get you only so far, and you will probably end using some kind of sequencer in order to make your life easier. It will execute tasks for you periodically based on timer or external interrupts. So you ended up using a sequencer that now reminds you of cooperative multithreading and then you ask yourself should I maybe give a try to an RTOS? But which one?

There are many open-source RTOS available. Some of which are FreeRTOS, mbedOS and Zephyr. FreeRTOS is a market-leading real-time operating system run on many devices all over the world. In November 2017, Amazon Web Services (AWS) announced that it was taking ‘stewardship’ of FreeRTOS. Some of the notable libraries FreeRTOS provides you with are TCP/IP stack, MQTT and HTTP, LoRaWAN and FAT. 

mbedOS is a project managed by Arm. It supports ARM Cortex-M devices with a sufficient amount of RAM and FLASH. It uses C++ for application development and there are a lot of libraries for sensors and actuators developed by users. BLE, WiFi and Ethernet stacks are available for many devices. I used it few times some years ago and back then it looked to me that it adds a lot of bloat code. That may change over the last couple of years but a major drawback is that it only supports ARM devices, so if you want to use ESP32 you are out of luck.

Zephyr Project is a Linux Foundation Project. It is supported by big industry players: Intel, Nordic Semiconductor, Google and even Facebook. Zephyr supports multiple architectures including ARC*, ARM*, Nios II, RISC-V, Tensillica, and x86. Zephyr provides you with TCP/IP, WiFi and BLE stacks, drivers for different sensors and actuators, it supports both FAT and littlefs file systems. It uses Device Tree to describe hardware and configuration information about the devices which makes the source code truly portable. In practice, this means that you can, for instance, switch from nRF52840 to STM32WB55 just by using different device tree files. All application logic and all of your source code will remain the same. This makes it an easy winner in the competition for RTOS I plan to use in upcoming projects. 

Getting started with Zephyr 

First, you need to install SDK and tools following this guide

Go to zephryproject/zephyr folder and source the project environment file to set the project environment variables:


Basic application project structure

Below is a basic project structure for the Zephyr application:

├── CMakeLists.txt
├── set_uart_speed.overlay
└── src
    └── main.c
  • CMakeLists.txt – this is the CMake file that links the application directory with the Zephyr’s CMake build system
  • set_uart_speed.overlay – the device tree overlay file used to set up UART baud rate
  • src/main.c – our main source file


cmake_minimum_required(VERSION 3.13.1)

set(BOARD nucleo_wb55rg)

# Add your source file to the "app" target. This must come after
# find_package(Zephyr) which defines the target.
target_sources(app PRIVATE src/main.c)

In line 3 we set the board we want to use in this build. You can see development boards supported by Zephyr here. If you want to use a different board you can change it here, or supply it as an argument when invoking CMake using -DBOARD=.



#include <zephyr.h>
#include <sys/printk.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000

void main(void) {
  while (1) {
    printk("Hello world! %s\n", CONFIG_BOARD);

The source code is self-explanatory. We are printing “Hello world!” and board name string every 1 second. From the Threads – Zephyr Project documentation or kernel.h header we can see the description of the k_msleep function “Put the current thread to sleep”. This indicates that main() is actually a thread. More about this in some future posts.



&usart1 {
	current-speed = <9600>;
Overlay file set_uart_speed.overlay is used to setup the baud rate for my Nucleo stm32wb55 board. &usart1 is the node from boards DTS file. DTS files are placed in zephyrproject/zephyr/boards folder. You can see in zephyrproject/zephyr/boards/arm/nucleo_wb55rg.dts that the default baud rate for this board is 115200 and this is only a simple example of modifying hardware settings using Zephyr’s device tree.

Building the app and flashing 

We will prepare the building system by invoking CMake. If we want to adjust the UART baud rate using set_uart_speed.overlay file we will supply it as an argument to CMake using cmake .. -DDTC_OVERLAY_FILE=”set_uart_speed.overlay”. After CMake generates Makefile we need to build the firmware using the make command, and finally, we can flash it with make flash. In this case, Zephyr will prepare ‘flash’ make target and it will use OpenOCD to flash the firmware to the STM32WB55. You can find below described building steps. 


mkdir build && cd build
cmake .. -DDTC_OVERLAY_FILE="set_uart_speed.overlay"
make flash
Amar Mahmutbegović

Amar Mahmutbegović

Head of Engineering

Embedded developer with a proven history in the development of BLE embedded firmware in C and C++. Co-founder of Semblie.

Semblie is a hardware and software development company based in Europe. We believe that great products emerge from ideas that solve real-world problems.