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

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

Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
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

v3.0.0

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 – Peripheral interaction

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. Make sure to use the version directory that matches the SDK version you are using

Alternatively, in the GitHub repository for this course, go to the base code for this exercise, found in l2/l2_e1 of whichever version directory you are using.

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

Note

If you are building with sysbuild you need to select the child image before the debug option is available

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 Figure out which GPIO pin is connected to LED1 on your board.

By looking at the bottom of your DK, you can see which pin is connected to LED1. This is the pin we will edit in the following steps.

On the nRF52840 DK, LED1 is connected to P0.13.

3.2 Exploring the GPIO

Find the GPIO P0 or P1 menu in the peripherals view, depending on if you want to change P0.xx or P1.xx. In this we want to change P0.13 so we expand the GPIO P0 menu.

3.2. Configure the pins

Expand the “DIR” submenu so that you can find the pin you want to change. In this case, we will change PIN 13

Configure the pin (PIN 13 in this case) as output by changing it from Input to Output.

3.3. Toggle the LED

Expand the “OUT” submenu to find the same pin again (PIN 13 in this case).

Change the pin from low to high and back to toggle LED1.

Part 2 – Advanced debugging techniques

4. Add some basic logic to the application.

Add the following code to main.c

Copy
for (int i = 0; i < 10; i++)
{
	test_var = test_var + 1;
	LOG_INF("test_var = %d",test_var);
}
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.

*** Booting nRF Connect SDK ***
Starting Exercise 1!
test_var = 125
test_var = 126
test_var = 127
test_var = -128
test_var = -127
test_var = -126
test_var = -125
test_var = -124
test_var = -123
test_var = -122
Terminal

6. Observe something strange…

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

6.1. Add 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 in the column (next to the line number) 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

The application will halt at main() since we added a regular breakpoint there.

6.4 Open nRF Debug -> Memory Explorer -> Data

Then click the Go to Symbol icon in the upper right hand corner in the data window, see the image below.

More on this

To get the same colorful view as the image above, select the Show symbols icon in the menu, see image below.

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:

Note

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, which is the # symbol.

The memory explorer will show you the memory region around where the object of interest is located (e.g. 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 (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 execute 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.

Note

The next step is only for nRF52 Series devices at the moment

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

Copy
CONFIG_CORTEX_M_DEBUG_MONITOR_HOOK=y
CONFIG_SEGGER_DEBUGMON=y
CONFIG_DK_LIBRARY=y
Kconfig

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:

  • Halt-mode debugging: Stops the CPU when a debug request occurs (default behavior)
  • Monitor-mode debugging: Lets a CPU debug parts of an application while crucial functions continue.

Unlike halt-mode, the monitor-mode (which is non-halting) is useful for scenarios where halting the entire application is risky, like debugging a Bluetooth LE application, PWM motor control, etc. 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 DK Buttons and LEDs Library so we can experiment with debugging an nRF Connect SDK or Zephyr library non-invasively.

7.2 Include the DK Buttons and LEDs library.

7.2.1 Include the header file for the DK Buttons and LEDs library.

Add the following at the top of main.c

Copy
#include <dk_buttons_and_leds.h>
C

7.2.2 Define and initialize a callback handler for button presses.

Add the following:

Copy
static void button_changed(uint32_t button_state, uint32_t has_changed)
{
	if (has_changed & DK_BTN1_MSK) {
		dk_set_led_on(DK_LED1);
	}
}

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;
}
C

7.2.3 Initialize the DK Buttons and LEDs library.

Inside main() add the following:

Copy
	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;
	}
C

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 logpoint 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

Copy
-exec monitor exec SetMonModeDebug=1
Terminal command

7.4.2 Resume the debug session.

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.

v3.0.0
v3.0.0