Zephyr wrapped up in a box

How to build your Zephyr app in a standalone folder

If you’re like me, you installed Zephyr and began making your own changes to the sample applications that came with the toolchain. But at some point–either for personal project repository tracking or building out a professional project–your program starts to take shape. You want to move it to its own standalone directory. It’s not immediately clear how to do that, so today we’ll dive into the nuts and bolts. (Spoiler alert: it’s pretty easy.)

There be dragons in developing inside the Zephyr directory

For those new to Zephyr, getting started examples like “Blinky” programs are located inside the ~/zephyrproject/zephyr/samples directory. I knew I shouldn’t just be making (and forgetting about) my own folders inside. It took my losing a few programs before I did anything about it. I wanted to reinstall the toolchain and I removed my zephyrproject directory without a thought for my poor, non-revision-controlled, early experiments with the RTOS. Don’t be me.

One option is to run git init in your own subdir within the Zephyr tree. But I feel like the work I’m doing should be separate from the toolchain I’m using. So I changed the formula: I set up my own tree and told it where Zephyr is installed.

Step by step

By far the easiest thing to do is to copy one of the sample directories. For my fellow Linux enthusiasts this looks something like cp -r ~/zephyrproject/zephyr/samples/basic/blinky ~/.

Here are the more verbose steps:

  1. Create a directory for your app
  2. Add CMakeLists.txt
  3. Create a src subdirectory and add main.c file to it

That sets up the directory. You now have a folder in your home directory called blinky. Like many zephyr samples, this includes the source files (src folder), a project file (prj.conf), CMake directives (CMakeLists.txt), a readme file (README.rst), and a yaml file (sample.yaml). More complex examples might have a boards directory (boards) that has hardware specific configurations.

Notably absent is any reference to the Zephyr SDK and toolchain to build your project. So we need to tell it where to find Zephyr in order to build your app inside of that directory.

Each time you begin a new terminal session, source this helper file:

source ~/zephyrproject/zephyr/zephyr-env.sh

If you are working with the Nordic fork of Zephyr, simply source the same file from that tree:

source ~/zephyr-nrf/zephyr/zephyr-env.sh

Don’t forget that each time you begin a new terminal session (no matter what tree you’re in) you need to enable your Python virtual environment and set up the build environment. Here’s an example of how I start an ESP32 development session:

[email protected] ~ $ cd ~/blinky
[email protected] ~/blinky $ source ~/zephyrproject/.venv/bin/activate
(.venv) [email protected] ~/blinky $ source ~/zephyrproject/zephyr/zephyr-env.sh
(.venv) [email protected] ~/blinky $ export ESPRESSIF_TOOLCHAIN_PATH="/home/mike/.espressif/tools/zephyr"
(.venv) [email protected] ~/blinky $ export ZEPHYR_TOOLCHAIN_VARIANT="espressif"
(.venv) [email protected] ~/blinky $ west build -b esp32 . -p

Here’s the same process, but for a Nordic-based board. Note two things: I didn’t need to set up the toolchain variables like I did with ESP32, and the binaries for different hardware can be built in the same tree using different toolchains–a powerful perk of using Zephyr.

[email protected] ~ $ cd ~/blinky
[email protected] ~/blinky $ source ~/zephyrproject/.venv/bin/activate
(.venv) [email protected] ~/blinky $ source ~/zephyr-nrf/zephyr/zephyr-env.sh
(.venv) [email protected] ~/blinky $ west build -b thingy91_nrf9160_ns . -p
TIP: The ESP32 example above will not build the blinky app unless you add an esp32.overlay file to configure the &led0 alias that main.c needs to attach to an LED. The Thiny91 doesn’t have this limitation. That particular dev board has an LED included, so &led0 is already specified in the dts file within the Zephyr toolchain.

Before we wrap up, let’s spend a beat discussing those CMake and Kconfig files.

Configuring a Zephyr project

I’ve moved the files of my app to a different location, but the build process remains the same. Every project needs a CMakeLists.txt file to specify the CMake version, designate this as a Zephyr project, and list the C files to include in the build. Beginners do not want to make this file themselves, so copy it from a known-working sample. You also name your project in this file using the project() designator, so go change that name now.

Projects usually also need to include a Kconfig file. This is the prj.conf that you see in a lot of projects, and for blinky that simply turns on the gpio subsystem:

CONFIG_GPIO=y

You may choose to add a boards subdirectory and store .conf files with specific board names. These files configure subsystems, while overlay files in the same directory designate hardware pins and peripherals. These two file types are key to making your Zephyr app portable across different hardware.

Further reading

Golioth has a “clean application” guide to get you started and it walks through the prj.conf file settings necessary for your app to connect to the Golioth Cloud. Of course the deeper dive into this issue is the Zephyr docs page on application development that drills down into every part of out-of-tree coding. But what I’ve covered today should be enough to get you started.

If you get stuck, the Golioth forum is a great place to ask for help on this topic. You’re also invited to join us for Golioth Office Hours, every Wednesday at 10 AM Pacific time on our Discord channel. We love chatting about the work you’re doing, and everyone wants an early look at your hardware demos during development!