💡
The following guide is be based in the Nordic nRf54l15 and more precisely with the nRf54l15dk boards that comes with an on board JLink

To work with Zephyr, you’ll need to install a tool called West, which helps manage several tasks like building, flashing, and handling your project. But keep this in mind: West is not Zephyr. West is written in Python, so you’d normally need to install it along with a bunch of Python libraries and some extra tools. Well, there’s no need to worry about that—at Modular MX, we’ve already prepared a container for you.

💡
If you don’t have experience with docker, no problem, check our guide: Docker for Embedded - Embedded House. But If you don’t like containers I recommend to use a virtual machine in Linux or a WSL2 instance in case you are using windows Installing West locally in Linux

Setting in up Zephyr

The container ships all the necessary dependencies, including the West tool with all its Python plugins. It doesn’t include any flash/debugger tools like OpenOCD or J-Link; because we try to keep it, as light as possible, don’t worry I will tell you how to flash and debug later on. Download the container from our dockerhub

$ docker pull modularmx/zephyros:latest

In your local machine choose a directory where your project and Zephyr sour code will live. Run the container with a shared folder using the --mount flag. And in my case the /home/user/workspace set the working folder inside my container

$ mkdir ~/zephyr
$ cd zephyr
$ docker run -it --rm -w /home/user/workspace --mount type=bind,src="$(pwd)",dst=/home/user/workspace modularmx/zephyros:latest

Once the container is running you can run the west tool to download Zephyr from its official repositories. This is going to take a while because it will download the entire project!!, something we should avoid using a manifest, but fort the “momento” I think is worthy.

$ west init
$ west update

By now, you should have your Zephyr folder organized with the following structure, which will serve as our Workspace. This directory is where the Zephyr source code will reside, and we will not interfere with any of the sources or directories contained within it.

.
├── bootloader
├── modules
├── tools
├── .west
└── zephyr

7 directories, 0 files

Test project

Every new project we make is going to be created inside our workspace, to keep some order we will create a folder called applications and inside this folder we create one folder for every new application. This what is called a “Zephyr workspace application” and you can find more information in Application Development — Zephyr Project Documentation

.
├── bootloader
├── modules
├── tools
├── .west
├── zephyr
└─── applications/
     ├── app1/
     ├── app2/
     └── app3/

Ok lets create our directory for our first project and the needed files

$ mkdir -p applications/app1/src
$ cd applications/app1

app1/src/main.c

/* Include libraries */
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* Delay of 1000 ms */
#define SLEEP_TIME_MS 1000

/* Get the DeviceTree alias for the "led0" alias */
#define LED0_NODE DT_ALIAS( led0 )

/* Get pin specification (device, pin, flags) for LED0 from the Devicetree */
const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET( LED0_NODE, gpios );

int main( void )
{
    /* Configure led0 as output*/
    gpio_pin_configure_dt( &led0, GPIO_OUTPUT );

    while(1)
    {
        /* Toggle LED led0 */
        gpio_pin_toggle_dt( &led0 );
        /* Sleep for 1000ms (delay) */
        k_msleep( SLEEP_TIME_MS );
    }
    return 0;
}

app1/prj.conf

# Disable optimizations
CONFIG_NO_OPTIMIZATIONS=y
# Include GPIO drivers in system config (KCONFIG)
CONFIG_GPIO=y

app1/CMakeLists.txt

# Minimum CMake required version
cmake_minimum_required( VERSION 3.20.0 )

# set enviroment variables like the board in use
set(BOARD nrf54l15dk/nrf54l15/cpuapp)
set(BOARD_FLASH_RUNNER jlink)

# Get Zephyr directory
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

# Project name and source files
project(Blinky)
target_sources(app PRIVATE src/main.c)

Build your project using west. -p always means make a new build directory (or rewrite the existing one), while app is the name of the directory with the source files. If you want to remove the compilation binaries for wherever reason just type west build -t pristine

$ west build -p always app
...
Memory region         Used Size  Region Size  %age Used
           FLASH:       22754 B      1428 KB      1.56%
             RAM:        4432 B       188 KB      2.30%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /home/user/workspace/applications/app1/build/zephyr/zephyr.elf for board: nrf54l15dk

Using west flash command

Our container does not come with any tools to flash or debug your board, it is necessary to do it externally with wherever tools you like (Jlink, bootloader, openocd, etc…). But you can integrate one of those tools into a new container and use the west flash command, for instance the docker file below uses the previous as base and adds the JLink software.

# Fetch the modular image for Zephyr OS as baseline 
FROM modularmx/zephyros:latest

# switch to "root" user
USER root

# install all the necessary dependencies
RUN apt-get update && apt-get install -qqy wget sudo libxrandr2 libfreetype6-dev libsm6 libxfixes3 libxcursor-dev \
	libxcb-render-util0 libxcb-randr0 libxcb-shape0 libxcb-image0 libxkbcommon-x11-0 libx11-xcb1 libx11-xcb1 \
	libxcb-icccm4 libxcb-sync1 libxcb-keysyms1 libxcb-xfixes0 libglib2.0-0 \
	&& apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Download and install the JLink software package 
RUN wget --post-data "accept_license_agreement=accepted" https://www.segger.com/downloads/jlink/JLink_Linux_x86_64.deb \
	&& dpkg --unpack JLink_Linux_x86_64.deb \
	&& rm -f /var/lib/dpkg/info/jlink.postinst \
	&& dpkg --configure jlink \
	&& apt install -yf \
	&& rm ./JLink_Linux_x86_64.deb

# set sudo priviledge to user (terrible idea, i know)
RUN usermod -aG root user

# switchback to "user" user
USER user

Build the container with a proper name (don’t forget this has to be done outside the previous running container)

$ docker build -t west_jlink .

Run the container with the shared directory and access to our USB devices

$ docker run --rm -it -w /home/user/workspace --mount type=bind,src="$(pwd)",dst=/home/user/workspace --device=/dev/bus/usb:/dev/bus/usb west_jlink

Inside your new container type west flash, and enjoy your led achievement

$ west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner jlink
-- runners.jlink: reset after flashing requested
-- runners.jlink: JLink version: 8.54
-- runners.jlink: Flashing file: /home/user/workspace/applications/app1/build/zephyr/zephyr.hex

Debugging with Ozone

But debug is far more better than just flashing, let’s prepare a separate container where we can run Ozone to debug our program.

💡
For more details on container for SEGGER tools, read this post from embedded house Part 8: GUI applications in docker
FROM ubuntu:latest

#install some dependencies 
RUN apt-get update && apt-get install -qqy wget libxrandr2 libfreetype6-dev libsm6 libxfixes3 libxcursor-dev

# download from segger website Ozone, latest version 
RUN wget --post-data "accept_license_agreement=accepted" https://www.segger.com/downloads/jlink/Ozone_Linux_x86_64.deb \
    && dpkg -i ./Ozone_Linux_x86_64.deb \
	&& rm ./Ozone_Linux_x86_64.deb

# rename user ubuntu to "user" top avoid conflicts with directories
RUN usermod -l user -d /home/user -m ubuntu \
  && usermod -aG root user

USER user

Build and run the container setting directories accordingly and also share the usb port appropriately. it is much better if you make a compose file Part 4: Time to Docker Compose

$ sudo xhost +local:docker
$ docker build -t ozone .
$ docker run --rm -it -w /home/user/workspace --net=host --env DISPLAY=$DISPLAY --mount type=bind,src="$(pwd)",dst=/home/user/workspace --device=/dev/bus/usb:/dev/bus/usb ozone

With the container running type the following line to execute ozone

$ ozone -device nrf54l15_m33 -select USB -if SWD -speed 4000 -programfile applications/app1/build/zephyr/zephyr.elf

Look at this nice video in youtube Ozone – The J-Link Debugger | Introduction if this is the first time you use Ozone .

By far my favorite way to work with Zephyr is using containers with manifest of course. By the way it is possible you are experimenting some problems with Ozone such as: where are the source files?. This has to do with directories and how containers and Ozone make use of them.