Getting started with Zephyr

Zephyr is an open-source real-time operating system, used mainly in embedded devices, with a focus on small systems, thanks to its very small footprint.

This post is a quick startup guide to show how to run Zephyr on two different boards, from two different vendors:

In this post, we will show how to install all the tools needed to build and run Zephyr, then run some samples, until we get access to the Zephyr shell over USB.

Getting Zephyr

Zephyr can be downloaded using a tool named west which is available through pip, and is responsible for download all Git repositories needed to build Zephyr, according to a manifest file.

The compilation of Zephyr itself and its various tools require quite a few dependencies which need to be installed separately. The Getting Started Guide on the Zephyr documentation has a good step-by-step guide mentioning these steps.

The dependencies you’ll need will depend on which OS or distribution you use. For regular distributions, the official documentation provides the commands. For instance on Arch Linux:

$ sudo pacman -S git cmake ninja gperf ccache dfu-util dtc wget \
    python-pip python-setuptools python-wheel tk xz file make

Now, we’re ready to install the west tool, and the recommended way to do so it is to use a Python virtual environment, following the next steps:

  1. Create the environment:
    $ python -m venv ~/zephyrproject/.venv
  2. Activate the environment:
    $ source ~/zephyrproject/.venv/bin/activate

    Note: to exit the virtual environment, you just have to use deactivate

  3. Install west
    $ pip install west

Now we can finally download Zephyr itself:

$ west init ~/zephyrproject
$ cd ~/zephyrproject
$ west update

Once done, the official documentation suggest to generate CMake configuration files, but that is only useful when building code outside of the context of west, so in our case we can safely skip this step.

We then need to install a few additional Python libraries, still within the virtual environment:

$ pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt

Then, installing the Zephyr SDK is also required because it contains all the toolchains needed to build Zephyr and your applications. Practically, it contains all the compilers needed for the different architectures supported by Zephyr.

$ cd ~
$ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5-1/zephyr-sdk-0.16.5-1_linux-x86_64.tar.xz
$ wget -q -O - https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5-1/sha256.sum | sha256sum -c --ignore-missing
$ tar xvf zephyr-sdk-0.16.5-1_linux-x86_64.tar.xz

In the above snippet, linux-x86_64 corresponds to the CPU architecture of your host machine, where Zephyr will be built. It does not correspond to the CPU architecture of the target device on which Zephyr will be running.

The SDK now needs to be set-up:

$ cd zephyr-sdk-0.16.5-1
$ ./setup.sh

Finally, we can install udev rules, to be able to flash your boards without root priviledges:

$ sudo cp ~/zephyr-sdk-0.16.5-1/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
$ sudo udevadm control --reload

Here it is, we now have a working checkout of Zephyr!

First sample

Zephyr provides a lot of samples, located in zephyr/samples. They are small programs, showing you how to use the different features and subsystems in Zephyr. For our first test, we will use the sample called Blinky, located under zephyr/samples/basic/blinky.

We will first try on the STM32L562E-DK board. For this, we have to go inside the zephyr directory, and then build the sample:

$ cd ~/zephyrproject/zephyr
$ west build -p always -b stm32l562e_dk samples/basic/blinky

-p always tells west to do a clean build.
-b stm32l562e_dk tells it for which board to build.

Once built, we need to connect our board to the host PC. For that, simply plug a cable onto the micro-USB port on the right of the board. If it is well connected, you should see multiple LEDs on.

Then, flashing the board is as straightforward as:

$ west flash

You should see a green LED blink each second, as shown on the video below:

Now, let’s try to run the same example on the Arduino nano 33 BLE. To re-build the sample, we can run the same command again with another board name:

$ west build -p always -b arduino_nano_33_ble samples/basic/blinky

But to flash the Arduino, a few more steps are needed. First, we need a patched version of BOSSA for Arduino boards, which is available on Github. The archive needs to be downloaded and uncompressed.

Before flashing the Arduino, you need to double-tap the main button on the board. You should see a green LED on, and an orange one fading on and off slowly. This means the board is in the flashing state.

You can then run west flash, but providing the path to the patched bossac tool:

$ west flash --bossac ~/bossac-1.9.1-arduino2-linux64/bin/bossac

You should then see a blinking red light!

Congratulations, you just ran your first Zephyr example application on two different boards.

Console over USB

If you look at the code of the Blinky sample, after the code to configure the LED, you will see this:

    while (1) {
        ret = gpio_pin_toggle_dt(&led);
        if (ret < 0) {
            return 0;
        }

        led_state = !led_state;
        printf("LED state: %s\n", led_state ? "ON" : "OFF");
        k_msleep(SLEEP_TIME_MS);
    }

The code is quite simple, it simply toggles the LED on and off every SLEEP_TIME_MS (defined at 1s, earlier in the file). Every time the state is toggled the code is also printing some logs. The question is, how to access those logs ?

The answer to this question is in the Device Tree, a structured file describing the hardware. You can find the Device Tree describing the Arduino Nano 33 BLE board in boards/arm/arduino_nano_33_ble/arduino_nano_33_ble-common.dtsi.

At the start of the file, we can see this section:

    chosen {
        zephyr,console = &uart0;
        ...
    };

This tells Zephyr that the console is directed to the first UART. But what if we want to get it through USB, using CDC-ACM?

First, we are going to copy the sample to modify it:

$ cp -r samples/basic/blinky my_project

Then, in my_project, you need to add a file named app.overlay with this content:

/ {
    chosen {
        zephyr,console = &cdc_acm_uart0;
    };
};

&zephyr_udc0 {
    cdc_acm_uart0: cdc_acm_uart0 {
        compatible = "zephyr,cdc-acm-uart";
    };
};

This tells Zephyr to use the USB port for the console and was found in the samples/subsys/usb/console/ example.
You will also need to add these configuration options to prj.conf, in the same folder:

CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y

You can find and set these options using menuconfig:

west build -t menuconfig

But if you want them to be permanent you have to write them in your prj.conf file.
This enables the USB stack, and asks Zephyr to initialize it at boot time.
You can then build your project (west build -p always -b arduino_nano_33_ble my_project) and flash it. The console should be named /dev/ttyACM0 on your host. You can access it with a terminal emulator, like picocom:

$ picocom /dev/ttyACM0

You should then see the output of the console:

picocom v3.1

port is        : /dev/ttyACM0
flowcontrol    : none
baudrate is    : 9600
parity is      : none
databits are   : 8
stopbits are   : 1
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv -E
imap is        : 
omap is        : 
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no

Type [C-a] [C-h] to see available commands
Terminal ready
*** Booting Zephyr OS build v3.6.0-2194-g46a0a72be855 ***
LED state: OFF
LED state: ON
LED state: OFF
LED state: ON
LED state: OFF
LED state: ON
LED state: OFF
...

On the STM32L562E-DK, the USB output is not on the micro-USB port: you need to plug an USB-C cable. As you have now two USB interfaces, the console will probably end up at /dev/ttyACM1, but it can also be on /dev/ttyACM0, depending on the plugging order.

Accessing the Zephyr shell

As the last step of this blog post, we will try to access the Zephyr shell, still over USB.

For that, we will use the sample at samples/subsys/shell/shell_module. We still need to configure it for USB. Luckily, this sample provides what we need.

In the sample, there is a file named usb.overlay, containing the relevant Device Tree change: we just need to rename it to app.overlay. If you look inside, it looks very similar to the one we used earlier, but the chosen node set by the overlay is zephyr,shell-uart instead of zephyr,console.

For the configuration, you just need to copy the options in overlay-usb.conf, and paste them in prj.conf.

After building and flashing, you should get the Zephyr shell on your terminal:

uart:~$ version
Zephyr version 3.6.99
uart:~$ demo board
stm32l562e_dk
uart:~$

Conclusion

We hope this guide has provided a comprehensive walkthrough for installing and running Zephyr on both the Arduino Nano 33 BLE and the STM32L562E-DK boards. By following the steps outlined here, you’re now equipped to install Zephyr, run samples on both the Arduino Nano 33 BLE, the STM32L562E-DK boards and hopefully others. Zephyr already has a wide variety of supported hardware, just pick one up and start developing!

We will be posting more articles on Zephyr in the coming weeks, so stay tuned!

One thought on “Getting started with Zephyr”

  1. This is awesome! I really hope to see more content around Zephyr. Would be great to provide a training course at some point. For example, OS fundamentals, VS Code usage, STM32, etc.

Leave a Reply