The Zephyr RTOS is one of the best around. It makes me feel like working on embedded Linux in the sense of device integration, code styling, device tree (DTS), CMake build systems and more. It has many build-in stacks for USB, networking, BLE, file system, boot loader, DFU, and more. Its API reaches and has partial POSIX support with a custom build system that makes tiny footprints. This post is a tutorial on bringing up for STM32 board with USB and USART interfaces.
Instaltion
Installing Zephyr on Linux should be very easy. Just type the following commands. Refer here for further details.
- Install west, and make sure ~/.local/bin is on your PATH environment variable:
pip3 install --user -U west
echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc
source ~/.bashr
- Get the Zephyr source code:
west init ~/zephyrproject
cd ~/zephyrproject
west update
west zephyr-export
- Select Zephyr version:
git checkout zephyr-v2.5.0
west update
or it can be done directly with version parameter at the west command - refer here:
west init -m https://github.com/zephyrproject-rtos/zephyr --Mr v2.5.0
- Build test application: hello world for nucleo_l4r5zi (stm32 l5r5zi).
cd zephyr
west build -b nucleo_l4r5zi -d build-nucleo_l4r5zi-hello_world samples/hello_world/
The target binaries were generaed under: build-nucleo_l4r5zi-hello_world/zephyr/zephyr
Porting to new target
I used the Nucleo-l4r5zi BSP since its processor is the same family as the new target board. I copied the board nucleo_l4r5zi as newtarget. The name of the new target board is newtarget, and I changed anywhere the label nucleo_l4r5zi to newtarget. The porting include some adaptation of the device tree. It is essential to add the following flash partitions in the DTS file to use the bootloader.
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00010000>;
read-only;
};
/*
* The flash starting at 0x00010000 and ending at
* 0x0001ffff (sectors 16-31) is reserved for use
* by the application.
*/
storage_partition: partition@1e000 {
label = "storage";
reg = <0x0001e000 0x00002000>;
};
slot0_partition: partition@20000 {
label = "image-0";
reg = <0x00020000 0x00060000>;
};
slot1_partition: partition@80000 {
label = "image-1";
reg = <0x00080000 0x00060000>;
};
scratch_partition: partition@e0000 {
label = "image-scratch";
reg = <0x000e0000 0x00020000>;
};
};
};
The partition mcuboot stores the bootloader. The application uses the partition storage, image-0, and image-1 are the first and the second slot where the mcoboot search the application. Both partitions must be the same size, and each one can have a copy of the application since the bootloader is not a position-independent code. The application on both partitions should compile to begin from 0x20000. When the bootloader updates the flash, it swaps between the data of the sections. The boot loader copies the old image to the second partition and the new image to the first partition. To inform zephyr to compile the application to begin from 0x20000, it is essential to add the following definition at the chosen section of the DTS file.
chosen {
zephyr,code-partition = &slot0_partition;
zephyr,console = &usart3;
...
};
The zephyr,code-partition is used by Kconfig.zephyr as value for FLASH_LOAD_OFFSET which used in the linker script as the beginning of the application. If it is not defined, the application will start from 0x0.
Also, as the same as in embedded Linux DTS, I set the USART3 as my default UART for the console. In the case of stm32, I did not have any chance to get working UART and USB. It just works perfectly. To define the specific MCU’s pin mux state, it had to change the following include files to support the new target device.
#include <st/l4/stm32l4r5Xi.dtsi>
#include <st/l4/stm32l4r5q(g-i)ix-pinctrl.dtsi>
DFU + Bootloader
- Bootloader Zypher project use mcuboot as its bootloader.
west build -s $HOME/zephyrproject/bootloader/mcuboot/boot/zephyr -d build-mcuboot -b newtarget
See here for more details about mcuboot with zephyr.
- Build and sign the DFU:
west build -b newtarget -d build-dfu samples/subsys/usb/dfu
$HOME/zephyrproject/bootloader/mcuboot/scripts/imgtool.py sign \
--key $HOME/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem \
--header-size 0x200 \
--align 8 \
--version 1.2 \
--slot-size 0x60000 \
build-dfu/zephyr/zephyr.bin \
build-dfu/zephyr/signed-dfu-app.bin
It has to add the following line to samples/subsys/usb/dfu/prj.conf to reboot after burning the new image.
CONFIG_REBOOT=y
- Burn the two above using the following openocd commands:
reset
halt
flash probe 0
stm32l4x lock 0
stm32l4x mass_erase 0
stm32l4x unlock 0
flash probe 0
flash write_image erase $HOME/zephyrproject/zephyr/build-mcuboot/zephyr/zephyr.bin 0x08000000
flash probe 0
flash write_image erase $HOME/zephyrproject/zephyr/build-dfu/zephyr/signed-dfu-app.bin 0x08020000
After reset, this waht you shoud see in the terminal:
*** Booting Zephyr OS build zephyr-v2.5.0 ***
I: Starting bootloader
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: primary slot
I: Swap type: none
I: Bootloader chainload address offset: 0x20000
The swap type above is at none mode. For this case, it means that the bootloader will use the current image as the main application. To swap the image, it has to do a DFU process and install a new image. In this case, the bootloader will do an image swap right after the first boot after DFU and keep using the new image in the subsequent reboots.
Application Loadable with DFU
To install application using DFU, it has to build the it with the following option in its prj.conf file. At the above hello world example it is under samples/hello_world/prj.conf.
CONFIG_BOOTLOADER_MCUBOOT=y
To build and sign hello world do the following:
west build -b newtarget -d build-hello_worlds samples/hello_world
$HOME/zephyrproject/bootloader/mcuboot/scripts/imgtool.py sign \
--key $HOME/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem \
--header-size 0x200 \
--align 8 \
--version 1.2 \
--slot-size 0x60000 \
build-hello_world/zephyr/zephyr.bin \
build-hello_world/zephyr/signed-app.bin
I have used dfu-util to flash a new firmware.
$ sudo dfu-util --alt 1 --download build-hello_world/zephyr/signed-app.bin
This is its output:
dfu-util 0.9
Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/
dfu-util: Invalid DFU suffix signature
dfu-util: A valid DFU suffix will be required in a future dfu-util release!!!
Opening DFU capable USB device...
ID 2fe3:0002
Run-time device DFU version 0110
Claiming USB DFU Runtime Interface...
Determining device status: state = appIDLE, status = 0
Device really in Runtime Mode, send DFU detach request...
Resetting USB...
Opening DFU USB Device...
Claiming USB DFU Interface...
Setting Alternate Setting #1 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 0110
Device returned transfer size 128
Copying data from PC to DFU device
Download [=========================] 100% 15872 bytes
Download done.
state(2) = dfuIDLE, status(0) = No error condition is present
Done!
After reset this output should appear:
*** Booting Zephyr OS build zephyr-v2.5.0 ***
I: Starting bootloader
I: Primary image: magic=good, swap_type=0x4, copy_done=0x1, image_ok=0x1
I: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Swap type: test
I: Bootloader chainload address offset: 0x20000
I: Jumping to the first image slot
*** Booting Zephyr OS build zephyr-v2.5.0 ***
Hello World! newtarget
The swap type above is at test mode - it means that in the next reset, the bootloader will start again from the previous image. Unless the image new image can then update the contents of flash at runtime to mark itself “OK” as explained here. If the image updates itself as “OK”, it should start at the “none” swap type.
Upgrading an old image with a new one by swapping can be a two-step process. In this process, mcuboot performs a “test” swap of image data in flash and boots the new image or it will be executed during operation. The new image can then update the contents of flash at runtime to mark itself “OK”, and mcuboot will then still choose to run it during the next boot. When this happens, the swap is made “permanent”. If this doesn’t happen, mcuboot will perform a “revert” swap during the next boot by swapping the image(s) back into its original location(s) , and attempting to boot the old image(s).
References
[1] https://docs.zephyrproject.org/latest/getting_started/index.html
[2] https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/mcuboot/design.html#
[3] https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/mcuboot/design.html
[4] https://hub.mender.io/t/updating-device-firmware-using-dfu-in-zephyr-project-on-a-frdm-k64f-board/1618