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 “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.
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
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:
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
.
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.
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:
(I plan to rewrite and distribute these as west extensions in future).
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.
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
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
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.
zephyr.signed.bin
file accessible to your phone (eg. by uploading it to iCloud, google drive, dropbox, or some other webserver).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.