The Zephyr Project is an open-source real-time operating system (RTOS) focused on embedded and IoT platforms. Zephyr is a Linux Foundation hosted ‘Collaboration Project’, and supports a wide array of devices and architectures.
The Raspberry Pi Pico is a microprocessor board developed by Raspberry Pi and is based on their RP2040 chip. The RP2040 is a SoC containing a dual-core Arm Cortex M0+ processor and a small amount of on-board flash.
The Pi Pico and the RP2040 it is built around are both supported by Zephyr, and in this blog post I will discuss how I built, flashed and booted a Zephyr image and test application to a Pi Pico.
Initial Zephyr setup and installation
To setup Zephyr on my local machine, I followed the official getting started guide. On my Ubuntu 22.04 machine, this required creating a python virtual environment (venv), configuring the ‘west‘ tool within the newly created venv and downloading the Zephyr source code. I also needed to download and extract the latest Zephyr SDK.
The west tool is the primary way to build and flash Zephyr and it provides several Emulation Boards as build targets which are useful for testing that everything is setup and installed correctly. For my setup, I tested by building and running the Zephyr provided hello_world project for the x86 emulation board (qemu_64) via the following command.
west build --pristine -b qemu_x86 zephyr/samples/hello_world
I then ran the built binary using the following command.
west build -t run
I received the following output, indicating that everything is setup and working correctly.
*** Booting Zephyr OS build v3.6.0 ***
Hello World! qemu_x86
West Workspace
A typical minimal Zephyr/West workspace will have the following directory layout:
workspace/
├── .west/
│ ├── config
├── application/
│ ├── CMakeLists.txt
│ ├── prj.conf
│ ├── src/
│ │ └── main.c
│ └── west.yml
├── modules/
└── zephyr/
Let’s take a look at it’s contents in more detail:
- .west/ This is used to indicate that the current folder is a west workspace, and is generated alongside the “config” file within when the west init command is used.
- .west/config defines the directory/remote to use as the manifest for the current west workspace. For my basic modified hello_world example, it contains the following, which defines the subdirectory that my Zephyr application lives in and the application’s own west manifest.
[manifest]
path = application
file = west.yml
[zephyr]
base = zephyr
- application/ The home of our custom Zephyr application, contain all the source code and configuration required to build it. This folder can be named however you want, provided it is reflected in the
.west/config
file. - application/CMakeLists.txt Used for build specific configuration of our Zephyr application. As the hello_world example is very simple, it’s own CMakeLists.txt reflects this:
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)
- application/prj.conf a kconfig fragment that is used for setting kconfig values specific to the application. Once again, as hello_world is a very simple example it’s prj.conf is empty.
- application/src/ the location of all the application source code. A main.c is required, though Zephyr supports other languages such as C/C++ and assembly source files.
- application/west.yml the west manifest specific to the application. For hello_world this file is again fairly basic, simply defining where to find the build commands for the west tool to use, what remote to use for the Zephyr source:
manifest:
self:
west-commands: scripts/west-commands.yml
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v3.6.0
- modules/ a location for West to store any modules (external projects) that the application defines in it’s manifest.
- zephyr/ a location for West to store the Zephyr source code.
Further information is available in the official Zephyr documentation here and here.
Creating a new Zephyr application
When creating a new Zephyr application, the official documentation provides a Zephyr Example Application repository can be used as a starting point, a bare “skeleton” for starting development. Alternatively, an example application such as the hello_world application can be copied and modified, which is the method I decided to use.
Serial over USB
By default, the serial output of the Pi Pico is unavailable when using Zephyr unless you use a Raspberry Pi Debug Probe to connect to the Pico’s dedicated debug pins. I didn’t have a debug probe to hand and wanted to use the Pico’s USB Serial Console instead, so I followed the example in this repo to enable that functionality.
This involved updating the project’s prj.conf to enable the required serial features, adding a section to the app.overlay to enable Zephyr’s support for cdc-acm-uart, and adding the setup functions and test print to the src/main.c.
Building for and Flashing to Pi Pico
The west tool’s build command uses the –b argument to define the target board to build for, so in order to build an image for the Pi Pico the rpi_pico value must be given. As the artifacts of the previous x86 test build still existed in my Zephyr directory, I also needed to pass the –pristine argument to tell west to perform a clean build, otherwise the build process would fail.
There are several ways to flash a Zephyr image to the Pi Pico, but as I’m not using a JLink or OpenOCD probe, the only option available to me is to copy a .uf2 image to the Pico by hand. By default Zephyr will generate a uf2 image file of the latest successful build named zephyr.uf2 within the build/zephyr/ directory, and this image can be flashed to a Pi Pico by following these steps:
- Disconnect power and the Micro USB cable from the Pi Pico
- Press and hold the BOOTSEL button on the Pico, and whilst continuing to hold down the button, connect the Micro USB port to the computer.
- If done successfully, the Pico should appear on the PC as a removable storage volume named “RPI-RP2“. Mount this volume, then copy the relevant .uf2 file to the root of this volume.
- The Pico should automatically eject from the PC once the file copy is complete.
- If successful, the uf2 image should be flashed onto the Pico, and will be automatically booted each time power is supplied to the Pico. To replace the image on the Pico, repeat these steps.

After building and flashing my testing application (modified for USB Serial) to the Pi Pico, when it rebooted I was able to see my test print over the Serial console, available at /dev/ttyACM0 with a baud rate of 115200. With minicom I was able to monitor the console using the following command.
minicom -b 115200 -o -D /dev/ttyACM0
We now have the Zephyr tools and SDK setup and configured, with a simple hello world application outputing via USB, making a great starting point for further development.
Cover Image credit: Phiarc