Board Porting Guide

To add Zephyr support for a new board, you at least need a board directory with various files in it. Files in the board directory inherit support for at least one SoC and all of its features. Therefore, Zephyr must support your SoC as well.

Boards, SoCs, etc.

Zephyr’s hardware support hierarchy has these layers, from most to least specific:

  • Board: a particular CPU instance and its peripherals in a concrete hardware specification

  • SoC: the exact system on a chip the board’s CPU is part of

  • SoC series: a smaller group of tightly related SoCs

  • SoC family: a wider group of SoCs with similar characteristics

  • CPU core: a particular CPU in an architecture

  • Architecture: an instruction set architecture

You can visualize the hierarchy like this:

Configuration Hierarchy

Fig. 43 Configuration Hierarchy

Here are some examples. Notice how the SoC series and family levels are not always used.

Board

SoC

SoC series

SoC family

CPU core

Architecture

nrf52dk_nrf52832

nRF52832

nRF52

Nordic nRF5

Arm Cortex-M4

Arm

frdm_k64f

MK64F12

Kinetis K6x

NXP Kinetis

Arm Cortex-M4

Arm

stm32h747i_disco

STM32H747XI

STM32H7

STMicro STM32

Arm Cortex-M7

Arm

rv32m1_vega_ri5cy

RV32M1

(Not used)

(Not used)

RI5CY

RISC-V

Make sure your SoC is supported

Start by making sure your SoC is supported by Zephyr. If it is, it’s time to Create your board directory. If you don’t know, try:

  • checking Supported Boards for names that look relevant, and reading individual board documentation to find out for sure.

  • asking your SoC vendor

If you need to add SoC, CPU core, or even architecture support, this is the wrong page, but here is some general advice.

Architecture

See Architecture Porting Guide.

CPU Core

CPU core support files go in core subdirectories under arch, e.g. arch/x86/core.

See Install a Toolchain for information about toolchains (compiler, linker, etc.) supported by Zephyr. If you need to support a new toolchain, Build and Configuration Systems is a good place to start learning about the build system. Please reach out to the community if you are looking for advice or want to collaborate on toolchain support.

SoC

Zephyr SoC support files are in architecture-specific subdirectories of soc. They are generally grouped by SoC family.

When adding a new SoC family or series for a vendor that already has SoC support within Zephyr, please try to extract common functionality into shared files to avoid duplication. If there is no support for your vendor yet, you can add it in a new directory zephyr/soc/<YOUR-ARCH>/<YOUR-SOC>; please use self-explanatory directory names.

Create your board directory

Once you’ve found an existing board that uses your SoC, you can usually start by copy/pasting its board directory and changing its contents for your hardware.

You need to give your board a unique name. Run west boards for a list of names that are already taken, and pick something new. Let’s say your board is called plank (please don’t actually use that name).

Start by creating the board directory zephyr/boards/<ARCH>/plank, where <ARCH> is your SoC’s architecture subdirectory. (You don’t have to put your board directory in the zephyr repository, but it’s the easiest way to get started. See Custom Board, Devicetree and SOC Definitions for documentation on moving your board directory to a separate repository once it’s working.)

Note

The board directory name does not need to match the name of the board. Multiple boards can even defined be in one directory. For example, for boards with multi-core SoC, a logical board might be created for each core following the naming scheme <board>_<soc-core>, with definitions for all of these different boards defined inside the same directory. This and similar schemes are common for upstream vendor boards.

Your board directory should look like this:

boards/<ARCH>/plank
├── board.cmake
├── CMakeLists.txt
├── doc
│   ├── plank.png
│   └── index.rst
├── Kconfig.board
├── Kconfig.defconfig
├── plank_defconfig
├── plank.dts
└── plank.yaml

Replace plank with your board’s name, of course.

The mandatory files are:

  1. plank.dts: a hardware description in devicetree format. This declares your SoC, connectors, and any other hardware components such as LEDs, buttons, sensors, or communication peripherals (USB, BLE controller, etc).

  2. Kconfig.board, Kconfig.defconfig, plank_defconfig: software configuration in Configuration System (Kconfig) formats. This provides default settings for software features and peripheral drivers.

The optional files are:

  • board.cmake: used for Flash and debug support

  • CMakeLists.txt: if you need to add additional source files to your build.

  • doc/index.rst, doc/plank.png: documentation for and a picture of your board. You only need this if you’re Contributing your board to Zephyr.

  • plank.yaml: a YAML file with miscellaneous metadata used by the Test Runner (Twister).

Write your devicetree

The devicetree file boards/<ARCH>/plank/plank.dts describes your board hardware in the Devicetree Source (DTS) format (as usual, change plank to your board’s name). If you’re new to devicetree, see Introduction to devicetree.

In general, plank.dts should look like this:

/dts-v1/;
#include <your_soc_vendor/your_soc.dtsi>

/ {
     model = "A human readable name";
     compatible = "yourcompany,plank";

     chosen {
             zephyr,console = &your_uart_console;
             zephyr,sram = &your_memory_node;
             /* other chosen settings  for your hardware */
     };

     /*
      * Your board-specific hardware: buttons, LEDs, sensors, etc.
      */

     leds {
             compatible = "gpio-leds";
             led0: led_0 {
                     gpios = < /* GPIO your LED is hooked up to */ >;
                     label = "LED 0";
             };
             /* ... other LEDs ... */
     };

     buttons {
             compatible = "gpio-keys";
             /* ... your button definitions ... */
     };

     /* These aliases are provided for compatibility with samples */
     aliases {
             led0 = &led0; /* now you support the blinky sample! */
             /* other aliases go here */
     };
};

&some_peripheral_you_want_to_enable { /* like a GPIO or SPI controller */
     status = "okay";
};

&another_peripheral_you_want {
     status = "okay";
};

If you’re in a hurry, simple hardware can usually be supported by copy/paste followed by trial and error. If you want to understand details, you will need to read the rest of the devicetree documentation and the devicetree specification.

Example: FRDM-K64F and Hexiwear K64

This section contains concrete examples related to writing your board’s devicetree.

The FRDM-K64F and Hexiwear K64 board devicetrees are defined in frdm_k64fs.dts and hexiwear_k64.dts respectively. Both boards have NXP SoCs from the same Kinetis SoC family, the K6X.

Common devicetree definitions for K6X are stored in nxp_k6x.dtsi, which is included by both board .dts files. nxp_k6x.dtsi in turn includes armv7-m.dtsi, which has common definitions for Arm v7-M cores.

Since nxp_k6x.dtsi is meant to be generic across K6X-based boards, it leaves many devices disabled by default using status properties. For example, there is a CAN controller defined as follows (with unimportant parts skipped):

can0: can@40024000 {
     ...
     status = "disabled";
     ...
};

It is up to the board .dts or application overlay files to enable these devices as desired, by setting status = "okay". The board .dts files are also responsible for any board-specific configuration of the device, such as adding nodes for on-board sensors, LEDs, buttons, etc.

For example, FRDM-K64 (but not Hexiwear K64) .dts enables the CAN controller and sets the bus speed:

&can0 {
     status = "okay";
     bus-speed = <125000>;
};

The &can0 { ... }; syntax adds/overrides properties on the node with label can0, i.e. the can@4002400 node defined in the .dtsi file.

Other examples of board-specific customization is pointing properties in aliases and chosen to the right nodes (see Aliases and chosen nodes), and making GPIO/pinmux assignments.

Write Kconfig files

Zephyr uses the Kconfig language to configure software features. Your board needs to provide some Kconfig settings before you can compile a Zephyr application for it.

Setting Kconfig configuration values is documented in detail in Setting Kconfig configuration values.

There are three mandatory Kconfig files in the board directory for a board named plank:

boards/<ARCH>/plank
├── Kconfig.board
├── Kconfig.defconfig
└── plank_defconfig
Kconfig.board

Included by boards/Kconfig to include your board in the list of options.

This should at least contain a definition for a BOARD_PLANK option, which looks something like this:

config BOARD_PLANK
   bool "Plank board"
   depends on SOC_SERIES_YOUR_SOC_SERIES_HERE
   select SOC_PART_NUMBER_ABCDEFGH
Kconfig.defconfig

Board-specific default values for Kconfig options.

The entire file should be inside an if BOARD_PLANK / endif pair of lines, like this:

if BOARD_PLANK

# Always set CONFIG_BOARD here. This isn't meant to be customized,
# but is set as a "default" due to Kconfig language restrictions.
config BOARD
   default "plank"

# Other options you want enabled by default go next. Examples:

config FOO
   default y

if NETWORKING
config SOC_ETHERNET_DRIVER
   default y
endif # NETWORKING

endif # BOARD_PLANK
plank_defconfig

A Kconfig fragment that is merged as-is into the final build directory .config whenever an application is compiled for your board.

You should at least select your board’s SOC and do any mandatory settings for your system clock, console, etc. The results are architecture-specific, but typically look something like this:

CONFIG_SOC_${VENDOR_XYZ3000}=y                      /* select your SoC */
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=120000000   /* set up your clock, etc */
CONFIG_SERIAL=y
plank_x_y_z.conf

A Kconfig fragment that is merged as-is into the final build directory .config whenever an application is compiled for your board revision x.y.z.

Build, test, and fix

Now it’s time to build and test the application(s) you want to run on your board until you’re satisfied.

For example:

west build -b plank samples/hello_world
west flash

For west flash to work, see Flash and debug support below. You can also just flash build/zephyr/zephyr.elf, zephyr.hex, or zephyr.bin with any other tools you prefer.

General recommendations

For consistency and to make it easier for users to build generic applications that are not board specific for your board, please follow these guidelines while porting.

  • Unless explicitly recommended otherwise by this section, leave peripherals and their drivers disabled by default.

  • Configure and enable a system clock, along with a tick source.

  • Provide pin and driver configuration that matches the board’s valuable components such as sensors, buttons or LEDs, and communication interfaces such as USB, Ethernet connector, or Bluetooth/Wi-Fi chip.

  • If your board uses a well-known connector standard (like Arduino, Mikrobus, Grove, or 96Boards connectors), add connector nodes to your DTS and configure pin muxes accordingly.

  • Configure components that enable the use of these pins, such as configuring an SPI instance to use the usual Arduino SPI pins.

  • If available, configure and enable a serial output for the console using the zephyr,console chosen node in the devicetree.

  • If your board supports networking, configure a default interface.

  • Enable all GPIO ports connected to peripherals or expansion connectors.

  • If available, enable pinmux and interrupt controller drivers.

  • It is recommended to enable the MPU by default, if there is support for it in hardware. For boards with limited memory resources it is acceptable to disable it. When the MPU is enabled, it is recommended to also enable hardware stack protection (CONFIG_HW_STACK_PROTECTION=y) and, thus, allow the kernel to detect stack overflows when the system is running in privileged mode.

Flash and debug support

Zephyr supports Building, Flashing and Debugging via west extension commands.

To add west flash and west debug support for your board, you need to create a board.cmake file in your board directory. This file’s job is to configure a “runner” for your board. (There’s nothing special you need to do to get west build support for your board.)

“Runners” are Zephyr-specific Python classes that wrap flash and debug host tools and integrate with west and the zephyr build system to support west flash and related commands. Each runner supports flashing, debugging, or both. You need to configure the arguments to these Python scripts in your board.cmake to support those commands like this example board.cmake:

board_runner_args(jlink "--device=nrf52" "--speed=4000")
board_runner_args(pyocd "--target=nrf52" "--frequency=4000000")

include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)

This example configures the nrfjprog, jlink, and pyocd runners.

Warning

Runners usually have names which match the tools they wrap, so the jlink runner wraps Segger’s J-Link tools, and so on. But the runner command line options like --speed etc. are specific to the Python scripts.

For more details:

  • Run west flash --context to see a list of available runners which support flashing, and west flash --context -r <RUNNER> to view the specific options available for an individual runner.

  • Run west debug --context and west debug --context <RUNNER> to get the same output for runners which support debugging.

  • Run west flash --help and west debug --help for top-level options for flashing and debugging.

  • See Flash and debug runners for Python APIs.

  • Look for board.cmake files for other boards similar to your own for more examples.

To see what a west flash or west debug command is doing exactly, run it in verbose mode:

west --verbose flash
west --verbose debug

Verbose mode prints any host tool commands the runner uses.

The order of the include() calls in your board.cmake matters. The first include sets the default runner if it’s not already set. For example, including nrfjprog.board.cmake first means that nrfjprog is the default flash runner for this board. Since nrfjprog does not support debugging, jlink is the default debug runner.

Multiple board revisions

See Building for a board revision for basics on this feature from the user perspective.

To create a new board revision for the plank board, create these additional files in the board folder:

boards/<ARCH>/plank
├── plank_<revision>.conf     # optional
├── plank_<revision>.overlay  # optional
└── revision.cmake

When the user builds for board plank@<revision>:

  • The optional Kconfig settings specified in the file plank_<revision>.conf will be merged into the board’s default Kconfig configuration.

  • The optional devicetree overlay plank_<revision>.overlay will be added to the common plank.dts devicetree file

  • The revision.cmake file controls how the Zephyr build system matches the <board>@<revision> string specified by the user when building an application for the board.

Currently, <revision> can be either a numeric MAJOR.MINOR.PATCH style revision like 1.5.0, an integer number like 1, or single letter like A, B, etc. Zephyr provides a CMake board extension function, board_check_revision(), to make it easy to match either style from revision.cmake.

Valid board revisions may be specified as arguments to the board_check_revision() function, like:

board_check_revision(FORMAT MAJOR.MINOR.PATCH
                     VALID_REVISIONS 0.1.0 0.3.0 ...
)

Note

VALID_REVISIONS can be omitted if all valid revisions have specific Kconfig fragments, such as <board>_0_1_0.conf, <board>_0_3_0.conf. This allows you to just place Kconfig revision fragments in the board folder and not have to keep the corresponding VALID_REVISIONS in sync.

The following sections describe how to support these styles of revision numbers.

MAJOR.MINOR.PATCH revisions

Let’s say you want to add support for revisions 0.5.0, 1.0.0, and 1.5.0 of the plank board with both Kconfig fragments and devicetree overlays. Create revision.cmake with board_check_revision(FORMAT MAJOR.MINOR.PATCH), and create the following additional files in the board directory:

boards/<ARCH>/plank
├── plank_0_5_0.conf
├── plank_0_5_0.overlay
├── plank_1_0_0.conf
├── plank_1_0_0.overlay
├── plank_1_5_0.conf
├── plank_1_5_0.overlay
└── revision.cmake

Notice how the board files have changed periods (“.”) in the revision number to underscores (“_”).

Fuzzy revision matching

To support “fuzzy” MAJOR.MINOR.PATCH revision matching for the plank board, use the following code in revision.cmake:

board_check_revision(FORMAT MAJOR.MINOR.PATCH)

If the user selects a revision between those available, the closest revision number that is not larger than the user’s choice is used. For example, if the user builds for plank@0.7.0, the build system will target revision 0.5.0.

The build system will print this at CMake configuration time:

-- Board: plank, Revision: 0.7.0 (Active: 0.5.0)

This allows you to only create revision configuration files for board revision numbers that introduce incompatible changes.

Any revision less than the minimum defined will be treated as an error.

You may use 0.0.0 as a minimum revision to build for by creating the file plank_0_0_0.conf in the board directory. This will be used for any revision lower than 0.5.0, for example if the user builds for plank@0.1.0.

Exact revision matching

Alternatively, the EXACT keyword can be given to board_check_revision() in revision.cmake to allow exact matches only, like this:

board_check_revision(FORMAT MAJOR.MINOR.PATCH EXACT)

With this revision.cmake, building for plank@0.7.0 in the above example will result in the following error message:

Board revision `0.7.0` not found.  Please specify a valid board revision.

Letter revision matching

Let’s say instead that you need to support revisions A, B, and C of the plank board. Create the following additional files in the board directory:

boards/<ARCH>/plank
├── plank_A.conf
├── plank_A.overlay
├── plank_B.conf
├── plank_B.overlay
├── plank_C.conf
├── plank_C.overlay
└── revision.cmake

And add the following to revision.cmake:

board_check_revision(FORMAT LETTER)

Number revision matching

Let’s say instead that you need to support revisions 1, 2, and 3 of the plank board. Create the following additional files in the board directory:

boards/<ARCH>/plank
├── plank_1.conf
├── plank_1.overlay
├── plank_2.conf
├── plank_2.overlay
├── plank_3.conf
├── plank_3.overlay
└── revision.cmake

And add the following to revision.cmake:

board_check_revision(FORMAT NUMBER)

board_check_revision() details

board_check_revision(FORMAT <LETTER | NUMBER | MAJOR.MINOR.PATCH>
                     [EXACT]
                     [DEFAULT_REVISION <revision>]
                     [HIGHEST_REVISION <revision>]
                     [VALID_REVISIONS <revision> [<revision> ...]]
)

This function supports the following arguments:

  • FORMAT LETTER: matches single letter revisions from A to Z only

  • FORMAT NUMBER: matches integer revisions

  • FORMAT MAJOR.MINOR.PATCH: matches exactly three digits. The command line allows for loose typing, that is -DBOARD=<board>@1 and -DBOARD=<board>@1.0 will be handled as -DBOARD=<board>@1.0.0. Kconfig fragment and devicetree overlay files must use full numbering to avoid ambiguity, so only <board>_1_0_0.conf and <board>_1_0_0.overlay are allowed.

  • EXACT: if given, the revision is required to be an exact match. Otherwise, the closest matching revision not greater than the user’s choice will be selected.

  • DEFAULT_REVISION <revision>: if given, <revision> is the default revision to use when user has not selected a revision number. If not given, the build system prints an error when the user does not specify a board revision.

  • HIGHEST_REVISION: if given, specifies the highest valid revision for a board. This can be used to ensure that a newer board cannot be used with an older Zephyr. For example, if the current board directory supports revisions 0.x.0-0.99.99 and 1.0.0-1.99.99, and it is expected that the implementation will not work with board revision 2.0.0, then giving HIGHEST_REVISION 1.99.99 causes an error if the user builds using <board>@2.0.0.

  • VALID_REVISIONS: if given, specifies a list of revisions that are valid for this board. If this argument is not given, then each Kconfig fragment of the form <board>_<revision>.conf in the board folder will be used as a valid revision for the board.

Custom revision.cmake files

Some boards may not use board revisions supported by board_check_revision(). To support revisions of any type, the file revision.cmake can implement custom revision matching without calling board_check_revision().

To signal to the build system that it should use a different revision than the one specified by the user, revision.cmake can set the variable ACTIVE_BOARD_REVISION to the revision to use instead. The corresponding Kconfig files and devicetree overlays must be named <board>_<ACTIVE_BOARD_REVISION>.conf and <board>_<ACTIVE_BOARD_REVISION>.overlay.

For example, if the user builds for plank@zero, revision.cmake can set ACTIVE_BOARD_REVISION to one to use the files plank_one.conf and plank_one.overlay.

Contributing your board

If you want to contribute your board to Zephyr, first – thanks!

There are some extra things you’ll need to do:

  1. Make sure you’ve followed all the General recommendations. They are requirements for boards included with Zephyr.

  2. Add documentation for your board using the template file doc/templates/board.tmpl. See Documentation Generation for information on how to build your documentation before submitting your pull request.

  3. Prepare a pull request adding your board which follows the Contribution Guidelines.