Porting boards {#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. @note We assume here that your `CPU` and `CPU_MODEL` is already supported in `RIOT` so no peripheral or cpu implementation is needed. # General structure {#general-structure} Like @ref creating-an-application "applications" or @ref creating-modules "modules", boards consist on a directory containing source files and makefiles. Usually a `BOARD` directory has the following structure ``` board-foo/ |----dist/ |----scripts |----board.c |----doc.txt |----include/ |----periph_conf.h |----board.h |----gpio_params.h |----Makefile |----Makefile.dep |----Makefile.features |----Makefile.include ``` ## Source files {#board-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.). Some boards might also define optimized `XTIMER_%` values (e.g. @ref XTIMER_BACKOFF). - `gpio_params.h`: if the board supports @ref drivers_saul "SAUL" then its @ref saul_gpio_params_t is defined here. (Analogously, a `adc_params.h` can contain a @ref saul_adc_params_t, and `pwm_params.h` a @ref saul_pwm_rgb_params_t and a @ref saul_pwm_dimmer_params_t). - 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//` Board initialization functions are defined in `board.c`. This file must at least define a `board_init()` function that is called at startup. This function initializes the `CPU` by calling`cpu_init()` among others. 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 the CPU core */ cpu_init(); /* initialize GPIO or others... */ ... } ``` ## Makefiles ### Makefile {#Makefile} A board's Makefile just needs to include `Makefile.base` in the RIOT repository and define the `MODULE` as `board` (see @ref creating-modules "modules" for more details) ```mk MODULE = board include $(RIOTBASE)/Makefile.base ``` ### Makefile.dep {#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: ```mk 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. ### Makefile.features {#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 @ref build-system-basics "build system basics" for more details on these variables). e.g.: ```mk 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 {#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 define the following values for the serial port (depends on the host OS): ``` 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`: ```mk # 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.txt {#board-doc} 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. @code /** @defgroup boards_foo FooBoard @ingroup boards @brief Support for the foo board @author FooName BarName ### User Interface .... ### Using UART ... ### Flashing the device ... */ @endcode # 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/` directory. **Usage:** From the RIOT base directory, run: ``` 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. # Using Common code {#common-board-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: @code -\#include "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) +/** @} */ @endcode If you want to use common makefiles, include them at the end of the specific `Makefile`, e.g. for a `Makefile.features`: ```mk 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 ``` # Boards outside of RIOTBASE {#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. ``` /home/ |----RIOT/ |---- ... |----external-boards/ |----board-foo/ |----dist/ |----scripts |----board.c |----doc.txt |----include/ |----periph_conf.h |----board.h |----gpio_params.h |----Makefile |----Makefile.dep |----Makefile.features |----Makefile.include ``` 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): ```mk 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 @ref common-board-code). e.g: `include $(RIOTBOARD)/foo-parent/Makefile.*include*` An example can be found in [`tests/external_board_native`](https://github.com/RIOT-OS/RIOT/tree/master/tests/external_board_native) # Tools {#boards-tools} Some scripts and tools available to ease `BOARD` porting and testing: - Run `dist/tools/insufficient_memory/add_insufficient_memory_board.sh ` 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`. - Run `dist/tools/compile_and_test_for_board/compile_and_test_for_board.py . --with-test-only` to run all automated tests on the new board.