mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-15 01:23:49 +01:00
602 lines
20 KiB
Plaintext
602 lines
20 KiB
Plaintext
---
|
|
title: Porting Boards
|
|
description: Guide on how to port new boards to RIOT-OS
|
|
---
|
|
|
|
import FileTree from '@components/FileTree.astro';
|
|
|
|
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.
|
|
|
|
:::note
|
|
We assume here that your `CPU` and `CPU_MODEL` is already supported
|
|
in `RIOT` so no peripheral or cpu implementation is needed.
|
|
:::
|
|
|
|
## Porting Flowchart
|
|

|
|
|
|
## General Structure
|
|
|
|
Like [applications](/advanced_tutorials/creating_applications/) or
|
|
[modules](/advanced_tutorials/creating_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.
|
|
|
|
<FileTree>
|
|
|
|
- board-foo
|
|
- dist
|
|
- scripts/
|
|
- board.c
|
|
- doc.md
|
|
- include
|
|
- periph_conf.h
|
|
- board.h
|
|
- gpio_params.h
|
|
- Makefile
|
|
- Makefile.dep
|
|
- Makefile.features
|
|
- Makefile.include
|
|
|
|
</FileTree>
|
|
|
|
### 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](https://doc.riot-os.org/group__drivers__saul.html) then its
|
|
[saul_gpio_params_t](https://doc.riot-os.org/structsaul__gpio__params__t.html)
|
|
is defined here. (Analogously, an `adc_params.h` can
|
|
contain a [saul_adc_params_t](https://doc.riot-os.org/structsaul__adc__params__t.html)
|
|
and `pwm_params.h` a
|
|
[saul_pwm_rgb_params_t](https://doc.riot-os.org/structsaul__pwm__rgb__params__t.html)
|
|
and a [saul_pwm_dimmer_params_t](https://doc.riot-os.org/structsaul__pwm__dimmer__params__t.html)).
|
|
- other: other specific headers needed by one `BOARD`
|
|
|
|
:::note
|
|
Header files do not need to be defined in `include/`, but if defined
|
|
somewhere else then they must be added to the include path in
|
|
`Makefile.include`: `INCLUDES += -I<some>/<directory>/<path>`
|
|
:::
|
|
|
|
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).
|
|
|
|
```c
|
|
void board_init(void)
|
|
{
|
|
/* initialize GPIO or others... */
|
|
...
|
|
}
|
|
```
|
|
|
|
### Makefiles
|
|
|
|
#### Makefile
|
|
|
|
A board's Makefile just needs to include `Makefile.base` in the RIOT
|
|
repository and define the `MODULE` as `board` (see
|
|
[modules](/advanced_tutorials/creating_modules) for more details)
|
|
|
|
```makefile
|
|
MODULE = board
|
|
|
|
include $(RIOTBASE)/Makefile.base
|
|
```
|
|
|
|
#### 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:
|
|
|
|
```makefile
|
|
ifneq (,$(filter netdev_default,$(USEMODULE)))
|
|
USEMODULE += sx1276
|
|
endif
|
|
```
|
|
|
|
:::note
|
|
`Makefile.dep` is processed only once so you have to take care of adding
|
|
the dependency block for your board *before* its dependencies pull in their own
|
|
dependencies.
|
|
:::
|
|
|
|
##### 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](https://doc.riot-os.org/group__drivers__sx127x.html)
|
|
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](https://doc.riot-os.org/group__drivers__saul.html)
|
|
interface. This is usually done as following:
|
|
|
|
```makefile
|
|
ifneq (,$(filter saul_default,$(USEMODULE)))
|
|
USEMODULE += saul_gpio
|
|
USEMODULE += apds9960
|
|
USEMODULE += bmp280_i2c
|
|
USEMODULE += lis3mdl
|
|
USEMODULE += sht3x
|
|
endif
|
|
```
|
|
|
|
#### 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](/build-system/build_system_basics/)
|
|
for more details on these variables).
|
|
|
|
e.g.:
|
|
|
|
```makefile
|
|
CPU = foo
|
|
CPU_MODEL = foobar
|
|
|
|
# Put defined MCU peripherals here (in alphabetical order)
|
|
FEATURES_PROVIDED += periph_i2c
|
|
FEATURES_PROVIDED += periph_spi
|
|
FEATURES_PROVIDED += periph_uart
|
|
```
|
|
|
|
#### 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):
|
|
|
|
```makefile
|
|
PORT_LINUX ?= /dev/ttyACM0
|
|
PORT_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`:
|
|
|
|
```makefile
|
|
# Define the default port depending on the host OS
|
|
PORT_LINUX ?= /dev/ttyUSB0
|
|
PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.usbserial*)))
|
|
|
|
# this board uses OpenOCD
|
|
PROGRAMMER ?= openocd
|
|
```
|
|
|
|
### 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.
|
|
|
|
```markdown
|
|
@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](https://pypi.org/project/riotgen/) tool
|
|
will generate `doc.txt` files instead of `doc.md` files. You can upgrade it to
|
|
the latest version with
|
|
|
|
```bash
|
|
pip install --upgrade riotgen
|
|
```
|
|
|
|
## Timer Configuration
|
|
|
|
### 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.
|
|
|
|
```c
|
|
static const timer_conf_t timer_config[] = {
|
|
{
|
|
[...]
|
|
.max = 0x0000ffff,
|
|
[...]
|
|
}
|
|
};
|
|
|
|
#define TIMER_0_MAX_VALUE 0x0000FFFFUL
|
|
```
|
|
|
|
:::caution
|
|
`ztimer` does not automatically check if the `max` field and the
|
|
`TIMER_0_MAX_VALUE` definition match!
|
|
|
|
For example: If you observe "erratic" blinking patterns in
|
|
`examples/basic/blinky`, make sure to check if the sizes match.
|
|
:::
|
|
|
|
### Overhead Calibration
|
|
|
|
When using the high level timer `ztimer` there is an overhead in calling the
|
|
[ztimer_sleep](https://doc.riot-os.org/group__sys__ztimer.html#gade98636e198f2d571c8acd861d29d360)
|
|
and [ztimer_set](https://doc.riot-os.org/group__sys__ztimer.html#ga8934a79a89e35d58673418a1e4a2e69c)
|
|
functions. This offset can be compensated for.
|
|
It can be measured by running `tests/sys/ztimer_overhead` on your board, i.e:
|
|
|
|
```bash
|
|
$ BOARD=my-new-board make -C tests/sys/ztimer_overhead flash term
|
|
```
|
|
|
|
This should give the following output:
|
|
```
|
|
main(): This is RIOT!
|
|
ZTIMER_USEC auto_adjust params:
|
|
ZTIMER_USEC->adjust_set = xx
|
|
ZTIMER_USEC->adjust_sleep = xx
|
|
ZTIMER_USEC auto_adjust params cleared
|
|
zitmer_overhead_set...
|
|
min=6 max=7 avg_diff=6
|
|
zitmer_overhead_sleep...
|
|
min=21 max=21 avg_diff=21
|
|
ZTIMER_USEC adjust params for my-new-board:
|
|
CONFIG_ZTIMER_USEC_ADJUST_SET 6
|
|
CONFIG_ZTIMER_USEC_ADJUST_SLEEP 21
|
|
```
|
|
|
|
The last two lines can be added as defines to the new board `board.h`:
|
|
|
|
```c
|
|
/**
|
|
* @name ztimer configuration values
|
|
* @{
|
|
*/
|
|
#define CONFIG_ZTIMER_USEC_ADJUST_SET 6
|
|
#define CONFIG_ZTIMER_USEC_ADJUST_SLEEP 21
|
|
/** @} */
|
|
```
|
|
|
|
Alternatively, the pseudomodule
|
|
[ztimer_auto_adjust](https://doc.riot-os.org/group__pseudomodule__ztimer__auto__adjust.html)
|
|
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
|
|
|
|
To help you start porting a board, the RIOT build system provides the
|
|
`generate-board` make target. It is a wrapper around the
|
|
[riotgen](https://pypi.org/project/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:
|
|
|
|
```bash
|
|
make generate-board
|
|
```
|
|
|
|
Then 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
|
|
|
|
### Using Common Code
|
|
|
|
To avoid code duplication, common code across boards has been grouped in
|
|
`boards/common`. e.g. `BOARD`s based on the same cpu (`boards/common/nrf52`) or
|
|
`BOARD`s 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`:
|
|
|
|
```c
|
|
/**
|
|
* @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
|
|
|
|
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.
|
|
|
|
```makefile
|
|
USEMODULE += boards_common_adafruit-nrf52-bootloader
|
|
```
|
|
|
|
Not 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
|
|
|
|
If you want to use common makefiles, include them at the end of the specific
|
|
`Makefile`, e.g. for a `Makefile.features`:
|
|
|
|
```makefile
|
|
CPU = foo
|
|
CPU_MODEL = foobar
|
|
# Put defined MCU peripherals here (in alphabetical order)
|
|
FEATURES_PROVIDED += periph_i2c
|
|
FEATURES_PROVIDED += periph_spi
|
|
FEATURES_PROVIDED += periph_uart
|
|
include $(RIOTBOARD)/common/foo_common/Makefile.features
|
|
```
|
|
|
|
If 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:
|
|
|
|
```makefile
|
|
MODULE = board
|
|
|
|
DIRS += $(RIOTBOARD)/common/myCommonFolder
|
|
|
|
include $(RIOTBASE)/Makefile.base
|
|
```
|
|
|
|
If possible, you should use the New Style Common Code though.
|
|
|
|
### 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`.
|
|
|
|
<FileTree>
|
|
|
|
- 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.include
|
|
|
|
</FileTree>
|
|
|
|
The main `Makefile` defines the module name for the common board module and
|
|
should follow the general naming scheme of `boards_common_awesome-common-stuff`.
|
|
|
|
```makefile
|
|
MODULE = boards_common_adafruit-nrf52-bootloader
|
|
|
|
include $(RIOTBASE)/Makefile.base
|
|
```
|
|
|
|
The `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.
|
|
```makefile
|
|
# SORT THIS ALPHABETICALLY BY COMMON BOARD NAME!
|
|
...
|
|
ifneq (,$(filter boards_common_adafruit-nrf52-bootloader,$(USEMODULE)))
|
|
DIRS += $(RIOTBOARD)/common/adafruit-nrf52-bootloader
|
|
endif
|
|
...
|
|
```
|
|
|
|
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:
|
|
```makefile
|
|
# SORT THIS ALPHABETICALLY BY COMMON BOARD NAME!
|
|
...
|
|
ifneq (,$(filter boards_common_adafruit-nrf52-bootloader,$(USEMODULE)))
|
|
include $(RIOTBOARD)/common/adafruit-nrf52-bootloader/Makefile.dep
|
|
endif
|
|
...
|
|
```
|
|
|
|
You only have to add entries to the `board/Makefile`s if your common code
|
|
actually has the regarding Makefile-type.
|
|
|
|
## Boards Outside of RIOTBASE
|
|
|
|
All `BOARD`s 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.
|
|
|
|
<FileTree>
|
|
|
|
- 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.include
|
|
|
|
</FileTree>
|
|
|
|
If 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:
|
|
|
|
- `Makefile`
|
|
- `MODULE` cannot be `board`: `foo-parent` will already define
|
|
`MODULE = board`, so use any other name, lets say `MODULE = board-foo`.
|
|
- Include the location of the parent `BOARD` to inherit from (if there is
|
|
one):
|
|
|
|
```makefile
|
|
DIRS += $(RIOTBOARD)/foo-parent
|
|
```
|
|
|
|
- `Makefile.include`
|
|
- duplicate the include done by `$(RIOTBASE)/Makefile.include` to also
|
|
include the parent board header. e.g: if inheriting from `foo-parent`
|
|
``INCLUDES += $(addprefix -I,$(wildcard $(RIOTBOARD)/foo-parent/include))`
|
|
|
|
- `Makefile.dep`: `board` is added by default to `USEMODULE` but since
|
|
`board-foo` is used for this `BOARD`, it must be explicitly included by adding
|
|
`USEMODULE += board-foo`.
|
|
|
|
- Then simply include in each `Makefile.*` the corresponding parent `BOARD`
|
|
`Makefile.*`, just as it is done for common `BOARD` code (as explained in
|
|
[Using Common Code](#using-common-code)). e.g:
|
|
`include $(RIOTBOARD)/foo-parent/Makefile.*include*`
|
|
|
|
An example can be found in
|
|
[`tests/build_system/external_board_native`](https://github.com/RIOT-OS/RIOT/tree/master/tests/build_system/external_board_native).
|
|
|
|
## Board Names and Aliases
|
|
|
|
New boards should be named according to
|
|
[RDM0003](https://github.com/RIOT-OS/RIOT/blob/master/doc/memos/rdm0003.md).
|
|
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`](https://github.com/RIOT-OS/RIOT/blob/master/makefiles/board_alias.inc.mk).
|
|
[`BOARD=native`](https://doc.riot-os.org/group__boards__common__native.html)
|
|
is a special alias in that it
|
|
resolves to either [`native32`](https://doc.riot-os.org/group__boards__native32.html)
|
|
or [`native64`](https://doc.riot-os.org/group__boards__native64.html)
|
|
depending on the host architecture.
|
|
|
|
## Tools
|
|
|
|
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 the `Makefile.ci` lists to
|
|
exclude the `BOARD` from automated compile-tests of applications that do
|
|
not fit on the `BOARD`s `CPU`.
|
|
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-only`
|
|
to run all automated tests on the new board.
|
|
|
|
## Further Reference
|
|
|
|
- [In her blog](https://blog.martine-lenders.eu/riot-board-en.html), Martine Lenders documented her approach of
|
|
porting the [Adafruit Feather nRF52840 Express](https://doc.riot-os.org/group__boards__adafruit-feather-nrf52840-express.html)
|
|
in February 2020.
|
|
- [Over at HackMD](https://hackmd.io/njFHwQ33SNS3sQKAkLkNtQ), Akshai M documented his approach of
|
|
porting the [Silicon Labs SLSTK3400A starter kit](https://doc.riot-os.org/group__boards__slstk3400a.html) in July 2020.
|