UF2 Image and Bootloader

I am using Windows 11, NCS v2, VSCode extension, under WSL2.

What I want to do is build a UF2 image of my zephyr application and be able to drag/drop it onto a bootloader which supports UF2.

I have looked and I see there are a number of Kconfig parameters which seem to reference this functionality, but I can't figure out what combination of settings and values to use.

I'm familiar that Adafruit has a bootloader (link) which supports UF2, and works on the nrf52840 feather, which I am using.

I can directly flash the Adafruit UF2 bootloader using my jlink programmer.  I can also build zephyr applications and flash them through the vscode extension with jlink.

Specifically what I want to accomplish is:

  • Take an application which I can normally directly flash to the board (any sample or other application)
  • Make changes to the prj.conf file
  • Ultimately end up with a UF2 file I can drag/drop onto the USB drive presented by a UF2 bootloader, adafruit or otherwise, but initially adafruit

A number of Kconfig parameters explicitly name UF2 and Adafruit UF2 bootloaders.  Is the Adafruit UF2 bootloader expected to work for zephyr applications with the right build configuration?

Below I reference a number of the parameters I have seen and played with unsuccessfully, but I may be missing more.  I could really use a working example if there is one.

CONFIG_BUILD_OUTPUT_UF2 - allows automatic building of a zephyr.uf2 file in the build directory.  I enabled this, should I?  I do see zephyr.uf2 produced as part of the build.

CONFIG_BOOTLOADER_BOSSA - Select the Adafruit UF2 variant of the BOSSA bootloader - not sure what this does.  Should I set this?

CONFIG_BOOTLOADER_BOSSA_ADAFRUIT_UF2 - "Select the Adafruit UF2 variant of the BOSSA bootloader"  Not really sure what this does.  Should I set this?

CONFIG_BUILD_OUTPUT_UF2_FAMILY_ID - "UF2 bootloaders only accept UF2 files with a matching family ID"  For the nRF52840, I believe the right value is "0xADA52840".  I think I should set this.

CONFIG_ROM_START_OFFSET - If the application is built for chain-loading by a bootloader this variable is required to be set to value that leaves sufficient space between the beginning of the image and the start of the first section to store an image header or any other metadata. In the particular case of the MCUboot bootloader this reserves enough space to store the image header, which should also meet vector table alignment requirements on most ARM targets, although some targets may require smaller or larger values.  Would I need to set this?  To what?

CONFIG_FLASH_LOAD_OFFSET -  This option specifies the byte offset from the beginning of flash that the kernel should be loaded into. Changing this value from zero will affect the Zephyr image's link, and will decrease the total amount of flash available for use by application code. If unsure, leave at the default value 0.  Should I set this to some offset from 0 to give space to the bootloader?
CONFIG_FLASH_LOAD_SIZE - If non-zero, this option specifies the size, in bytes, of the flash area that the Zephyr image will be allowed to occupy. If zero, the image will be able to occupy from the FLASH_LOAD_OFFSET to the end of the device. If unsure, leave at the default value 0.  I guess set to 0?
CONFIG_BOOTLOADER_MCUBOOT -  This option signifies that the target uses MCUboot as a bootloader, or in other words that the image is to be chain-loaded by MCUboot. This sets several required build system and Device Tree options in order for the image generated to be bootable using the MCUboot open source bootloader. Currently this includes: * Setting ROM_START_OFFSET to a default value that allows space for the MCUboot image header * Activating SW_VECTOR_RELAY_CLIENT on Cortex-M0 (or Armv8-M baseline) targets with no built-in vector relocation mechanisms By default, this option instructs Zephyr to initialize the core architecture HW registers during boot, when this is supported by the application. This removes the need by MCUboot to reset the core registers' state itself.  Should I set this, or no?  Would I need SW_VECTOR_RELAY_CLIENT set if using a UF2 bootloader?
 
I could use a guide or explanation of how this is all meant to work to put this together.
The UF2 bootloaders are clearly the future (the present, really).  This needs to work for any reasonable user-facing product offering with upgradable firmware.

Thanks.

  • Hi,

    The bootloaders we officially support for nRF Connect SDK is listed at Bootloaders and Device Firmware Updates.

    UF2 is not one of those, so the support I can give for this will be limited. For better support, I suggest that you contact Adafruit support.

    From my general bootloader knowledge:

    An application does not need to know about the bootloader. The bootloader will begin at address 0x0 and then point to where the application starts from.
    For other similar cases, it has been enough to build the application with CONFIG_FLASH_LOAD_OFFSET. The bootloader should be configured to point to an address for an application. Make sure the flash offset is the same as this one.

    Using CONFIG_FLASH_LOAD_OFFSET is only relevant if you build the bootloader separately from the application.
    So I guess that if you use CONFIG_BUILD_OUTPUT_UF2, the offset might be handled automatically.

    CONFIG_BOOTLOADER_MCUBOOT

    It should be "no". If you do not need MCUboot, do not use MCUboot.

    CONFIG_ROM_START_OFFSET

    I have not heard about this before. So I would guess that you do not need it.

    CONFIG_BUILD_OUTPUT_UF2 - allows automatic building of a zephyr.uf2 file in the build directory.  I enabled this, should I?  I do see zephyr.uf2 produced as part of the build.

    CONFIG_BOOTLOADER_BOSSA - Select the Adafruit UF2 variant of the BOSSA bootloader - not sure what this does.  Should I set this?

    CONFIG_BOOTLOADER_BOSSA_ADAFRUIT_UF2 - "Select the Adafruit UF2 variant of the BOSSA bootloader"  Not really sure what this does.  Should I set this?

    CONFIG_BUILD_OUTPUT_UF2_FAMILY_ID - "UF2 bootloaders only accept UF2 files with a matching family ID"  For the nRF52840, I believe the right value is "0xADA52840".  I think I should set this.

    I do not know about UF2-specific configurations. Ask Adafruit about those.

    Regards,
    Sigurd Hellesvik

  • Hi, I don't really understand, Adafruit didn't introduce those prj.conf parameters, those are part of Zephyr.  Why don't you know what they do?

    I can't ask Adafruit, they have told me already they don't provide support.

    But again, these are Zephyr (and therefore Nordic) parameters.  How am I supposed to understand how they work if you don't know?

  • Specifically, it looks like these params were introduced in 2021

    https://github.com/zephyrproject-rtos/zephyr/pull/31066

    It's Zephr parameters, right?

  • Ok I have worked out how to do it, and will update this thread with more detailed answers for the sake of the community.

    If you want to be able to upload zephyr firmware as a UF2 file to a USB drive, using the Adafruit bootloader, do this.

    I'm using WSL2 (linux) and a jlink programmer so those are the instructions I'll provide.  I'm using the feather nrf52840 so I'll use that also, but the bootloader supports lots of boards.

    1. Get the Adafruit bootloader

    $ git clone https://github.com/adafruit/Adafruit_nRF52_Bootloader
    $ cd Adafruit_nRF52_Bootloader

    2. Build and flash the bootloader

    $ make BOARD=feather_nrf52840_express flash
    Flashing: feather_nrf52840_express_bootloader-0.6.4-dirty_nosd.hex
    nrfjprog --program _build/build-feather_nrf52840_express/feather_nrf52840_express_bootloader-0.6.4-dirty_nosd.hex --sectoranduicrerase -f nrf52 --reset
    Parsing image file.
    WARNING: A programming operation has been performed without --verify.
    WARNING: Programming can fail without error.
    Applying system reset.
    Run.

    You should see a new USB flash drive appear in explorer

    3. Change your prj.conf to include UF2 output and a rom start offset of 0x1000, corresponding to the Adafruit bootloader expectations.

    CONFIG_BUILD_OUTPUT_UF2=y
    CONFIG_ROM_START_OFFSET=0x1000

    4. Build pristine.  Notice a UF2 file is output.  The flash output file size is also 4k smaller than before (accounting for the lost space due to the offset).

    [276/276] Linking CXX executable zephyr/zephyr.elf
    Memory region         Used Size  Region Size  %age Used
               FLASH:      493704 B         1 MB     47.08%
                SRAM:      123736 B       256 KB     47.20%
            IDT_LIST:          0 GB         2 KB      0.00%
    Converting to uf2, output size: 987648, start address: 0x0
    Wrote 987648 bytes to zephyr.uf2

    5. Open explorer to the build output files of your application, which is <appname>/build/zephyr, and look for the zephyr.uf2 file.

    6. Drag/drop the zephyr.uf2 file onto the flash drive that appeared before.  It may take a moment of copying, but then the flash drive will vanish and your code will begin to run as normal.

    7. Double-click the reset button to get back into the USB drive for drag/drop upgrade.

    Note - I've found that with the CONFIG_ROM_START_OFFSET parameter set, I can no longer "flash" directly from VSCode.  It kind of just bricks the device.  Commenting out that parameter during rapid code/flash/test iterations is probably the way to go.  I'll put the bootloader on there when I'm done.

Related