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.

Parents
  • Hi Bojan,

    The guide you refer to demonstrates the serial recovery feature of MCUBoot, where the DFU process is handled entirely in the bootloader. There is no option to enter DFU by GPREGret there, but you can add it yourself by modifying MCUBoot to check for a magic word in the retention register that you set in your application before resetting. In that case you could look at how this is handled in main() in bootloader\mcuboot\boot\zephyr\main.c and add your logic there.

    Regarding the second question, you can reset via mcumgr using "mcumgr reset".

    Einar

  • Hello, Einar. 

    Thanks for your explanations! 

    I can report that resetting nRF via mcumgr  using:

    mcumgr --conntype=serial --connstring=<USB Serial Device> reset

    after binary file is transferred over USB works fine! 

    Concerning the first question (entering DFU by GPREGRET), I tried what you suggested. Inside the application code, I tried to write the 0xB1 value into GPREGRET register and restart the board after that with the following piece of code:

    static void reboot_into_bootloader(void) {  
      #define BOOTLOADER_DFU_GPREGRET_MASK            (0xB0)      
      #define BOOTLOADER_DFU_START_BIT_MASK           (0x01)  
      #define BOOTLOADER_DFU_START    (BOOTLOADER_DFU_GPREGRET_MASK | BOOTLOADER_DFU_START_BIT_MASK)
      NRF_POWER_Type gp_reg_ret;
      nrf_power_gpregret_set((NRF_POWER_Type *)&gp_reg_ret, BOOTLOADER_DFU_START);
      k_msleep(1500);
      uint8_t gp_reg;
      gp_reg = nrf_power_gpregret_get(&gp_reg_ret);
      printk("System resetting. GPREGRET: %d\n", gp_reg_ret.GPREGRET);
      printk("System resetting. GPREGRET: %d\n", gp_reg);
      NVIC_SystemReset();
    }

    On the other side, inside main.c file located at bootloader\mcuboot\boot\zephyr\main.c, I tried to read the content of GPREGRET register during the boot process with the following piece of code:

    BOOT_LOG_INF("Checking GPREGRET register...");
    NRF_POWER_Type gp_reg_ret;
    uint8_t gp_reg;
    gp_reg = nrf_power_gpregret_get(&gp_reg_ret);
    BOOT_LOG_INF("System booting. GPREGRET: %d\n", (gp_reg_ret.GPREGRET & 0xFF));
    BOOT_LOG_INF("System booting. GPREGRET: %d\n", gp_reg);

    The content of GPREGRET register after restart is equal to 0xC3!

    Do you have any idea why those two values differ? According to some threads (link), GPREGRET register is a RAM mapped retained register which will lose it's content after power-cycle. However, it keeps its content after a wakeup from SYSTEMOFF mode. I was using NVIC_SystemReset() function to reset the CPU. Could it be that the register lost its value because of the way CPU is restarted?

    Also, should I clear the GPREGRET register before writing any value there? nRF SDK has a function for that (sd_power_gpregret_clr()) but I was unable to find anything similar in <hal/nrf_power.h>

    Cheers,

    Bojan.

  • Hi Bojan,

    The retention registers are retained during a soft reset (NVIC_SystemReset()) but not during a pin reset. I did not think about that. GPREGRET is allready in use by sys_arch_reboot() implementatino for nRF52 in zephyr\soc\arm\nordic_nrf\nrf52\soc.c, which is called by sys_reboot(). But GPREGRET2 seems to be unused, so if you use that instead I think you should be good.

    The retention register is not cleared automatically, so you you want to clear it once you have read it in MCUboot and decided to enter DFU mode so that it does not repeat again on next reset and so on.

    Einar

  • Hello, Einar. 

    I tried the same thing with GPREGRET2 register but the same thing happens. No matter what I write into GPREGRET/GPREGRET2 registers, I always read 0xC3 and 0xF6, respectively, after soft resetting the CPU with NVIC_SystemReset() function. 

  • I tried the same thing with the latest NCS v1.5.1 as well. Again, GPREGRET/GPREGRET2 registers have some constant value after soft reset no matter what I write into them before the soft reset. In the case of NCS v1.5.1 the value of those registers differ compared to the v1.4.2:


    v1.4.2

    Button has changed. State: 1
    Button 1 pressed
    Setting GPREGRET/GPREGRET2 registers...
    Before soft reset. GPREGRET: 86
    Before soft reset. GPREGRET2: 172
    *** Booting Zephyr OS build v2.4.0-ncs2  ***
    [00:00:00.254,730] <inf> mcuboot: Starting bootloader. Test!
    [00:00:00.254,760] <inf> mcuboot: Checking GPREGRET/GPREGRET2 registers...
    [00:00:00.254,760] <inf> mcuboot: System booting. GPREGRET: 195
    [00:00:00.254,760] <inf> mcuboot: System booting. GPREGRET2: 246
    [00:00:00.255,249] <inf> mcuboot: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    [00:00:00.255,249] <inf> mcuboot: Boot source: none
    [00:00:00.255,371] <inf> mcuboot: Swap type: none
    *** Booting Zephyr OS build v2.4.0-ncs2  ***


    v1.5.1

    Button has changed. State: 1
    Button 1 pressed
    Setting GPREGRET/GPREGRET2 registers...
    Before soft reset. GPREGRET: 86
    Before soft reset. GPREGRET2: 172
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    I: Starting bootloader
    I: Checking GPREGRET/GPREGRET2 registers...
    I: System booting. GPREGRET: 88
    I: System booting. GPREGRET2: 191
    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: 0x10000
    *** Booting Zephyr OS build v2.4.99-ncs2  ***

Reply
  • I tried the same thing with the latest NCS v1.5.1 as well. Again, GPREGRET/GPREGRET2 registers have some constant value after soft reset no matter what I write into them before the soft reset. In the case of NCS v1.5.1 the value of those registers differ compared to the v1.4.2:


    v1.4.2

    Button has changed. State: 1
    Button 1 pressed
    Setting GPREGRET/GPREGRET2 registers...
    Before soft reset. GPREGRET: 86
    Before soft reset. GPREGRET2: 172
    *** Booting Zephyr OS build v2.4.0-ncs2  ***
    [00:00:00.254,730] <inf> mcuboot: Starting bootloader. Test!
    [00:00:00.254,760] <inf> mcuboot: Checking GPREGRET/GPREGRET2 registers...
    [00:00:00.254,760] <inf> mcuboot: System booting. GPREGRET: 195
    [00:00:00.254,760] <inf> mcuboot: System booting. GPREGRET2: 246
    [00:00:00.255,249] <inf> mcuboot: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    [00:00:00.255,249] <inf> mcuboot: Boot source: none
    [00:00:00.255,371] <inf> mcuboot: Swap type: none
    *** Booting Zephyr OS build v2.4.0-ncs2  ***


    v1.5.1

    Button has changed. State: 1
    Button 1 pressed
    Setting GPREGRET/GPREGRET2 registers...
    Before soft reset. GPREGRET: 86
    Before soft reset. GPREGRET2: 172
    *** Booting Zephyr OS build v2.4.99-ncs2  ***
    I: Starting bootloader
    I: Checking GPREGRET/GPREGRET2 registers...
    I: System booting. GPREGRET: 88
    I: System booting. GPREGRET2: 191
    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: 0x10000
    *** Booting Zephyr OS build v2.4.99-ncs2  ***

Children
  • 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