Porting Boards
At some point you might need to port a new BOARD to RIOT, either because
that specific development board is not yet supported or because you have a
custom BOARD for your project.
If you want to port a BOARD to RIOT you have two choices: doing it
inside of RIOTBASE or outside. In either case the file structure
is basically the same and moving from one to another is easy.
This guide details the generic structure you need to add a new BOARD
to RIOT, the different files as well as their functionality.
Porting Flowchart
Section titled “Porting Flowchart”General Structure
Section titled “General Structure”Like applications or
modules,
boards consist of a directory containing source files and
Makefiles. Usually a BOARD directory has the following structure,
although not all of the subdirectories or Makefiles have to be present for
a board implementation to work.
board-foo├── dist│ └── scripts├── board.c├── doc.md├── include│ ├── periph_conf.h│ ├── board.h│ └── gpio_params.h├── Makefile├── Makefile.dep├── Makefile.features└── Makefile.includeSource Files
Section titled “Source Files”Header files in board-foo/include define physical mappings or
configurations. e.g:
periph_conf.h: defines configurations and mappings for peripherals as well as clock configurations.board.h: holds board specific definitions or mappings, for example LEDs, buttons. It might as well override default drivers parameters (e.g.: assigning specific pin connections to a LCD screen, radio, etc.).gpio_params.h: if the board supports SAUL then its saul_gpio_params_t is defined here. (Analogously, anadc_params.hcan contain a saul_adc_params_t andpwm_params.ha saul_pwm_rgb_params_t and a saul_pwm_dimmer_params_t).- other: other specific headers needed by one
BOARD
Board initialization functions are defined in board.c.
This file can define a board_init() function that is called at startup.
It is run before the scheduler is started, so it must not block (e.g. by
performing I2C operations).
void board_init(void){ /* initialize GPIO or others... */ ...}Makefiles
Section titled “Makefiles”Makefile
Section titled “Makefile”A board’s Makefile just needs to include Makefile.base in the RIOT
repository and define the MODULE as board (see
modules for more details)
MODULE = board
include $(RIOTBASE)/Makefile.baseMakefile.dep
Section titled “Makefile.dep”Dependencies on other MODULES or FEATURES can be defined here. This might
specify MODULES or dependencies that need to be pulled under specific
configurations. e.g.: if your board has a sx1276 lora chip:
ifneq (,$(filter netdev_default,$(USEMODULE))) USEMODULE += sx1276endifDefault Configurations
Section titled “Default Configurations”There are two pseudomodules that are used to indicate that certain drivers of devices present in the platform should be enabled. Each board (or CPU) has knowledge as to which drivers should be enabled in each case.
The previous code snippet shows how a board which has a Semtech SX1272 and SX1276 radio driver device, pulls in its driver when the default network interfaces are required.
When the pseudomodule saul_default is enabled, the board should pull in all
the drivers of the devices it has which provide a
[S]ensor [A]ctuator [U]ber [L]ayer
interface. This is usually done as following:
ifneq (,$(filter saul_default,$(USEMODULE))) USEMODULE += saul_gpio USEMODULE += apds9960 USEMODULE += bmp280_i2c USEMODULE += lis3mdl USEMODULE += sht3xendifMakefile.features
Section titled “Makefile.features”This file defines all the features provided by the BOARD. These features
might also need to be supported by the CPU. Here, define the CPU and
CPU_MODEL (see build system basics
for more details on these variables).
e.g.:
CPU = fooCPU_MODEL = foobar
# Put defined MCU peripherals here (in alphabetical order)FEATURES_PROVIDED += periph_i2cFEATURES_PROVIDED += periph_spiFEATURES_PROVIDED += periph_uartMakefile.include
Section titled “Makefile.include”This file contains BSP or toolchain configurations for the BOARD. It
should at least define the configuration needed for flashing (i.e. specify a
default programmer) as well as the serial configuration (if one is available).
The default serial port configuration is provided by
makefiles/tools/serial.inc.mk and defines the following values for the serial
port (depending on the host OS):
PORT_LINUX ?= /dev/ttyACM0PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.usbmodem*)))So if the board is also using this, there’s no need to redefine these variables in the board configuration.
For example a board that is using a custom serial port (via an USB to serial
adapter) and that is flashed using OpenOCD by default would have the following
content in its Makefile.include:
# Define the default port depending on the host OSPORT_LINUX ?= /dev/ttyUSB0PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.usbserial*)))
# this board uses OpenOCDPROGRAMMER ?= openocddoc.md
Section titled “doc.md”Although not explicitly needed, if upstreamed and as a general good
practice, this file holds all BOARD documentation. This can include
datasheet reference, documentation on how to flash, etc.
The documentation must be under the proper doxygen group, you can compile the
documentation by calling make doc and then open the generated html file on
any browser.
@defgroup boards_foo FooBoard@ingroup boards@brief Support for the foo board@author FooName BarName <foo.bar@baz.com>
#### User Interface
...
#### Using UART
...
#### Flashing the device
...Previously documentation was contained in doc.txt files with C-style comment
blocks. This style has been deprecated in favor of using doc.md files in
Markdown format, which eliminates formatting and interpretation issues.
Old style files will continually be replaced by the new format.
Up to version 0.9.2 the riotgen tool
will generate doc.txt files instead of doc.md files. You can upgrade it to
the latest version with
pip install --upgrade riotgenTimer Configuration
Section titled “Timer Configuration”Timer Width
Section titled “Timer Width”The ztimer driver assumes a timer register bit-width of 32-bits by default.
If your microcontroller has a smaller timer register (e.g. 16-bits), you have
to explicitly specify the maximum value the timer register can hold with the
TIMER_0_MAX_VALUE define.
This is the same value that is put in the max field of the timer_config
structure.
Typical values are 0x0000FFFFUL for 16-bit wide timers, 0x00FFFFFFUL for
24-bit wide timers and 0xFFFFFFFFUL for 32-bit wide timers.
static const timer_conf_t timer_config[] = { { [...] .max = 0x0000ffff, [...] }};
#define TIMER_0_MAX_VALUE 0x0000FFFFULOverhead Calibration
Section titled “Overhead Calibration”When using the high level timer ztimer there is an overhead in calling the
ztimer_sleep
and ztimer_set
functions. This offset can be compensated for.
It can be measured by running tests/sys/ztimer_overhead on your board, i.e:
$ BOARD=my-new-board make -C tests/sys/ztimer_overhead flash termThis should give the following output:
main(): This is RIOT!ZTIMER_USEC auto_adjust params: ZTIMER_USEC->adjust_set = xx ZTIMER_USEC->adjust_sleep = xxZTIMER_USEC auto_adjust params clearedzitmer_overhead_set...min=6 max=7 avg_diff=6zitmer_overhead_sleep...min=21 max=21 avg_diff=21ZTIMER_USEC adjust params for my-new-board: CONFIG_ZTIMER_USEC_ADJUST_SET 6 CONFIG_ZTIMER_USEC_ADJUST_SLEEP 21The last two lines can be added as defines to the new board board.h:
/** * @name ztimer configuration values * @{ */#define CONFIG_ZTIMER_USEC_ADJUST_SET 6#define CONFIG_ZTIMER_USEC_ADJUST_SLEEP 21/** @} */Alternatively, the pseudomodule ztimer_auto_adjust can be used in an application to enable automatic timer offset compensation at board startup. This however incurs overhead both in the text segment and at bootup time.
Helper Tools
Section titled “Helper Tools”To help you start porting a board, the RIOT build system provides the
generate-board make target. It is a wrapper around the
riotgen command line tool that is helpful
when starting to port a board: all required files are generated with
copyright headers, doxygen groups, etc, so you can concentrate on the port.
The board source files are created in the boards/<board name> directory.
From the RIOT base directory, run:
make generate-boardThen answer a few questions about the driver:
- Board name: Enter a name for your board. It will be used as the name
of the board directory under
boards. - Board displayed name: Enter the name of the board, as displayed in the Doxygen documentation.
- CPU name: Enter the name of the CPU embedded on the board.
- CPU model name: Enter the precise model name of the CPU.
- Features provided: CPU features provided (and configured) for this board.
Other global information (author name, email, organization) should be retrieved automatically from your git configuration.
Common Board Directories
Section titled “Common Board Directories”Using Common Code
Section titled “Using Common Code”To avoid code duplication, common code across boards has been grouped in
boards/common. e.g. BOARDs based on the same cpu (boards/common/nrf52) or
BOARDs having the same layout boards/common/nucleo64.
In the case of source files this means some functions like board_init can be
already defined in the common code. Unless having specific configurations or
initialization you might not need a board.c or board.h. Another common use
case is common peripheral configurations, for example in the cfg_timer_tim5.h:
/** * @name Timer configuration * @{ */static const timer_conf_t timer_config[] = { { .dev = TIM5, .max = 0xffffffff, .rcc_mask = RCC_APB1ENR_TIM5EN, .bus = APB1, .irqn = TIM5_IRQn }};
#define TIMER_0_ISR isr_tim5
#define TIMER_NUMOF ARRAY_SIZE(timer_config)/** @} */New Style Common Code
Section titled “New Style Common Code”The common board definitions of RIOT are currently being reworked to make the
usage of common code easier and less error prone. For example, if you want
to use the common code for the Adafruit nRF52 Bootloader that is used
by many of the nRF52 based boards from Adafruit, you simply have to add the
following line to the Makefile.dep of your board. Everything else
will be automatically included by the build system.
USEMODULE += boards_common_adafruit-nrf52-bootloaderNot all common code is migrated to the new style yet, so if you are unsure
whether it is or not, you can check if the boards/Makefile already
includes a reference to the common code you want to use. If you are still
unsure, you can still use the Old Style Common Code or ask the
community.
Old Style Common Code
Section titled “Old Style Common Code”If you want to use common makefiles, include them at the end of the specific
Makefile, e.g. for a Makefile.features:
CPU = fooCPU_MODEL = foobar# Put defined MCU peripherals here (in alphabetical order)FEATURES_PROVIDED += periph_i2cFEATURES_PROVIDED += periph_spiFEATURES_PROVIDED += periph_uartinclude $(RIOTBOARD)/common/foo_common/Makefile.featuresIf the common code includes source files, it might be necessary
to explicitly include the directory in your Makefile so the Make system
finds all the necessary files:
MODULE = board
DIRS += $(RIOTBOARD)/common/myCommonFolder
include $(RIOTBASE)/Makefile.baseIf possible, you should use the New Style Common Code though.
Moving Common Code to a Dedicated Folder
Section titled “Moving Common Code to a Dedicated Folder”If you port a board that is very similar to an already existing board, it might
make sense to move the shared code to a common directory located in
boards/common to use it as described in the previous section.
The directory structure of a common folder is very similar to the board
folder structure and not all files and folders have to be present except for
the main Makefile.
RIOT└── boards └── common └── adafruit-nrf52-bootloader ├── board.c ├── doc.md ├── include │ ├── periph_conf.h │ ├── board.h │ └── gpio_params.h ├── Makefile ├── Makefile.dep ├── Makefile.features └── Makefile.includeThe main Makefile defines the module name for the common board module and
should follow the general naming scheme of boards_common_awesome-common-stuff.
MODULE = boards_common_adafruit-nrf52-bootloader
include $(RIOTBASE)/Makefile.baseThe Makefile.dep, Makefile.features and Makefile.include are optional
and work the same way as their normal board pendants.
To inform the build system about the common folders and Makefiles, the
boards/Makefile, boards/Makefile.dep, boards/Makefile.features and
boards/Makefile.include files have to be modified.
The boards/Makefile contains the directory entries for the common files.
The entries should check if the common module is used and conditionally add
the directory to the DIRS variable.
Please note that the entries should be sorted alphabetically.
# SORT THIS ALPHABETICALLY BY COMMON BOARD NAME!...ifneq (,$(filter boards_common_adafruit-nrf52-bootloader,$(USEMODULE))) DIRS += $(RIOTBOARD)/common/adafruit-nrf52-bootloaderendif...The boards/Makefile.dep, boards/Makefile.features and
boards/Makefile.include just include their common counterparts. As an
example, an entry of the boards/Makefile.dep is shown:
# SORT THIS ALPHABETICALLY BY COMMON BOARD NAME!...ifneq (,$(filter boards_common_adafruit-nrf52-bootloader,$(USEMODULE))) include $(RIOTBOARD)/common/adafruit-nrf52-bootloader/Makefile.dependif...You only have to add entries to the board/Makefiles if your common code
actually has the regarding Makefile-type.
Boards Outside of RIOTBASE
Section titled “Boards Outside of RIOTBASE”All BOARDs in RIOT reside in RIOTBOARD (RIOTBOARD being a make variable
set to $(RIOTBOARD)/boards).
If one wants to use a BOARD outside of RIOTBOARD, the way to go is setting
the EXTERNAL_BOARD_DIRS variable to the path to the directory containing your
external boards, e.g.: EXTERNAL_BOARD_DIRS=/home/external-boards/ (this would
commonly be done in your application Makefile or your environment). You can
specify multiple directories separated by spaces.
/home├── RIOT│ └── ...└── external-boards └── board-foo ├── dist │ └── scripts ├── board.c ├── doc.md ├── include │ ├── periph_conf.h │ ├── board.h │ └── gpio_params.h ├── Makefile ├── Makefile.dep ├── Makefile.features └── Makefile.includeIf the external BOARD is very similar to a BOARD already present in
RIOTBOARD, the external BOARD (board-foo) can inherit from that
parent BOARD (e.g: foo-parent).
In this case some special considerations must be taken with the makefiles:
MakefileMODULEcannot beboard:foo-parentwill already defineMODULE = board, so use any other name, lets sayMODULE = board-foo.- Include the location of the parent
BOARDto inherit from (if there is one):
DIRS += $(RIOTBOARD)/foo-parent-
Makefile.include- duplicate the include done by
$(RIOTBASE)/Makefile.includeto also include the parent board header. e.g: if inheriting fromfoo-parent“INCLUDES += $(addprefix -I,$(wildcard $(RIOTBOARD)/foo-parent/include))`
- duplicate the include done by
-
Makefile.dep:boardis added by default toUSEMODULEbut sinceboard-foois used for thisBOARD, it must be explicitly included by addingUSEMODULE += board-foo. -
Then simply include in each
Makefile.*the corresponding parentBOARDMakefile.*, just as it is done for commonBOARDcode (as explained in Using Common Code). e.g:include $(RIOTBOARD)/foo-parent/Makefile.*include*
An example can be found in
tests/build_system/external_board_native.
Board Names and Aliases
Section titled “Board Names and Aliases”New boards should be named according to
RDM0003.
Historically, some board names have not followed this structure.
For backwards compatibility, RIOT supports board aliases that can be used
in place of the actual board name in the environment or Make variable BOARD.
A list of all existing board aliases can be found in
makefiles/board_alias.inc.mk.
BOARD=native
is a special alias in that it
resolves to either native32
or native64
depending on the host architecture.
Some scripts and tools available to ease BOARD porting and testing:
-
Run
dist/tools/insufficient_memory/update_insufficient_memory_board.sh <board>if your board has little memory. This updates theMakefile.cilists to exclude theBOARDfrom automated compile-tests of applications that do not fit on theBOARDsCPU. Please keep in mind that this will take quite a while depending on your system! -
Run
dist/tools/compile_and_test_for_board/compile_and_test_for_board.py . <board> --with-test-onlyto run all automated tests on the new board.
Further Reference
Section titled “Further Reference”- In her blog, Martine Lenders documented her approach of porting the Adafruit Feather nRF52840 Express in February 2020.
- Over at HackMD, Akshai M documented his approach of porting the Silicon Labs SLSTK3400A starter kit in July 2020.