Direct Firmware Update (DFU)

Many boards running Zephyr are able to support Direct Firmware Update (abbreviated DFU) without requring an expensive programming interface, either over USB, USB-to-serial, or Bluetooth.

To achieve this, there are two optional components of Zephyr that are involved. These are the mcuboot bootloader, and the mcumgr development tool.

The mcuboot bootloader

The mcuboot “bootstrap-loader” program (bootloader) is a small Zephyr program whose sole job is to load your main Zephyr application. When you have installed mcuboot on your target board, you can then install two or more copies of your application on the target board, and control which one is loaded.

The general procedure is that you divide the flash memory into serveral “slots”, and tell mcuboot which slot to load at startup time. While your application is running, one can upload a new copy of the application into a spare slot, and then instruct mcuboot which slot to boot at the next restart. One can arrange to boot the updated slot once only as a test (in case the updated code does not work), or permanently thereafter.

This means that we can safely upgrade a running system while minimising the risk that some error (either in the upload process itself, or in the newly uploaded firmware) renders our target board unusable.

Installing mcuboot on your target board

MCUboot expects application firmware files (also called “firmware images”) to be digitally signed by the application distributor, in order to prevent accidental upload of invalid files, and also to prevent deliberate upload of unauthorized and/or malicious firmware by third parties.

In order to construct your firmware images for use with mcuboot, you must first (once only) generate a signing key that you will use for digitally signing all your future firmware images, replacing the sample key that is distributed with mcuboot.

cd zephyrproject/bootloader/mcuboot
./scripts/imgtool.py keygen -k root_rsa-2048.pem -t rsa-2048

Now compile mcuboot for your target board, which will incorporate the key that you generated into the mcuboot image. You only need to flash the bootloader to your board once, it will remain on the board thereafter, alongside your application image(s)

cd zephyrproject/bootloader/mcuboot/boot/zephyr
west build -p -b YOURBOARDNAME
west flash

The mcumgr tool

In order to communicate with and configure Zephyr’s DFU components, a portable protocol named mcumgr is used (this protocol was originated by a similar operating system named Apache MyNewt, and is now also used by Zephyr and a number of other systems. In recognition of its origin you will sometimes see mcumgr referred to as newtmgr).

The program that implements the management protocol is also called mcumgr.

Here is the general procedure for direct-firmware update:

  1. Compile your application as normal
  2. Digitally sign your firmware image, to ensure mcuboot will accept it
  3. Upload your signed binary to the target board using mcumgr
  4. Instruct mcuboot to load the new slot (using mcumgr, or zephyr’s command shell)

Installing mcumgr on your helper system

Install the Go programming language using instructions at golang.org.

Install the mcumgr program using go get github.com/apache/mynewt-mcumgr-cli/mcumgr

You could also use Kevin to install golang: salt-call --local state.apply dev.golang.

Configuring your application to work with mcuboot

Add the following lines to your application’s prj.conf, which will add code to your application firmware to allow it to cooperate with mcuboot and mcumgr:

CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUMGR=y
CONFIG_MCUMGR_SMP_BT=y
CONFIG_MCUMGR_SMP_SHELL=y
CONFIG_MCUMGR_CMD_IMG_MGMT=y

You will need to digitally sign your compiled applications with the key you generated above.

Each time you build your application, you must digitally sign the generated firmware image:

west sign -d ${BUILD_DIR} -t imgtool -- --key ~/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem

This will generate zephyr.signed.bin and zephyr.signed.hex from your zephyr.bin and zephyr.hex object-files.

Shell aliases for streamlined remote deploy

In order to streamline the process of building and signing a firmware image, and installing either locally or via a helper system, I have written some deliberately-tersely-named shell aliases, which I place in a file named .env in my project directory, and activate by typing source .env:

alias build='west build'
alias sign='if [ build/zephyr/zephyr.signed.hex -nt build/zephyr/zephyr.hex ] ; then true; else west sign -t imgtool -- --key ~/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem ; fi'
alias flash='west flash --runner pyocd --hex-file build/zephyr/zephyr.signed.hex'
function upload {
  if [ -n "$CONN" ]
    then sudo mcumgr --conn $CONN image upload ${BUILD_DIR}/zephyr/zephyr.signed.hex
  else
    sudo mcumgr --conntype serial --connstring "$PORT,baud=$BAUD" image upload build/zephyr/zephyr.signed.hex
  fi
}
function helpercopy {
  rsync -Pv build/zephyr/zephyr.signed.hex helper:zephyr.hex
  rsync -Pv build/zephyr/zephyr.signed.bin helper:zephyr.bin 
}
function helperflash { ssh -t helper sudo openocd -f interface/jlink.cfg -c \"transport select swd\" -f target/nrf52.cfg -c init -c \"program zephyr.hex verify reset exit\" ; }
  
alias go='build && sign'
alias gosh='go && flash'
alias gosu='go && upload'
alias gosc='go && helpercopy`
alias gosp='gosc && helperflash`

The above gives you four new shell shortcuts:

  • go will build and sign your applicatoin
  • gosh will build, sign, and flash your app using a programmer cable
  • gosu will build, sign, and upload your app using mcumgr
  • gosp will build and sign your app, copy it to a helper system, and flash it from there

(I plan to rewrite and distribute these as west extensions in future).

Defining a connection for use with mcumgr

The mcumgr protocol supports uploading over a serial cable, or over Bluetooth, plus some other methods (such as UDP).

One can invoke mcumgr by specifying which connection method and details to use, or reduce typing by defining an alias for a particular connection.

The long form is:

mcumgr --conntype serial --connstring /dev/SOMEPORT,115200 SOMECOMMAND
mcumgr --conntype blue --connstring peer_name=NAME SOMECOMMAND

To save time, you can save a predefined connection for each particular upload method you use often.

For serial ports:

Assuming our target board is connected to /dev/ttyUSB0 at 115200 bps, do this:

mcumgr conn add USB0 type=serial connstring=/dev/ttyUSB0,115200

For Bluetooth:

Assuming our target board advertises the bluetooth name ALICE, do this:

mcumgr conn add ALICE type=ble connstring='peer_name=ALICE'

Now we can use the shortcut forms mcumgr -c USB0 or mcumgr -c ALICE to reduce typing when perofrming the operations neccessary to upload and configure our build.

Putting it all together - upgrading with mcumgr

Lets step through the process of safely uploading a new firmware image via Direct Firmware Update. (Replace MYCONN below with whatever you named the connection you have defined).

First, list the image(s) currently on the device:

mcumgr -c MYCONN image list

Next, upload our signed image:

mcumgr -c MYCONN image upload zephyr.signed.bin

Now, observe the new state of the image slots:

mcumgr -c MYCONN image list

Note the “hash value” of whichever slot is new, or is NOT marked active. Here we will presume the hash for the updated slot is 786444d163329fcc71da1514f69c436a6f85eb63d40fcb5f90f98b075a1cc29d.

To test your new image, make the desired image the boot target once only:

mcumgr -c MYCONN image test 786444d163329fcc71da1514f69c436a6f85eb63d40fcb5f90f98b075a1cc29d

(When you use the ‘test’ command, the target will revert back to the previous image at the next reboot. This allows an “escape clause” should the new firmware fail to function).

After testing (or if confident that testing is not neccessary), use the confirm operation to make the desired image the boot target henceforth:

mcumgr -c MYCONN image confirm 786444d163329fcc71da1514f69c436a6f85eb63d40fcb5f90f98b075a1cc29d

Performing the upload-activate process in one step

Once you get into a code-upload-test rhythm (and presuming you have a programming cable with which to recover should one of your uploaded-and-confirmed images fail in such a way that prevents further DFU, one can do the above process with a single shell script.

I save this script as mcumgr-upload on my workstation, or on my helper system, and then I set an shell-environment variable named CONN (eg. export CONN=ALICE)

#!/bin/bash
set -e
mcumgr -c $CONN image list
mcumgr -c $CONN image upload $1 &&
mcumgr -c $CONN image list | tee image.lst &&
HASH=`grep hash: image.lst | tail -1 | awk '{print $2}'`
[ -n "$HASH" ]
mcumgr -c $CONN image confirm $HASH
mcumgr -c $CONN reset

With this script installed on my workstation or helper system, the process of uploading, confirming and rebooting now becomes just a single command:

mcumgr-upload zephyr.bin

Using Nordic nRF Connect app for Bluetooth DFU

Not everybody has a programming cable, but almost everybody has a smartphone. If your target device supports Bluetooth, DFU over Bluetooth is convenient.

Once you have a signed bin file, you can use Nordic’s free Mobile app nRF Connect (available for Android and iPhone) to install the image.

  1. Make the signed zephyr.signed.bin file accessible to your phone (eg. by uploading it to iCloud, google drive, dropbox, or some other webserver).
  2. Start the nRFConnect app on your phone, and use the “scan” tab to find your device and connect to it
  3. Once connected, you should see a (DFU) button appear at top right of your phone’s screen
  4. Tap the DFU button in the app and navigate to your image file
  5. Begin the upload. (It could take 10 minutes or longer for a large image)

Using nrfConnect is a good option for field upgrades, especially if performed by end-users. During beta testing, I typically use google drive to share a new firmware file with end-users, which they can then deploy using their phones.