3 mins read
When I first ventured into Zephyr Project, I was met with a build system that
felt both familiar and foreign. Having worked with CMake in other projects,
I assumed the transition would be straightforward. However, Zephyr introduces
its own layer with west
, Kconfig
, and DeviceTree
, adding some a bunch
of complexity to the build process.
This article aims to demystify Zephyr’s build system to newcomers by breaking
down its components and illustrating how they interact.
The role of West
At its core is Zephyr’s west
meta-tool (why “west”? because Zephyr is the
name of west wind, before being an RTOS), commonly seen as a swiss knife to
work with Zephyr. It’s designed to manage multiple repositories and streamline
the build process. It also handles tasks like fetching dependencies, building
applications, and flashing firmware to devices.
For the sake of simplicity, we’ll assume for the rest of this article that you have correctly installed dependencies and have a Zephyr workspace already set up. If it’s not the case, the official documentation can be found here.
Anatomy of a Zephyr application
The simplest Zephyr application includes the following files:
my_app/
├── src/
│ └── main.c
├── prj.conf
└── CMakeLists.txt
main.c
: Contains themain()
function. It’s the entry point of your application.prj.conf
: Specifies configuration options using Kconfig syntax.CMakeLists.txt
: Defines how to build the application using CMake.
Example CMakeLists.txt
:
cmake cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)
target_sources(app PRIVATE src/main.c)
This setup tells CMake to include Zephyr’s build system and compile main.c
as
part of the application. In particular, we can make two observations on this
minimal CMakeLists file:
- There is only one source file to compile (
target_sources
). ZEPHYR_BASE
needs to be defined in the environment variables.
Building the application
To build the application for a specific board:
west build -b <board_name> path/to/my_app
This command performs several steps:
- Configuration: CMake processes
CMakeLists.txt
, integrating Zephyr’s build system. - Kconfig processing: Reads
prj.conf
and other Kconfig files to generate a.config
file. - DeviceTree processing: Parses the board’s DeviceTree files to generate hardware definitions.
- Compilation: Uses Ninja (default) or Make to compile the application and Zephyr kernel into a firmware image.
The output is placed in a build/
directory that contains artifacts like
zephyr.elf
, zephyr.hex
, and zephyr.bin
.
Understanding prj.conf and Kconfig
The prj.conf
file allows you to enable or configure various features of Zephyr. For example:
CONFIG_GPIO=y
CONFIG_LOG=y
CONFIG_MAIN_STACK_SIZE=1024
These options are processed by Kconfig to generate a .config
file, which in turn influences the
build process and the behavior of the application.
You can also interactively configure available options:
west build -t menuconfig
This opens a terminal-based interface to modify configuration options, similar to make menuconfig
in the Linux kernel.
DeviceTree overlays
Zephyr uses DeviceTree to describe hardware components (one of its many
similarities with the Linux kernel.) To customize hardware configurations, you
can create board-specific overlay files inside boards/
.
my_app/
├── boards/
│ ├── rpi_pico.overlay
│ └── arduino_nano_33_ble.overlay
The concept of device tree overlays is adding custom layers to describe (add,
remove, modify) peripherals of the used microcontroller.
For example, an overlay file that enables an onboard LED (led0
):
dts &led0 {
status = "okay";
};
This overlay modifies the board’s DeviceTree to enable led0
. During the build process, Zephyr
merges this overlay with the base DeviceTree to generate hardware definitions.
Common build options
- Pristine Builds: To ensure a clean build environment (to force the rebuild for another board for example):
west build -p always -b <board_name> path/to/my_app
- Specifying Build Directory: To build in a specific directory:
west build -b <board_name> -d build_dir path/to/my_app