Run Zephyr on nrf52840 (Particle Xenon)

Mark Zachmann
Oct 17, 2019 · 7 min read

I’ve been experimenting with Nordic Semiconductor nrf52840 boards, first using Arduino, then naked NordicSemi SDK/C++ and now I’m looking into Zephyr. The (imho) best of the module/boards is the (now-discontinued) Particle Xenon.


Why Zephyr?

Zephyr is a free open-source multi-threaded OS by the Linux Foundation. It’s a small-footprint operating system designed for smallish embedded systems with a specialization in Bluetooth. The nrf52840 with 1MB of flash and 1/4MB of ram and a fast ARM cpu is almost a perfect match.

I’m considering it mainly because of the Bluetooth support. Nordic (and others) have helped with the Zephyr Bluetooth implementation and it’s much nicer to use than Nordic’s internal Bluetooth stack for the simple reason that the code is open-source and compiled-in so it’s much easier to debug (and fix if necessary). It’s possible that Nordic’s SoftDevice (the SDK closed implementation of Bluetooth) is as reliable, or even more-so, but debugging it is agony if not impossible. It also requires somewhat indeterminate space in the flash/ram map and must be sideloaded with apps. This makes the upgrade process complex.

Quick Summary

Before getting into how to build/use Zephyr, just a summary of my thoughts after a few days of implementation.

Zephyr is very easy to implement and get running. I can even use it with the Nrf5 bootstrapper. It took just a couple of days to get it running on my custom Xenon implementation and powering an OLED the way I wanted it. The Zephyr API is simple but very powerful.

The documentation could be better. In typical open-source fashion a fair amount of understanding requires going through the code. The overview documentation is reasonable and the setup documentation is very good.

The API reference (one page is shown on the left here) as you can see is very bad. Note the entire lack of an overview and configuration options is a list of CONFIG defines with no info. Arguments are declared but not explained. It’s poorly done autodoc. I’m not entirely sure what a ‘static’ C function is in this context.

The sample applications are also poorly documented and usually trivial but still quite useful.

The toolset is very good but slightly complex. It runs under Windows, Mac, and Linux. Under Linux& MAC you can even debug in a simulated environment (QEMU).

I have been able to get Segger Studio with a JLink to debug Zephyr very adequately on the Particle Xenon. It’s not documented (so I’ll discuss in detail) and not supported (yet) so… but it seems to work very well and you get full visual debugging — and the Bluetooth code is visible.

Debugging a Zephyr App with Segger Studio

Getting Started

The Getting Started at the Zephyr site is very good. I won’t duplicate it. If you are using a stock Particle Xenon then there is even bundled support for it. I started with those Xenon files but modified them for my usage (I remap the I2c, SPI, … pins and add some peripherals). Modification was easy and discussed in the porting guide, but I just read through the files and some samples.

The .DTS file specifies the board-specific device tree. This defines what pieces of the nrf52840 the board makes available in the hardware implementation and what pins correspond to those pieces (such as I2C or SPI).

The config/KConfig files define which pieces of the subsystem are being actively used by the software and how — similar to the Nordic SDK .conf file.

A good way to think about it is that the Device Tree shows what’s available (such as partitions) and the prj.conf (for the project) or board_defconfig (for the board) says whether or not it’s being used.

I didn’t bother trying to figure out how to flash the Xenon the Zephyr way because i’m using the Nrf5 bootloader and Bluetooth DFU (device firmware updater). Flashing via the JLink is painless, however. I just set the project load file to the built .elf application and when I debug/go it downloads the binary .elf file to the module at the right offset.

Configuration Editing via GUI

Zephyr comes with a configuration (prj.conf) editor that’s pretty good. To invoke it use west as in:

west build -t guiconfig -b board-name project-name


west build -t menuconfig -b board-name project-name

for a terminal-based gui.

Unfortunately these apps change the .config file in your build folder, so they get wiped away by a new prj.conf. Copy any changes to your prj.conf or Kconfig files to have them persist.

Setting Flash Offset

Setting the flash offset and size was a struggle. I used the partition definitions from the Xenon (see mesh_feather.dtsi) and they did nothing until I worked out to add


to the prj.conf file. I think CONFIG_BOOTLOADER_MCUBOOT would be similar.
Note that this used to be: CONFIG_USE_CODE_PARTITION.

Workspace Design

Like me, you probably put your source code into a cloud-clone folder (such as OneDrive/Documents/myproject).

The default Zephyr installation is to place a copy of zephyr into your source tree and then build into a folder in the source tree. This is clumsy/bad since you end up with the entire Zephyr tree and build output needing to get backed up.

So, instead I did the following:

  1. Create a non-cloud folder (I use C:\z) and run west install to set up the zephyr folder as if for a build. This can go anywhere and doesn’t need to be backed up over time.
  2. Place the source code in a folder of your choice with the board code in a typical board folder.
    Here the BlueLora folder is the C/C++ source code. The nrf52840_bluelora folder is the device tree that becomes part of Zephyr. The various root files are the segger support files for debugging (not used in the build).
Zephyr Code Source Tree for BlueLora project

3. Set ZEPHYR_BASE in your environment to point to the zephyr source tree.

4. Insert a symbolic link to your board source folder in the zephyr source tree at {zephyr-source}/boards/arm via

mklink /d {driver-folder-name} {folder-source-name-full-path}

which will create a symbolic link between where it needs to live in the zephyr tree and the true source code folder in OneDrive. In my case that was

mklink /d nrf52840_bluelora C:\OneDrive\Documents\BlueLora\nrf52840_bluelora


In the main source folder (here …OneDrive-Personal/Documents/BlueLora), start a command prompt and you can run

west build -b {board-name} -p auto -d {build-folder} {project-name}

where project-name is the local source folder (here BlueLora) and build-folder is the full path to where to build (such as C:\build\zbuild).

The created .elf file is {build folder}\zephyr\zephyr.elf.

There are some issues with building and sync of edited files. The decision as to what to build isn’t always accurate, and sometimes things get left in the build folder that were deleted. Just delete the entire build folder to have it do a clean rebuild.

Most of the time if I have sync issues I just do a full build (-p always) and that takes care of it.


One great thing about using a ‘real’ operating system is Asserts. These are warnings that something seems wrong and they are sprinkled in the OS code and valuable — seeing an assert has saved me hours of stupid mistakes.

To enable Asserting add these two lines to your prj.conf file:


These lines enable asserting and then indicate that user code will provide a custom assert handler. That lets you display asserts and set a breakpoint on them. Mine looks like:

// this implements a system assert for debugging purposes
void assert_post_action(const char *file, unsigned int line)
LOG_INF("*** ASSERT from %s at line %d.", log_strdup(file), line);

Using Segger Studio with Zephyr

I’ve gotten to like Segger Studio so I wanted to hook it up to Zephyr but the doc is self-contradictory and scant. There is a special version of Segger specific for the Nrf Connect SDK (which uses Zephyr) but I found it to be buggy and not useful.

Instead I used standard Segger Studio. The big trick is avoiding the many files with the same names for different architectures.

  1. Run Segger Studio
  2. Create a new project/solution using the “An externally built executable for Nordic Semiconductor nRF” option.
  3. Set the load file to the compiled .elf file (usually in the build/zephyr folder)
  4. Add two dynamic folders. The zephyr folder is carefully scanned to ignore the other (non-ARM architectures). The soc folder is specifically only scanned in the Nordic_nrf folder.

This ensures that most of the non-app source is included so that you can step through stuff. When debugging, SES will ask for a few extra .c or .h files but not many.

The other thing to do is allow building for debug. A few prj.conf entries:


And one last conf entry to allow C++


Once this is all set up you can debug with Segger Studio (building still uses the zephyr west application).

Warning: I wasted almost 4 days tracking down a bug introduced by


Apparently a number of stack settings are too small with optimization fully disabled and tracking down which one overflowed (causing a hard fault) is very very hard. Using CONFIG_DEBUG_OPTIMIZATIONS (which uses debug instead of none) seems to work better.

Home Wireless

Home automation in the wireless IOT era

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store