This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

DFU over USB - nRF52840 running Zephyr

Hello, gentleman!

We are developing our nRF52840-based application using nRF Connect SDK v1.4.2. I know there is a NCS v1.5.1 already available, we plan to switch to that version pretty soon. 

We would like to have the ability to update nRF52840 firmware by connecting it to the PC over USB and transferring updated firmware over USB serial link (USB DFU profile). 

I've been researching a little bit and came across this guide explaining how to perform DFU with MCUBoot bootloader using a serial USB connection to a host PC. Even though this example contains everything we need and helped us a lot to understand the DFU process, it contains two parts that we would like to be able to automate:

  1. Once we have our initial firmware version together with the MCUBoot bootloader flashed into nRF52840, we would like to be able to put the nRF52840 into MCUBoot's DFU mode from our firmware (e.g. at the moment we detect USB power). This process is performed manually in the guide by pressing a Reset button while holding a Button 1 on DK (see step 27). 
  2. Similarly, once the DFU process is finished and new firmware binary file transferred over USB, we need to hit Reset button on nRF52840 DK board in order to restart CPU and boot updated firmware (step 35). Again, we would like to be able to automatically restart the nRF52840 once the DFU process is done because we will not have Reset button exposed to the end user in our final product design. 

With that said, I would have a few questions related to the above limitations:

- Is it possible to somehow put the MCUBoot in DFU mode from inside the firmware? I know that, in the case of nRF SDK and baremetal C approach, we had the possibility to play with the GPREGRET register and put the nRF52 in DFU mode with the following piece of code, for example:

void reboot_into_bootloader(void) {
  uint32_t gp_reg_ret;

  APP_ERROR_CHECK(sd_power_gpregret_set(0, BOOTLOADER_DFU_START));
  nrf_delay_ms(1500);
  APP_ERROR_CHECK(sd_power_gpregret_get(0, &gp_reg_ret));
  perform_system_reset();
}

Can we do something similar from the NCS/Zephyr environment?

- Would it be possible to catch from the NCS/Zephyr side when USB Power is detected/removed on nRF52840 (APP_USBD_EVT_POWER_DETECTED event from nRF SDK world)?

- Finally, is there a way to automatically restart the board once DCU process is finished without a need to manually press Reset button?

Thank you very much for your time and efforts, guys. It is really appreciated. 

Cheers,

Bojan.

  • Hi,

    That is odd. I have not found any code in NCS that write to GPREGRET2. Also, I did a very quick test using the basic blinky sample from Zephyr in NCS 1.5.1 where I just added CONFIG_BOOTLOADER_MCUBOOT=y in prj.conf and read and updated GPREGRET2 before doing a soft reset repeatedly, and that works as expected as you can see here:

    Code:

    /*
     * Copyright (c) 2016 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr.h>
    #include <device.h>
    #include <devicetree.h>
    #include <drivers/gpio.h>
    #include <hal/nrf_power.h>
    #include <system_nrf52840.h>
    
    /* 1000 msec = 1 sec */
    #define SLEEP_TIME_MS   1000
    
    /* The devicetree node identifier for the "led0" alias. */
    #define LED0_NODE DT_ALIAS(led0)
    
    #if DT_NODE_HAS_STATUS(LED0_NODE, okay)
    #define LED0	DT_GPIO_LABEL(LED0_NODE, gpios)
    #define PIN	DT_GPIO_PIN(LED0_NODE, gpios)
    #define FLAGS	DT_GPIO_FLAGS(LED0_NODE, gpios)
    #else
    /* A build error here means your board isn't set up to blink an LED. */
    #error "Unsupported board: led0 devicetree alias is not defined"
    #define LED0	""
    #define PIN	0
    #define FLAGS	0
    #endif
    
    void main(void)
    {
    	int count = 0;
    
    	static uint8_t gpregret2_val;
    
    	printk("Started...\n");
    
    	gpregret2_val = nrf_power_gpregret_get(NRF_POWER);
    
    	printk("GPREGRET2 value: 0x%02x\n", gpregret2_val);
    
    	nrf_power_gpregret_set(NRF_POWER, gpregret2_val + 1);
    
    	const struct device *dev;
    	bool led_is_on = true;
    	int ret;
    
    	dev = device_get_binding(LED0);
    	if (dev == NULL) {
    		return;
    	}
    
    	ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS);
    	if (ret < 0) {
    		return;
    	}
    
    	while (1) {
    		count++;
    		gpio_pin_set(dev, PIN, (int)led_is_on);
    		led_is_on = !led_is_on;
    		k_msleep(SLEEP_TIME_MS);
    
    		if (count > 5)
    		{
    			NVIC_SystemReset();
    		}
    	}
    }
    

    Log:

    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    I: Starting bootloader
    I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Boot source: none
    I: Swap type: none
    I: Bootloader chainload address offset: 0xc000
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    Started...
    GPREGRET2 value: 0x00
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    I: Starting bootloader
    I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Boot source: none
    I: Swap type: none
    I: Bootloader chainload address offset: 0xc000
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    Started...
    GPREGRET2 value: 0x01
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    I: Starting bootloader
    I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    I: Boot source: none
    I: Swap type: none
    I: Bootloader chainload address offset: 0xc000
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    Started...
    GPREGRET2 value: 0x02

    Can you share how you read and write to GPREGRET2 in your app and in MCUBoot?

  • Hey, Einar. 

    I already shared how I write to GPREGRET/GPREGRET2 registers in one of my above messages. Now, when I compare that with the code you shared, I am realizing I did it wrong. I did not use NRF_POWER as a function argument, that was my mistake. Face palm tone1

    I confirm that both GPREGRET/GPREGRET2 registers are keeping the value written to them before a soft restart with NVIC_SystemReset() function. Consequently, I was able to enter DFU mode without pressing any button (butonless DFU)! Thanks for bearing with me!

    One more observation I wanted to share with you... because of the following piece of code inside MCUBoot bootloader (bootloader\mcuboot\boot\zephyr\main.c):

    #ifdef CONFIG_MCUBOOT_SERIAL
    
        struct device const *detect_port;
        uint32_t detect_value = !CONFIG_BOOT_SERIAL_DETECT_PIN_VAL;
    
        detect_port = device_get_binding(CONFIG_BOOT_SERIAL_DETECT_PORT);
        __ASSERT(detect_port, "Error: Bad port for boot serial detection.\n");
    
        /* The default presence value is 0 which would normally be
         * active-low, but historically the raw value was checked so we'll
         * use the raw interface.
         */
        rc = gpio_pin_configure(detect_port, CONFIG_BOOT_SERIAL_DETECT_PIN,
    #ifdef GPIO_INPUT
                                GPIO_INPUT | GPIO_PULL_UP
    #else
                                GPIO_DIR_IN | GPIO_PUD_PULL_UP
    #endif
               );
        __ASSERT(rc == 0, "Error of boot detect pin initialization.\n");
    
    #ifdef GPIO_INPUT
        rc = gpio_pin_get_raw(detect_port, CONFIG_BOOT_SERIAL_DETECT_PIN);
        detect_value = rc;
    #else
        rc = gpio_pin_read(detect_port, CONFIG_BOOT_SERIAL_DETECT_PIN,
                           &detect_value);
    #endif
        __ASSERT(rc >= 0, "Error of the reading the detect pin.\n");
        if (detect_value == CONFIG_BOOT_SERIAL_DETECT_PIN_VAL &&
            !boot_skip_serial_recovery()) {
            BOOT_LOG_INF("Enter the serial recovery mode");
            rc = boot_console_init();
            __ASSERT(rc == 0, "Error initializing boot console.\n");
            boot_serial_start(&boot_funcs);
            __ASSERT(0, "Bootloader serial process was terminated unexpectedly.\n");
        }
    #endif

    CPU were entering DFU mode every time the board is power-cycled. Not only when Button 1 is held down while Reset button is pressed but every time the board is power-cycled. I disabled that piece of code and I am now entering DFU mode only through the GPREGRET2 register. 

    As a cherry on the top, it remains to replace the .pem key that comes with the MCUBoot bootloader with our own public/private keypair. I will feel free to create a new ticket if I face any difficulty there. 

    One more time, thanks for your great support!

    Cheers, Beers

    Bojan.

Related