- Di 04 Februar 2020
- riot
- Martine S. Lenders
- #riot, #board, #microcontroller, #adafruit, #feather, #nrf52
Introduction
It is already a few weeks back, but this year marked a debut for me: Though I am a long-term contributor to the RIOT project—my oldest contribution goes back to June 2011—I never actually ported a board for RIOT support. This changed at the beginning of this year.
RIOT is an operating system for the Internet of Things (IoT), so an operating system for hardware platforms that do not typical PC operating systems fit like Linux, Mac OSX, or Windows or even phone operating systems like Android or iOS. When talking of IoT devices, the RIOT community usually thinks in terms of kilobytes of memory. Not gigabytes, not megabytes, kilobytes! As in, a few 1000 bytes. The premise behind this thinking is, that IoT devices can not follow Moore's Law—which states that every two years the number of transistors in a given piece of computer hardware approximately doubles. In fact, the efforts to save cost and energy for IoT hardware are directly opposed to Moore's Law: every additional transistor, be it for calculation or storage, costs more money and leads to more energy usage. Given, that for the IoT a multiple of 10 of devices per person is expected, meaning at least 100 billion devices deployed, low cost and low energy usage is vital.
The main code base of RIOT is written in the C programming language, but bindings for C++, Rust, Python, JavaScript, and others exist as well.
For RIOT, a board is defined as a module that defines a microcontroller and configuration parameters how this microcontroller can interact with other devices on this platform and the outside world. Colloquially speaking, a board is somewhat like a computer system (e.g. a PC), and the microcontroller the central processor (CPU). So the process of porting a board is providing these configuration parameters.
Personally, I am more involved with the networking architecture of RIOT—I make sure RIOT can talk to the outside world. So my closest contact to hardware in that area is mostly the interaction with the network device. Thus, there was just never the need for me to port a board for RIOT. So why did I do it now? Mainly, because I wanted to try and learn and because I had this piece of hardware lying around, for which I knew, that the port would be relatively easy, considering my skill set.
The board I ported is the Adafruit Feather nRF52840. I bought this board earlier this year mainly to save some shipping costs, but also because I like the form factor of Adafruit's Feather line and wanted to see more support for it in RIOT. It then became incidentally part of the most current RIOT release 2020.01.
I am intentionally leaving the process of creating a pull request out as I want to focus this article mainly on how to provide a board port, not how to get it upstream.
Creating the board port
As I said, a board in RIOT is mostly just configuration, both for the build
system and the actual code. But first I needed a place to put this
configuration.
For simplicity I cut out the “Adafruit” out of the board's identifying name—and
because the Adafruit Feather M0 already was called feather-m0
. Hence, for
RIOT's purpuses the board is now just called feather-nrf52840
. Under this name
I created a directory for the board configuration to reside in:
boards/feather-nrf52840
.
There I first created a Makefile to tell the build system make
that
the directory is indeed describing a board. This file is called
boards/feather-nrf52840/Makefile
:
MODULE = board
include $(RIOTBASE)/Makefile.base
The first line tells the build system, that this is a module called board
(all
boards are called like this) and the third includes all the stuff the build
system developers of RIOT already provided for a module to be built.
However, the build system needs more information. What features (i.e. interfaces
and devices) does the board support? This is done with the file
boards/feather-nrf52840/Makefile.features
:
CPU_MODEL = nrf52840xxaa
# Put defined MCU peripherals here (in alphabetical order)
FEATURES_PROVIDED += periph_i2c
FEATURES_PROVIDED += periph_spi
FEATURES_PROVIDED += periph_uart
FEATURES_PROVIDED += periph_usbdev
# Various other features (if any)
FEATURES_PROVIDED += radio_nrf802154
include $(RIOTBOARD)/common/nrf52/Makefile.features
The first line tells the build system, the CPU (or more accurate microcontoller) the board is using in a naming scheme that is understand by the build system. We basically tell it, "this board is using an nRF52840 microcontroller". Lines 4-9 tell the build system which features are provided by the board, naming communication interfaces such as I²C, SPI, UART, and USB, but also that the radio of this board can speak the IEEE 802.15.4 protocol—imagine something like low-power WiFi—via its nRF microcontroller. The last line declares to include further features provided by the nRF52 family of microcontrollers in general.
Lastly, I tell the build system with the file
boards/feather-nrf52840/Makefile.include
, what else needs
to be included for this board to be programmable, among them the most important
thing: the programmer itself. In case of the Feather nRF52840 this is the Segger
JLink:
# HACK: replicate dependency resolution in Makefile.dep, only works
# if `USEMODULE` or `DEFAULT_MODULE` is set by the command line or in the
# application Makefile.
ifeq (,$(filter stdio_%,$(DISABLE_MODULE) $(USEMODULE)))
RIOT_TERMINAL ?= jlink
endif
include $(RIOTMAKE)/tools/serial.inc.mk
include $(RIOTBOARD)/common/nrf52/Makefile.include
Now we finally leave the build system and tell the microcontroller where to put
all the peripheral interfaces to the outside world and devices on the board in
the boards/feather-nrf52840/include/periph_config.h
. Namely,
I configured one UART, one I²C and one SPI interface according to the pin
labeling on the board. I strictly followed the schematics provided by Adafruit
for this and looked at the periph_conf.h
of other nRF52-based boards for
reference.
/*
* Copyright (C) 2020 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup boards_feather-nrf52840
* @{
*
* @file
* @brief Peripheral configuration for the Adafruit Feather nRF52840
* Express
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*
*/
#ifndef PERIPH_CONF_H
#define PERIPH_CONF_H
#include "periph_cpu.h"
#include "cfg_clock_32_1.h"
#include "cfg_rtt_default.h"
#include "cfg_timer_default.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name UART configuration
* @{
*/
static const uart_conf_t uart_config[] = {
{
.dev = NRF_UARTE0,
.rx_pin = GPIO_PIN(0,24),
.tx_pin = GPIO_PIN(0,25),
.rts_pin = (uint8_t)GPIO_UNDEF,
.cts_pin = (uint8_t)GPIO_UNDEF,
.irqn = UARTE0_UART0_IRQn,
},
};
#define UART_0_ISR (isr_uart0)
#define UART_NUMOF ARRAY_SIZE(uart_config)
/** @} */
/**
* @name SPI configuration
* @{
*/
static const spi_conf_t spi_config[] = {
{
.dev = NRF_SPI0,
.sclk = 14,
.mosi = 13,
.miso = 15
}
};
#define SPI_NUMOF ARRAY_SIZE(spi_config)
/** @} */
/**
* @name I2C configuration
* @{
*/
static const i2c_conf_t i2c_config[] = {
{
.dev = NRF_TWIM1,
.scl = 11,
.sda = 12,
.speed = I2C_SPEED_NORMAL
}
};
#define I2C_NUMOF ARRAY_SIZE(i2c_config)
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* PERIPH_CONF_H */
We also need to tell the sensor/actuator abstraction layer of RIOT, SAUL,
where to find all the buttons and LEDs provided on the board, so it can react to
button push events and control the LEDs. This we do in
boards/feather-nrf52840/include/gpio_params.h
. There are
two LEDs and one switch and I again just followed the schematics:
/*
* Copyright (C) 2020 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup boards_feather-nrf52840
* @{
*
* @file
* @brief Configuration of SAUL mapped GPIO pins
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#ifndef GPIO_PARAMS_H
#define GPIO_PARAMS_H
#include "board.h"
#include "saul/periph.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED configuration
*/
static const saul_gpio_params_t saul_gpio_params[] =
{
{
.name = "LED Red (D3)",
.pin = LED0_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INIT_CLEAR),
},
{
.name = "LED Blue (Conn)",
.pin = LED1_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INIT_CLEAR),
},
{
.name = "UserSw",
.pin = BTN0_PIN,
.mode = BTN0_MODE,
.flags = SAUL_GPIO_INVERTED,
},
};
#ifdef __cplusplus
}
#endif
#endif /* GPIO_PARAMS_H */
/** @} */
Lastly, the board needs to define some low-level debugging LED and button GPIO
pins and needs to initialize those pins. We do this in the in
boards/feather-nrf52840/include/board.h
and
boards/feather-nrf52840/board.c
.
Rumors tell, this might not be necessary in the future, so watch out for this
later if you read this when you follow my instructions ;-).
/*
* Copyright (C) 2020 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include "cpu.h"
#include "board.h"
#include "periph/gpio.h"
void board_init(void)
{
/* initialize the boards LEDs */
gpio_init(LED0_PIN, GPIO_OUT);
gpio_clear(LED0_PIN);
gpio_init(LED1_PIN, GPIO_OUT);
gpio_clear(LED1_PIN);
/* initialize the CPU */
cpu_init();
}
/** @} */
/*
* Copyright (C) 2020 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include "cpu.h"
#include "board.h"
#include "periph/gpio.h"
void board_init(void)
{
/* initialize the boards LEDs */
gpio_init(LED0_PIN, GPIO_OUT);
gpio_clear(LED0_PIN);
gpio_init(LED1_PIN, GPIO_OUT);
gpio_clear(LED1_PIN);
/* initialize the CPU */
cpu_init();
}
/** @} */
After that I tested if everything worked as configured, provided some documentation how to do that, and then called it done.
Conclusion
Given that you already worked with C and make
and that the microcontroller for
the board you want to port is already provided, porting a board is quite
straight-forward. Basically, all that is needed are 7 very concise files. All
you need beyond that is to look up what other people did and maybe read a bit up
on the boards doc. Being able to read hardware schematics also helps, but I
found myself quickly into the graphic notation for that, though I barely had
contact with this kind of notation before.
Update
Since the writing of this blog article another file is needed for a board port, so the new Kconfig system can recognize it.
It mostly reflects the content of Makefile.features
and Makefile.include
, just in Kconfig language:
# Copyright (c) 2020 HAW Hamburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
config BOARD
default "feather-nrf52840" if BOARD_FEATHER_NRF52840
config BOARD_FEATHER_NRF52840
bool
default y
select BOARD_COMMON_NRF52
select CPU_MODEL_NRF52840XXAA
select HAS_PERIPH_I2C
select HAS_PERIPH_SPI
select HAS_PERIPH_UART
select HAS_PERIPH_USBDEV
select HAS_RADIO_NRF802154
source "$(RIOTBOARD)/common/nrf52/Kconfig"