nRF Connect SDK Intermediate – [Lesson 9] – Exercise 4 – DFU over USB – v3.2.0

In all previous exercises (exercise 1 to 3), we utilized DFU over UART using the onboard UART-to-USB converter available on the development kit. However, incorporating a UART-to-USB converter into your products is, in many cases, not practical, as it increases the bill of materials.

Note

Since this exercise requires an nRF SoC with a USB peripheral, it is only supported on nRF5340 DK, nRF52840 DK, nRF52833 DKnRF54LM20 DK and nRF7002 DK. For a list of which SoCs contain a USB peripheral, check the Nordic Product Guide – 2023, or Product Guide – 2024.

On the other hand, using DFU over USB is advantageous for several reasons. Firstly, many products will not include debugger UART-to-USB converters, making it challenging to perform firmware updates via UART. Some Nordic SoCs come with built-in USB peripheral, eliminating the need for additional hardware and simplifying the update process. This built-in USB support is a strong selling point for reducing the bill of materials for a product using wired DFU.

All the boards that support USB (by SoC and proper connector) have a corresponding definition in the board device tree file. As an example, we can look into the nrf5340dk board device tree.

zephyr_udc0: &usbd {
	compatible = "nordic,nrf-usbd";
	status = "okay";
};
Devicetree

In addition, we can verify USB presence and it’s connections by looking into the board schematic file e.g: from the nrf5340dk hardware files.

nRF52 DKnRF52840 DK
nRF52833 DK
nRF5340 DKnRF54LM20 DKnRF54L15 DKnRF91XX DKsnRF7002 DK
USB

We will use the Zephyr CDC ACM drivers to communicate over USB. For more information on this, you can see USB device support.

We will first enable DFU over USB for serial recovery (In MCUboot) and then DFU from the application. We have covered the benefits of both options in Exercise 1. While both options could be active at the same time, as we show in this exercise, it is also possible to configure your project to support DFU in only one of the images. If you want to add USB to only MCUboot(aka: serial recovery), follow steps 1-4. If you want to add DFU over USB to only the application, follow step 5.

Exercise steps

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 9 – Exercise 4.

USB configuration for Serial Recovery

Important

Serial recovery is not supported on the nRF54LM20 yet. For this SoC only DFU over USB can be used so after completing step 1.1, please skip to step 5 .

1. Enable CDC ACM for Serial Recovery.

We need to configure both Kconfig and DTS for the MCUboot image to enable DFU over USB.

1.1 To enable CDC ACM for Serial Recovery, add CONFIG_BOOT_SERIAL_CDC_ACM to sysbuild/mcuboot.conf.

# STEP 1.1 - Configure serial recovery to use CDC_ACM, which by default uses the USB
CONFIG_BOOT_SERIAL_CDC_ACM=y
Kconfig

2. Add a CDC ACM node to the devicetree.

We also need to add CDC_ACM to DTS.

2.1 Add the following snippet in sysbuild/mcuboot.overlay (see USB device support).

/* STEP 2.1 - Configure CDC ACM */
&zephyr_udc0 {
	cdc_acm_uart0: cdc_acm_uart0 {
		compatible = "zephyr,cdc-acm-uart";
	};
};
Devicetree

2.2 Build the project.

We should now get the error (1). The size of the overflow will depend on which board is used because different boards can have different features enabled by default, which is inherited by MCUboot.

This error can come from any of the Sysbuild images, and it is not always obvious which image it comes from. We can see in (2) which image it is from, in this case, MCUboot. Therefore, we must either decrease features in MCUboot or increase the size of MCUboot. In this case: Since we added USB drivers to MCUboot, we need space.

3. Increasing the partition for the MCUboot bootloader.

3.1 Increase flash space for MCUboot child image, to fit USB drivers.

MCUboot partition size is 0xC000 by default. To increase this, set CONFIG_PM_PARTITION_SIZE_MCUBOOT to 0x11000 in sysbuild/mcuboot.conf. If you still get the above error, increase the size further. This is likely to be the case for the nRF5340 DK and the nRF7002 DK:

# Step 3.1 - Increase flash space for MCUboot child image, to fit USB drivers
CONFIG_PM_PARTITION_SIZE_MCUBOOT=0x11000
Kconfig

3.2 Increase the maximum number of image sectors MCUboot can handle (when using nRF54LM20DK).

# Step 3.2 - Increase the maximum number of image sectors MCUboot can handle (nRF54LM20DK)
CONFIG_BOOT_MAX_IMG_SECTORS=512
Kconfig

4. Testing Serial Recovery over USB.

4.1 Build and flash the application to your board.

4.2 Connect to the nRF USB port.

In this step, we will assume that the hardware has no Debugger/Interface MCU (IMCU), and we will use the nRF USB port to transport the firmware images.

Disconnect your board from the regular Debugger USB port and connect to the nRF USB port. The nRF USB port is only available on the nRF5340 DK, nRF52840 DK, nRF52833 DK, and nRF7002 DK since it requires an nRF SoC with a USB peripheral.

4.3 Follow the steps in Exercise 1 – Steps 4.1 – 4.6, but connect via the nRF USB instead of the debugger USB.

USB configuration for DFU from the application

5. Change the application to use CDC ACM for DFU.

Next, we will change the application to use CDC ACM for DFU. The configuration we use here is inspired by the SMP Server sample.

5.1 In the file app.overlay, we will first configure CDC ACM by adding the following:

/* STEP 5.1 - Configure CDC ACM */
&zephyr_udc0 {
	cdc_acm_uart0: cdc_acm_uart0 {
		compatible = "zephyr,cdc-acm-uart";
	};
};
Devicetree

5.2 Then we point the uart-mcumgr driver to CDC ACM . by adding the line zephyr,uart-mcumgr = &cdc_acm_uart0; as chosen.

/* STEP 5.2 - Choose CDC ACM for mcumgr */
/ {
	chosen {
		zephyr,uart-mcumgr = &cdc_acm_uart0;
	};
};
Devicetree

5.3 Next, we will have to add Kconfig options to prj.conf:

# STEP 5.3 - Enable USB subsystem
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_UART_USE_RUNTIME_CONFIGURE=y
CONFIG_UART_LINE_CTRL=y
CONFIG_USBD_CDC_ACM_CLASS=y
Kconfig

Note

As USB stack is not the topic we cover in this lesson we will use functions and macros from Zephyr samples to handle all the steps needed for device initialization (the process is described in USB device support) . These can be found at  USB common.

6 Include USB common helpers from the SDK

Starting from nRF Connect SDK version 3.2.0, we need to use new USB stack (from Zephyr v4.3 ). It comes together with samples containing: descriptors, configuration files, and device initialization functions. Let`s configure our project to use them.

6.1 Include common files from Zephyr USB samples in CMakeLists.txt

include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
CMake

6.2 Create Kconfig file that links to common USB sample settings

source "samples/subsys/usb/common/Kconfig.sample_usbd"

source "Kconfig.zephyr"
Kconfig

7 Include Zephyr USB stack module.

7.1 USB must be enabled in our source files (main.c). First, include the header file for USB. We will use the header from the sample that includes all the necessary modules.

#include <sample_usbd.h>
C

7.2 Create context for USB stack

static struct usbd_context *sample_usbd;
C

7.3 Create a USB callback function that handles VBUS events. It enables the USB device when VBUS is detected and disables the device when the device is disconnected from the bus.

static void sample_msg_cb(struct usbd_context *const ctx, const struct usbd_msg *msg)
{
    LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type));

    if (usbd_can_detect_vbus(ctx)) {
        if (msg->type == USBD_MSG_VBUS_READY) {
            if (usbd_enable(ctx)) {
                LOG_ERR("Failed to enable device support");
            }
        }

        if (msg->type == USBD_MSG_VBUS_REMOVED) {
            if (usbd_disable(ctx)) {
                LOG_ERR("Failed to disable device support");
            }
        }
    }
}
C

7.4 Use the sample helper function to initialize the USB stack in the device. It manages the entire initialization procedure, allowing us to focus on the DFU functionality.

    sample_usbd = sample_usbd_init_device(sample_msg_cb);
    if (sample_usbd == NULL) {
        LOG_ERR("Failed to initialize USB device");
        return -ENODEV;
    }
C

7.5 Enable the USB device in case the VBUS is not detected

    if (!usbd_can_detect_vbus(sample_usbd)) {
        ret = usbd_enable(sample_usbd);
        if (ret) {
            LOG_ERR("Failed to enable device support");
            return ret;
        }
    }
C

8. Flash the application and upgrade the firmware version.

8.1 Build and flash the application to your board.

8.2 Connect your computer to the nRF USB port on the DK (this step was described in step 4.2).

8.3 Perform DFU for the DK in the way we learned in Exercise 1 – Steps 5.3 – 5.4, but connected via the nRF USB instead of the debugger USB.

Switch language?

Progress is tracked separately for each language. Switching will continue from your progress in that language or start fresh if you haven't begun.

Your current progress is saved, and you can switch back anytime.

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.