If you are having issues with the exercises, please create a ticket on DevZone:
Click or drag files to this area to upload. You can upload up to 2 files.

Exercise 1 – Advanced debugging in nRF Connect for VS Code

In this exercise, we’ll enhance our understanding of using the debugger in nRF Connect for VS Code. Building upon the basics covered in Lesson 1-Exercise 1, we’ll experiment with direct interaction with SoC/SiP peripherals through direct reading or writing operations using the debugger. Then, we’ll explore advanced debugging methods, including conditional breakpoints, non-halting debugging with Cortex-M Debug Monitor, which is useful for scenarios like debugging a Bluetooth LE application and debugging nRF Connect SDK /Zephyr libraries using non-invasive methods.

This exercise consists of two parts. First, we will use the peripheral view available when using the debug feature in VS Code to control the LED of the development kit. In the second part, we will take a look at advanced debugging techniques.

Exercise steps

PART 1 – Peripherals Interactions

Open the code base of the exercise by navigating to Create a new application in the nRF Connect for VS Code extension, select Copy a sample, and search for Lesson 2 – Exercise 1.

1. Add a build configuration.

Create a new build configuration for your device. Select Optimize for debugging in the Optimization menu and build configuration

2. Enter debug mode in VS Code.

Inside the nRF Connect extension, select the debug action

3. Overview of the peripherals.

The application is halted at the start of the main loop. Now we shall toggle LED 1 by manipulating the GPIO register values. In this exercise, an nRF52840 is used, however, the steps will be the same on other devices, only the pin number will differ. By looking at the bottom side of the DK, you can see which pin is connected to LED1. On the nRF52840dk LED1 is connected to P0.13.

3.1. Exploring the GPIO

Find the GPIO P0 menu in the peripherals view

3.2. Configure the pins

Expand the “DIR” submenu so that you can find PIN 13

Configure PIN 13 as output by changing it from Input to Output

3.3. Toggle the LED

Expand the “OUT” submenu so that you can find PIN 13

The LED1 should now be turned on. To toggle it off, set PIN 13 to High

PART 2 – Advanced debugging techniques

4. Add some basic logic to the application.

Add the following code to main.c

5. Build and flash the application.

Connect to the serial interface for the device, build the application, and flash the device.

When the application runs, you should see the following output.

6. Observe something strange…

Something is strange here, 127+1 should not equal -128. So let’s use the debugging feature in VScode to understand what is happening.

6.1. Adding a conditional breakpoint

We have seen how to add a regular breakpoint in Lesson 1 – Exercise 1, however, there are situations where a regular breakpoint is not the best alternative. For example, the integer overflow problem that we experienced when running the code is encountered inside a loop, and it is manifested right after the condition (test_var == 127), after that, things go sideways. Using a regular breakpoint inside a loop means that we will need to halt the code on every iteration, and we need to manually press Continue until we reach the condition that triggers the issue. This, in some situations, could be either an impractical or inefficient process. For such cases will use a conditional breakpoint.

We will add an Advanced conditional breakpoint of type Expression condition, which is triggered only when an expression evaluates to true. In our simple example, that would be (test_var == 127).

6.1.1 Right-click on the line where test_var is incremented and select Add Conditional Breakpoint

6.1.2 in the Expression field, type in the condition we want to halt execution on (test_var == 127). Make sure to press enter.

6.2 Add a regular breakpoint on main() so we can compare the two types.

Notice that a conditional breakpoint has a different icon than the regular breakpoint icon.

6.3 Run the application in debug mode

6.4 The application will halt at main() since we added a regular breakpoint there. Open nRF Debug -> Memory Explorer ->Ram -> Go to Symbol.

6.5 Type in the global variable you would like to examine in memory; in our case, that would be test_var.

The memory region allocated to that variable will be highlighted as shown below:


The “Go to Symbol” works only for global objects (global variable, global functions, etc.). For local objects, you need to find out the address of the local object and use the “Go to address” functionality. It’s the # symbol.

The memory explorer will show you the memory region around where the object of interest is located (Eg. the variable test_var). This is quite useful to detect if there is any out-of-bound memory writing. The memory explorer also allows you to read/write directly to memory same as we did for peripherals.

6.6 Hit the Continue button or F5 to run the application to the next breakpoint, which is a conditional breakpoint that is triggered right before the integer overflow happens.

Notice how the memory location allocated to the variable test_var holds the value 127(0x7F) and how we can see neighboring memory addresses of the variable. We can actually hover the mouse over the neighboring cells and find out which objects are located in that region. This is quite useful when debugging stack and large buffers overflow.

You can also right-click on the variable name and select Add to Watch, the same as we did in Lesson 1-Exercise 1.

6.7 Press Step Over (F10) to execution the next line

The test_var is defined as an int8_t variable, which is a signed integer type with a width of exactly 8 bits. What’s happening is that a signed 8-bit int has a minimum value of -128 and a maximum value of 127. So when test_var had the value of 127 and we tried to increment it again, we tried to add a too-large value for an 8-bit int, which caused an integer overflow. For the compiler used, the behavior was to flip the variable around to the minimum value. Please be aware that integer overflow has an outcome of undefined behavior by the C standard, so such situations should be prevented in the code by applying the right programming techniques (For example: Check against INT8_MAX and INT8_MIN before incrementing/decrementing).

Overflow and underflow errors can lead to unexpected results in the application, so it’s important to be aware of them and take steps to prevent them. The same may happen in a stack; this is called a stack overflow. A stack overflow happens if the stack uses more memory than what is allocated to it.

7. Adding a logpoint
We have seen how to add a regular breakpoint in Lesson 1 – Exercise 1 , and now how to add a conditional breakpoint. Now we will look into adding logpoint. Logpoint can be used if you want to get information from, for example, a standard library without adding log commands and therefore change standard libraries. We will also see how we can run the debug sessions in non-halting mode.

7.1 Add the following Kconfig flags to prj.conf

The purpose of the first two Kconfigs is to enable non-halting debugging with Cortex-M Debug Monitor. The debugging process can run in two modes. The halt-mode debugging stops the CPU when a debug request occurs(default behavior). The monitor-mode debugging lets a CPU debug parts of an application while crucial functions continue. Unlike halt-mode, the monitor-mode (aka: non-halting) is useful for scenarios like debugging a Bluetooth LE application, PWM motor control, etc., where halting the entire application is risky. The CPU takes debug interrupts, running a monitor code for J-Link communication and user-defined functions. The last Kconfig CONFIG_DK_LIBRARY is to enable the Button and LED Library for Nordic DK library so we can experiment with debugging an nRF Connect SDK/Zephyr library non-invasively.

7.2 Now, we will include a library for the buttons and LEDs on development kits which we shall add a log point.
7.2.1 Add the following at the top of main.c

#include <dk_buttons_and_leds.h>

7.2.2 Add the following:

static void button_changed(uint32_t button_state, uint32_t has_changed)
	if (has_changed & DK_BTN1_MSK) {

static int init_button(void)
	int err;

	err = dk_buttons_init(button_changed);
	if (err) {
		printk("Cannot init buttons (err: %d)\n", err);

	return err;

7.2.3 Inside main() add the following:

	int err;

	err = dk_leds_init();
	if (err) {
		printk("LEDs init failed (err %d)\n", err);
		return 0;

	err = init_button();
	if (err) {
		printk("Button init failed (err %d)\n", err);
		return 0;

7.3 Build the application with debug optimization

7.4 Open dk_button_and_leds.c and find the definition of the function dk_set_led(). This can be done by holding the Ctrl key and clicking on the dk_set_led_on() function, then doing the same on dk_set_led().
Right-click and add a logpoint inside the function to get the value for led_idx .
Add the following expression: The value of led_idx: {led_idx} . The value of led_idx will be printed on the terminal and appended the string: the value of led_idx:

Notice that the log point has a diamond icon to separate it from a regular breakpoint and a conditional breakpoint.

7.4 Run the debug action

7.4.1 When the debug session has paused at the top of main, insert the following command in the debug console of nRF Connect for VS Code

7.4.2 Press Continue or F5 to resume the debug session. Now, when you press button 1 on the development board, you will get a log message in the debug console showing the value of led_idx.

Using this method we can get logs and object values without the need to explicitly add printk() statements or LOG_XXX() macros into the firmware and re-building a new firmware. We didn’t even need to halt the execution; such debugging techniques are quite useful for debugging applications such as Bluetooth LE applications, where halting the CPU completely will result in the Bluetooth Controller asserting, causing a device reset.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.