Zephyr – adding custom board

Adding custom board to Zephyr OS build system

Overview

When developing application on embedded systems, there often comes a time, when you want, or need, to move from development kit, to your custom made board. Although Zephyr supports many development kits, and you can focus on application development, configuring Zephyr build system for your board might be a hard step for you.

Preparing configuration

Finding place

Probably the best place for your custom board configuration file is your application directory, so you can move it with whole application source code. Placed there, you could find it with ease, when something needs to be modified.

As a first step, you need to create new folder in your application root directory, and name it /boards.

In this folder, create new folder and name it by architecture of your target device. You can find possible architectures under Zephyr root directory, in folder /boards. For this example, we will take ARM architecture. At this moment, we are in directory called like this:

{APPLICATION_ROOT}/boards/arm

This is the place, where we create folder containing our custom board. Create new folder, and call it by your board, for example we will name it myBoard1.

Our custom board directory path should look similar to this:

{APPLICATION_ROOT}/boards/arm/myBoard1

Adding files

Configuration should contain few files with specific names, which are:

  • myBoard1.dts – in this file, board hardware should be defined, such as LEDs, sensors, buttons, connectors, SoC, and other peripherals.
  • Kconfig.board, Kconfig.defconfig, myBoard1_defconfig – these files are containing the software configuration, default settings for software features and peripheral drivers.

The best way to see structure of configuration files, is to look at one from predefined development kits. For easiest configuration, you can copy and modify files from development kit with the most similar SoC.

What can be found, and what should be placed in these files? I will explain it on example of Nucleo-L432KC board, and files will be modified for custom board.

DTS file

myBoard1.dts file should always contain information about device tree version, and include your board SoC header files.

// This line informs about device tree version
/dts-v1/; 
// The next two files are SoC support headers.
#include <st/l4/stm32l432Xc.dtsi>
#include <st/l4/stm32l432k(b-c)ux-pinctrl.dtsi>

You can find SoC support headers in directory:

{ZEPHYR_ROOT}/dts/your_architecture/vendor/series/

To include SoC file to .dts file, only vendor and series from path should be given. After SoC files, it is time to define board vendor and name, compatible boards, and peripherals.

/ {
    model = "Self made board";
    compatible = "st,stm32l432kc-nucleo";

    chosen {
        zephyr,console = &usart2;
        zephyr,shell-uart = &usart2;
        zephyr,sram = &sram0;
        zephyr,flash = &flash0;
        zephyr,can-primary = &can1;
    };

    leds {
        compatible = "gpio-leds";
        green_led: led_0 {
            gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;
            label = "User LD3";
        };
    };

    buttons {
        compatible = "gpio-keys";
        button1: button_1 {
            gpios = <&gpiob 13 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
            label = "Button 1";
        };
    };

    aliases {
        led0 = &green_led;
        button1 = &button1;
    };
};

After opening bracket, one field needs to be defined. This is model which names your board. You can list compatible boards in the field compatible.

The chosen section is used to map peripherals to Zephyr nodes. List of possible fields can be found here.

The next sections are used to define your board inputs (named buttons) and outputs (named leds). In this place you can define GPIO signals. In aliases section, alias for every GPIO defined in sections above can be given, so each GPIO have its own descriptive name.

Peripherals to be used on your board are defined as fields in .dts file, for example:

&iwdg {
    status = "okay";
};

&usart2 {
    current-speed = <115200>;
    status = "okay";
};

&spi1 {
    status = "okay";
    cs-gpios = <&gpiob 0 0>;
    sx1276@0 {
        compatible = "semtech,sx1276";
        reg = <0>;
        label = "sx1276";
        reset-gpios = <&gpiob 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
        dio-gpios = <&gpioa 12 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
            <&gpiob 2 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
            <&gpiob 7 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
            <&gpiob 6 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
            <&gpiob 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
            <&gpioa 8 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
        spi-max-frequency = <400000>;
    };
};

The selected peripheral is enabled by property status = "okay";. Additional attributes of peripheral can be configured here, like serial baud rate, or external devices, accessed by given peripheral can be added.

Kconfig files

Kconfig.board is next important file. This file adds your board to Zephyr board list. It should contain at least the three lines listed below.

config MY_BOARD_1
    bool "My Board"
    depends on SOC_STM32L432XX

What is defined in this file? In the first line, name of the board is configured. By this name, Zephry build system will know that application is being built for your board. The next line defines a human readable name, and the last name informs about SoC dependency. Your SoC should be defined here.

The Kconfig.defconfig file contains default board specific settings. Whole content of this file has to be placed inside

if BOARD_NAME

...

endif

where BOARD_NAME is name of the board given in Kconfig.board. Example content of such file:

if MY_BOARD_1

config SPI_STM32_INTERRUPT
    default y
    depends on SPI

endif

The last file is myBoard1_defconfig, which contains MCU configuration, like clocks configuration, SoC definition, and OS features configuration. Example file is given below.

CONFIG_SOC_SERIES_STM32L4X=y
CONFIG_SOC_STM32L432XX=y
# 80MHz system clock
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=80000000

# Enable MPU
CONFIG_ARM_MPU=y

# Enable HW stack protection
CONFIG_HW_STACK_PROTECTION=y

# enable uart driver
CONFIG_SERIAL=y

# enable pinmux
CONFIG_PINMUX=y

# enable GPIO
CONFIG_GPIO=y

# clock configuration
CONFIG_CLOCK_CONTROL=y
# SYSCLK selection
CONFIG_CLOCK_STM32_SYSCLK_SRC_PLL=y
# PLL configuration
CONFIG_CLOCK_STM32_PLL_SRC_HSI=y
# produce 80MHz clock at PLL output
CONFIG_CLOCK_STM32_PLL_M_DIVISOR=1
CONFIG_CLOCK_STM32_PLL_N_MULTIPLIER=20
CONFIG_CLOCK_STM32_PLL_P_DIVISOR=7
CONFIG_CLOCK_STM32_PLL_Q_DIVISOR=2
CONFIG_CLOCK_STM32_PLL_R_DIVISOR=4
CONFIG_CLOCK_STM32_AHB_PRESCALER=1
CONFIG_CLOCK_STM32_APB1_PRESCALER=1
CONFIG_CLOCK_STM32_APB2_PRESCALER=1

# console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

Configuration of these 3 files requires a little knowledge about Kconfig, also used by Linux kernel. You can find more information about content of this files for Zephyr in zephyr docs.

Building for custom board

Telling Zephyr build system, that your application is being build for your board can be done two ways. But first of all, one line needs to be added to CMakeLists.txt in your project.

set(BOARD_ROOT .)

It will inform build system, to search for board configurations in your application root directory.

  • First way is to specify it by appropriate flag in build command, so the command looks like this:
west build -b myBoard1
  • The second way is to tell CMake what board is used. you can do it by adding the following line to CMakeLists.txt:
set(BOARD myBoard1)

Now, when you run west build for your app, it will build for your custom board by default.

Adding custom board version

Zephyr build system allows to add one board with multiple versions, so the application for different versions can be managed with ease. Two files are needed to create board revision:

  • <board_name>_<board_revision>.overlay
  • <board_name>_<board_revision>.conf

For our example:

  • myBoard1_1_0_0.conf,
  • myBoard1_1_0_0.overlay

Note that there are underscores instead of dots in version.

The second file is optional.

The *.conf file contains additional Kconfig configuration, to be merged into default Kconfig file.

The optional file *.overlay will be added to the default .dts file. Parameters in this file, will overwrite these defined in default .dts file.

For example, if we have defined our board UART as before, like this:

&usart2 {
    current-speed = <115200>;
    status = "okay";
};

and myBoard1_1_0_0.overlay file will contain the following lines:

&usart2 {
    current-speed = <9600>;
};

when building for board version 1.0.0, UART2 baud rate will be set to 9600.

Summary

Adding your custom board to Zephyr build system might be hard at first try, because it requires some knowledge about build system, but after a few tries things are getting easier. I hope that with help of this post, more people will be able to do this.

In LPN Plant we connect consulting, technical expertise and financial effectiveness to design and implement low power wireless solutions for industry. If you are looking for product developers or just need support in a piece of your system feel free to contact us. If you enjoying this type of content feel free to share content on social media.

Leave a Reply

Your email address will not be published. Required fields are marked *