Building the first app in Zephyr from Scratch
This is the first post on the Zephyr topic.
I’m assuming you have alread installed a working Zephyr Environment. The installation is quite easy if you follow the instructions and avoid messing with many flavours of Zephyr that are around, in particular if you are installing both the Nordic Connect SDK and Zephyr barebone side by side. I’m not saying that it won’t work, but I have tested it and found some issue, so for starting I suggest to just use the barebone Zephyr installation as expalined in the Getting started Guide and maybe install the Connect SDK later.
I installed the Zephyr environment in a directory under ~/Documents/devel/Zephyr
. To avoid messing the project around my filesystem, I’ve decided to build a “Freestanding” application, as explained in the Application Development Guide under the “Application types” section.
A freestanding application is an application that is located outside the Zephyr repository and the Zephyr workspace.
The only difference so far is that you must define the ZEPHYR_BASE
variable that must point to the zephyrproject/zephy
directory. I decided to have an environment variable set, but apparently you can set it in the CMakeLists.txt
file as well.
Creaing the project tree
The project tree must contain a few text files and directory, but it seems it is not mandatory and the behavior can be customized as needed. The Application Development Guide suggest to clone the sample application, but I love to have as less file as possible, because understanding what doesn’t work is easy when you just have a few files you wrote instead of dozen of files someone else wrote for you.
So: you need the following directories build:
src : the source files go here
The following files are needed:
src/main.c : the main source file
CMakeLists.txt : the CMake script
prj.conf : Project configuration
app.overlay : Device Tree overlay file
I took main.c
from the blinky sample1. I copied the app.overlay
device tree overlay from the readme.md
of the sample, adjusted to use the blue led of the Adafruit nrf52840 Feather Board.
prj.conf
just contains the following line:
CONFIG_GPIO=y
This is the app.overlay
file:
/ {
aliases {
led0 = &led_blue;
};
leds {
compatible = "gpio-leds";
led_blue: led_0 {
gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
};
};
};
And the CMakeLists.txt
is just copied and pasted from the Develiopment guide:
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr)
project(my_zephyr_app)
target_sources(app PRIVATE src/main.c)
Compiling
You can use either west or cmake+ninja. If ZEPHYR_BASE points to the correct directory, like in my env.sh file:
export ZEPHYR_BASE=`pwd`/zephyrproject/zephyr
then, running west should work without issues:
happycactus@Mint53: $ west build -b adafruit_feather_nrf52840
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
...
-- Board: adafruit_feather_nrf52840
-- ZEPHYR_TOOLCHAIN_VARIANT not set, trying to locate Zephyr SDK
-- Found host-tools: zephyr 0.15.1 (/opt/zephyr-sdk-0.15.1)
-- Found toolchain: zephyr 0.15.1 (/opt/zephyr-sdk-0.15.1)
-- Found Dtc: /opt/zephyr-sdk-0.15.1/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6")
-- Found BOARD.dts: /home/happycactus/Documents/devel/Zephyr/zephyrproject/zephyr/boards/arm/adafruit_feather_nrf52840/adafruit_feather_nrf52840.dts
-- Found devicetree overlay: /home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/app.overlay
-- Generated zephyr.dts: /home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/build/zephyr/zephyr.dts
-- Generated devicetree_generated.h: /home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/build/zephyr/include/generated/devicetree_generated.h
-- Including generated dts.cmake file: /home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/build/zephyr/dts.cmake
Parsing /home/happycactus/Documents/devel/Zephyr/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/happycactus/Documents/devel/Zephyr/zephyrproject/zephyr/boards/arm/adafruit_feather_nrf52840/adafruit_feather_nrf52840_defconfig'
Merged configuration '/home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/prj.conf'
Configuration saved to '/home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/build/zephyr/.config'
Kconfig header saved to '/home/happycactus/Documents/devel/Zephyr/MyFirstZephyrAppBYHand/build/zephyr/include/generated/autoconf.h'
-- The C compiler identification is GNU 12.1.0
-- The CXX compiler identification is GNU 12.1.0
...
[153/159] Linking C executable zephyr/zephyr_pre1.elf
[159/159] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 18908 B 1 MB 1.80%
RAM: 4104 B 256 KB 1.57%
IDT_LIST: 0 GB 2 KB 0.00%
Ok the app compiled just fine.
Flashing the board
I decided to use the Adafruit bootloader to flash the app. I could use also the Segger board embedded in the nrf52840-DK
board but in that case the bootloader would have been removed. It can be easily restored though.
The result of the compilation is an ELF binary that can’t be flashed as is, neither with SEGGER or with the Bootloader.
In both cases, the first step is to convert it to an ihex format, using objcopy
:
happycactus@Mint53: $ arm-none-eabi-objcopy -O ihex \
build/zephyr/zephyr.elf zephyr.hex
With segger and you can run
happycactus@Mint53: $ sudo nrfjprog -f nrf52 --chiperase \
--program zephyr.hex --reset
Uf2 Bootloader
To flash the firmware with the Adafruit bootloader you have two additional requirements:
- The application must run from the correct location in ram and,
- The binary artifact must be converted to the uf2 format.
By default, the Zephyr kernel will be located at address 0x0
, unless a bootloader is specified.
But adafruit uf2 assumes that the application will be located ad address 0x26000
so we must instruct the linker to locate there the kernel. You can either specify it from menuconfig by navigating into Build and Link Features
> Linker Options
> Kernel Load offset
or by setting CONFIG_FLASH_LOAD_OFFSET
accordingly, as explained in the Kconfig initial configuration section of the development guide.
For example, add this line to the prj.conf
file:
CONFIG_FLASH_LOAD_OFFSET=0x26000
Then you must convert it to uf2 format. The full instructions can be found in the Readme.md of the Adafruit_nRF52 bootloader repository. Anyway, once you have installed uf2conv.py2, just run:
happycactus@Mint53: $ uf2conv.py zephyr.hex -c -f 0xada52840 \
-o zephyr.uf2
Converted to uf2, output size: 37888, start address: 0x0
Wrote 37888 bytes to zephyr.uf2
Then copy the uf2 file in the emulated bootloader storage and the app should run at the next reset.