Debugging nRF9160 Zephyr applications with Ozone

Ozone is a free graphical debugger for embedded firmware from SEGGER. It’s a powerful tool that can give you deep visibility into what’s happening in your embedded system. It’s especially useful when debugging nRF9160 Zephyr apps. Sorting out multiple threads and multi image builds can be tough, but this is the tool you want.

In our previous post Taking the next step: Debugging with SEGGER Ozone and SystemView on Zephyr, Chris Gammell wrote about how to set up a SEGGER Ozone project to debug a Zephyr app running on the i.MX RT1060 Evaluation Kit. It’s a great general introduction to debugging a Zephyr app in Ozone and profiling the RTOS runtime behavior SystemView.

When I was trying to set up a similar Ozone project for debugging the Nordic nRF9160 SIP, I ran into a few snags along the way. Today, I’ll share what I’ve learned!

In this article, I’ll walk through how to:

  • Configure a nRF9160 Zephyr app for thread-aware debugging
  • Create an Ozone project for nRF9160 with the New Project Wizard
  • Modify the Ozone project to support debugging nRF9160 multi-image builds

Hardware Configuration

In the examples that follow, I’ll be using the Nordic nRF9160 DK board. This development kit from Nordic has a SEGGER J-Link OB debugger built into the board, so an external J-Link debugger is not required to follow-along with the examples.

 

Thread-awareness support in Zephyr

In a typical Zephyr app built using the Golioth Zephyr SDK, there will be multiple threads. For instance, one for the app’s main loop, one for the Golioth system client, and others for the UART shell, logging subsystem, network management, etc.

SEGGER provides a Zephyr RTOS plugin for Ozone that can show the status of each thread, but it requires that the Zephyr firmware is built with support for thread-aware debugging. Zephyr provides a CONFIG_DEBUG_THREAD_INFO Kconfig symbol that instructs the kernel to maintain a list of all threads and enables thread names to be visible in Ozone.

While you could simply add CONFIG_DEBUG_THREAD_INFO=y to your app’s prj.conf file, you probably only want to enable this extra debug info when you are building for debugging purposes. Instead, we can create an additional debug.conf Kconfig file that will only get merged in when we pass the -DEXTRA_CONF_FILE=debug.conf argument to the build system.

Since this article is about using Ozone for thread-aware debugging, we’ll use the zephyr/samples/basic/threads/ app from the nRF Connect SDK Zephyr repo as our example app in this article.

If this is your first time building one of the Zephyr sample apps, make sure to complete the nRF Connect SDK Installation Guide first to make sure your dev environment is set up correctly.

How to Enable Thread-Awareness

First, create a zephyr/samples/basic/threads/debug.conf file and add the following lines:

CONFIG_DEBUG_THREAD_INFO=y

# CONFIG_DEBUG_THREAD_INFO needs the heap memory pool to
# be defined for this app
CONFIG_HEAP_MEM_POOL_SIZE=256

Next, build the firmware, specifying the debug.conf file to be merged into the build configuration:

cd <your ncs workspace>/
west build -p -b nrf9160dk_nrf9160_ns zephyr/samples/basic/threads/ -- -DEXTRA_CONF_FILE="debug.conf"

If the build completed successfully, you’ll see the build/zephyr/zephyr.elf file we need to start a debugging session in Ozone.

Create the Ozone project

Now that we’ve built the firmware, you can launch Ozone and use the New Project Wizard to create an Ozone project:

Choose the nRF9160_xxAA device:

Select the J-Link device you want to use:

Select the build/zephyr/zephyr.elf ELF file we generated in the previous section:

Leave these fields the default values for now (we’ll update them later on):

After clicking “Finish”, you’ll see the Ozone project window appear.

In the “Console” window, run the following command to load the Zephyr RTOS plugin:

Project.SetOSPlugin("ZephyrPlugin.js");

You should now see a new “Zephyr” window in the Ozone project (if not, click on “View” → “Zephyr” to show the window):

Finally, save the project file by clicking on “File” → “Save Project as…”:

Start the debug session

Now that we’ve configured the Ozone project, we can start the debug session.

Click on “Debug” → “Start Debug Session” → “Download & Reset Program”:

Surprise! When the firmware starts to run, you’ll see a pop-up window indicating that the target has stopped in a HardFault exception state!

At this point, you might be wondering what’s going on here…

We’ve followed the same basic steps as outlined in our previous article, so why isn’t this working for the nRF9160?

Here’s a hint: the answer has to do with multi-image builds.

The missing step: flashing the merged image

You may have noticed that the board argument we passed to west build (nrf9160dk_nrf9160_ns) ends in _ns. This suffix is an indicator that the firmware will be built with Trusted Firmware-M (TF-M). This is the reference implementation of ARM’s IoT Security Framework called Platform Security Architecture (PSA).

TFM uses the ARM TrustZone security features of the nRF9160’s Cortex-M33 MCU to partition the MCU into a Secure Processing Environment (SPE) and Non-Secure Processing Environment (NSPE).

Here’s how the boot process works in a nutshell:

  1. When the MCU boots up, it starts executing in the secure environment (SPE).
  2. The boot process can optionally start with a secure bootloader chain using NSIB and/or MCUboot.
  3. If used, the bootloader starts TF-M, which configures a part of the MCU memory and peripherals to be non-secure.
  4. TF-M starts your Zephyr application which runs in the non-secure environment (NSPE).

When we build for _ns build targets, the TF-M image is automatically built and linked with the Zephyr app. If you look in the build/zephyr/ output directory, you’ll see a file named merged.hex, which is a single merged file containing the MCUboot bootloader (optional), the TF-M secure image, and the non-secure Zephyr app.

west flash knows to flash the full merged image, but Ozone doesn’t do this by default!

We need to configure Ozone to load the full merged image and start execution in the secure environment.

Fixing the Ozone project file

We’ll make a couple changes directly in the Ozone project file, which can be opened within Ozone by clicking “File” → “Edit Project File”:

Flash the merged image

Navigate to the TargetDownload section of the Ozone project file and add the following to configure Ozone to flash the merged image (changing the path to match the merged image file in your project):

/*********************************************************************
*
* TargetDownload
*
* Function description
* Replaces the default program download routine. Optional.
*
**********************************************************************
*/
void TargetDownload(void)
{
  Exec.Download("$(ProjectDir)/build/zephyr/merged.hex");
}

Fix the Vector Table & PC addresses

Navigate to the _SetupTarget section of the Ozone project file and make the following changes:

  1. Set the vector table address to 0
  2. Read the entry point program counter address from the vector table
/*********************************************************************
*
*       _SetupTarget
*
* Function description
*   Setup the target.
*   Called by AfterTargetReset() and AfterTargetDownload().
*
*   Auto-generated function. May be overridden by Ozone.
*
**********************************************************************
*/
void _SetupTarget(void) {
  unsigned int SP;
  unsigned int PC;
  unsigned int VectorTableAddr;

  VectorTableAddr = 0;
  //
  // Set up initial stack pointer
  //
  SP = Target.ReadU32(VectorTableAddr);
  if (SP != 0xFFFFFFFF) {
    Target.SetReg("SP", SP);
  }
  //
  // Set up entry point PC
  //
  PC = Target.ReadU32(VectorTableAddr + 4);
  if (PC != 0xFFFFFFFF) {
    Target.SetReg("PC", PC);
  } else {
    Util.Error("Project script error: failed to set up entry point PC", 1);
  }
}

When you save the project file, you should get a modal pop-up asking if you want to reload the project.

Choose “Yes”:

Restart the debug session:

After the image has been flashed to the device, you should see the debugger halted at main:

Click “Debug” → “Continue”:

The firmware should now run without exceptions!

Summary

Hopefully this helped you get started debugging the nRF9160 with Ozone.

The nRF9160 is fully supported in Zephyr, and has the highest level of support in the Golioth IoT device management platform (Continuously Verified). With Golioth, you can connect and secure your devices, send sensor data to the web, update firmware over-the-air, and scale your fleet with an instant IoT cloud.

Try it today—with Golioth’s Dev tier your first 50 devices are free!

Talk with an Expert

Implementing an IoT project takes a team of people, and we want to help out as part of your team. If you want to troubleshoot a current problem or talk through a new project idea, we're here for you.

Start the discussion at forum.golioth.io