Using and Developing with RIOT
Author: Oliver "Oleg" Hahm
Date: 08/09/2014
Updated by: Cenk Gündoğan (12/19/2016)
Table of Content
This tutorial gives you an high level overview of the RIOT operating system. It explains how to setup the build environment to develop for RIOT, how to deploy it to hardware, and how to use it. For an easy start, this tutorial illustrates development of RIOT applications by example code. Finally, it shows how you can use RIOT even without direct access to IoT hardware by either using the IoT-Lab testbed or RIOT's powerful native
port - a sort of virtual machine for RIOT.
Setup a Build Environment
For developing under RIOT, basically all you need is a working C/C++ (cross-)compiler and the make
build system. Depending on your target platform you eventually need additional tools to interact with your board, as for example OpenOCD
. We recommend to use a Linux-based system for best development experience. FreeBSD and Mac OS X should also work, while the support for MS Windows is untested.
To create the build environment there are many options, the most convenient ones are discussed below.
Installing the tools directly on your machine
The most straight-forward way to develop on RIOT is to install the toolchain directly on your PC. You find instructions, which are specific to your target platform, here. For this step see also below.
Using a virtual machine
The second option is to use a Linux installation within a virtual machine. Have a look here for more information on how to create and configure a VM with Ubuntu in VirtualBox.
Using a docker file
RIOT comes with a ready-to-use docker file to set up a docker container dedicated for RIOT development. Have a look here for more information.
Using Vagrant
Vagrant is another alternative to easyly start RIOTing. For this purpose, a Vagrantfile is included in RIOT's root directory, which sets up a reproducible and lightweight environment with all necessary toolchains included. See here for details.
Selecting the Right Toolchain
In case you have decided to setup your own toolchain, you should read this paragraph, otherwise you can skip this. Our experience while developing software for micro-controller based systems revealed that, unfortunately, not everything behaves exactly the same way with different toolchains. Even if this toolchain is supposed to support the chosen micro-controller. Therefore, the RIOT community recommends the suggested toolchain for the particular platform in order to avoid strange behavior. In the RIOT wiki you can find a list of recommendations for each supported board. In general, the GCC ARM Embedded (Launchpad) toolchain works fine with most ARM Cortex-M platforms, while for ARM7 the CodeSourcery toolchain version 2013.11 is known to be working well. For TI MSP430 micro-controllers, the MSP430 GCC is a good choice, while you might run into problems with older versions. On AVR you can use the AVR GCC.
Obtain the Code from GitHub
You have two options to obtain the latest RIOT version:
-
Either you clone the git repository by running
git clone https://github.com/RIOT-OS/RIOT.git
or
- download the zipped version from GitHub
For this tutorial please clone also the applications repository. It can be downloaded as a zip archive, as well.
Architecture and Folder Structure
In the following figure we illustrate the RIOT architecture based on a specific hardware, the IoT-LAB M3 node.
In this picture you can see all the relevant layers, from the hardware dependent code up to the application.
The hardware dependent code is separated into two directories, cpu
and boards
. For many micro-controllers the CPU dependent code is further split up into architecture-common code (e.g., cortexm_common
) and micro-controller specific code (e.g., stm32f1
). The first part implements usually functions such as the initialization of stacks, context switching, or interrupt handling. The controller specific part implements syscalls and peripheral drivers, such as SPI or I²C. Code within the directory boards
implements the platform specific functions and consists mostly of some headers, defining things like macros for LED toggling, and the peripheral configuration, defining the connection layout of pins etc.
On top of this hardware abstraction interface, which allows all upper layers to access all supported platforms in the same way, resides the platform independent code. There is the micro-kernel, which offers multi-threading, inter-process communication, a deterministic, preemptive scheduler, mutexes, and a timer abstraction. The directory drivers
contains device drivers for additional devices, such as sensors or (radio) transceivers. In the sys
folder, you will find all utilities and libraries that you expect from a full-fledged operating system for embedded devices. It includes a shell, data structures, a POSIX wrapper, and, of course, the different network stacks.
RIOT also comes along with a thirdparty standard C library. For ARM based platforms it uses the Newlib, while MSP430-libc is used for MSP430, and avrlib for AVR based systems. This allows you to use all the common C functions like memcpy()
, printf()
, or atoi()
.
Additional Features and Utilities
Along these directories that contain the operating system code itself, you will find some additional directories. The dist
folder provides you with templates, helper and testing scripts, and useful utilities to work with IoT devices. The API documentation is located in doc
. In the directory examples
you will find applications for RIOT, which demonstrate essential features of the operating system.
RIOT also provides the option to use external libraries in a BSD port like system. This way you can use external libraries such as libcoap
or OpenWSN
by including the upstream code bases of these projects for your RIOT application. Furthermore, RIOT comes along with some test applications and handy scripts that can be used by any continuous integration (CI) infrastructure.
Best Practice for RIOT Programming
Some "Dos" and "Donts" for RIOT development:
Dos
- Use static memory.
- Select the priorities carefully.
- Minimize stack usage with
DEVELHELP
and CREATE_STACKTEST
.
- Use threads to increase flexibility, modularity, and robustness by leveraging IPC.
Donts
- Don't use too many threads. Try not to use more than one thread per module. Don't create threads for one-time tasks. Usually, many tasks can be handled by an event loop that runs in a single thread.
- Don't use the POSIX wrapper if implementing something from scratch.
Working with an Example
Compile and run on native
The native port gives you a powerful tool for debugging and testing your software before deploying it on IoT hardware. It allows you to run any RIOT application as a process on your Linux or BSD/Mac OS host system. It even allows you to connect multiple processes virtually with each other - just like they would be connected over a network in a real IoT deployment.
For compiling the RIOT native
port, you will need the 32 bit version of some packages. All dependencies are described in the corresponding RIOT wiki page.
So, let's prepare a simple, virtual network. This requires to install sudo
and bridge-utils
and also requires superuser privileges for the setup. Go to your RIOT main directory and call
[RIOT]$ dist/tools/tapsetup/tapsetup -c 2
creating tapbr0 ...
creating tap0 ...
creating tap1 ...
Now we build and start the first virtual node. Therefore, we switch to the default example folder.
[RIOT]$ cd examples/default/
[default]$ make all term
Building application default for native w/ MCU native.
"make" -C /home/oleg/git/RIOT/cpu/native
...
RIOT native interrupts/signals initialized.
LED_RED_OFF
LED_GREEN_ON
RIOT native board initialized.
RIOT native hardware initialization complete.
main(): This is RIOT! (Version: 2017.01-devel-154-gbe702-zoidberg)
Native RTC initialized.
Welcome to RIOT!
>
> help
help
Command Description
---------------------------------------
reboot Reboot the node
ps Prints information about running threads.
rtc control RTC peripheral interface
ifconfig Configure network interfaces
txtsnd Sends a custom string as is over the link layer
saul interact with sensors and actuators using SAUL</code><code>
The command make term
gives you a (serial) interface to the node, so that you can see its output and send some commands to it.
Setup the Second Node
Now we want to extend our scenario by a second node.
Therefore, we specify a tap interface by (re-)using the PORT
environment variable. We can use any created tap interface from the previous step. (Default is tap0.) Once the node has started, we can query its hardware address with the ifconfig
command.
[default]$ PORT=tap1 make term
Welcome to RIOT!
> ifconfig
ifconfig
Iface 4 HWaddr: 76:7c:09:2d:53:f4
Source address length: 6
We do the same on the first node and also query its hardware address with the ifconfig
command.
Connecting two nodes
It's time to send our first packet (assuming that the first node is still running):
> txtsnd 4 76:7c:09:2d:53:f4 riotlab
More info about the required parameters can be obtained by calling txtsnd
without any parameters.
On the first node we should see something like:
> PKTDUMP: data received:
~~ SNIP 0 - size: 7 byte, type: NETTYPE_UNDEF (0)
000000 72 69 6f 74 6c 61 62
~~ SNIP 1 - size: 20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4 rssi: 0 lqi: 0
flags: 0x0
src_l2addr: 76:7c:09:2d:53:f4
dst_l2addr: 76:ab:08:1b:5f:de
~~ PKT - 2 snips, total size: 27 byte
The received packet internally resembles two snips with different protocol types:
Using the same example on real hardware
To run the same example on a real node, we need to perform almost the same steps compared to the native port.
We use the environment variable BOARD
to specify the target platform - and maybe PORT
for the serial port. make term
will open a terminal program for the serial communication. make flash
will start the correct programming tool for the specified node and sets the corresponding parameters.
[default]$ BOARD=iotlab-m3 make all flash term
Building application default for iotlab-m3 with MCU stm32f1.
"make" -C /home/oleg/git/RIOT/cpu/stm32f1
...
wrote 55296 bytes from file RIOT/examples/default/bin/iot-lab_M3/default.hex
in 2.454112s (22.004 KiB/s)
...
shutdown command invoked
RIOT/dist/tools/pyterm/pyterm -p /dev/ttyUSB2 -b 500000
INFO # Connect to serial port /dev/ttyUSB2
Welcome to pyterm!
Type '/exit' to exit.
help
> help
Command Description
---------------------------------------
reboot Reboot the node
ps Prints information about running threads.
ifconfig Configure network interfaces
txtsnd Sends a custom string as is over the link layer
saul interact with sensors and actuators using SAUL
...and on a testbed
- In case you want to test your IoT application in a (very) large scale testbed, you can use the IoT-LAB.
- To run your experiments there, just register for an account and follow the (very good) tutorials on their page to get used to their interface.
- In order to run a RIOT application on the IoT-LAB, just call
BOARD=iotlab-m3 make all
to build the binary (elf and hex are built).
- Flash the nodes like usual (using the IoT-LAB web interface or CLI).
-
By default, IoT-LAB proposes to use netcat to access the nodes. If you want use it somewhat more comfortable than using netcat, you can use the same tool which is used for a locally connected node: pyterm.
You can find it in dist/tools/pyterm
.
- For example, run
pyterm -ts m3-7:20000
on grenoble.iot-lab.info
.
Using a Custom Application
The shell in a nutshell
- For this step we will use gnrc_networking example application.
- You can configure RIOT to provide you with some default system shell commands.
- All available shell commands and some online help are shown by calling
help
:
> help
help
Command Description
---------------------------------------
udp send data over UDP and listen on UDP ports
reboot Reboot the node
ps Prints information about running threads.
ping6 Ping via ICMPv6
random_init initializes the PRNG
random_get returns 32 bit of pseudo randomness
ifconfig Configure network interfaces
txtsnd Sends a custom string as is over the link layer
fibroute Manipulate the FIB (info: 'fibroute [add|del]')
ncache manage neighbor cache by hand
routers IPv6 default router list
rpl rpl configuration tool ('rpl help' for more information)
The selection of commands depends on the configuration of your application (and may vary a little bit for different platforms).
Let's communicate
The gnrc_networking example application provides you with a custom (application defined) shell command:
- udp: A very basic variation of netcat for arbitrary UDP connections.
After we flashed two nodes, we'll have to lookup their IPv6 addresses using ifconfig
:
> ifconfig
Iface 7 HWaddr: 06:02 Channel: 26 Page: 0 NID: 0x23
Long HWaddr: 36:32:48:33:46:d8:86:02
TX-Power: 0dBm State: IDLE max. Retrans.: 3 CSMA Retries: 4
ACK_REQ CSMA MTU:1280 HL:64 6LO RTR IPHC
Source address length: 8
Link type: wireless
inet6 addr: ff02::1/128 scope: local [multicast]
inet6 addr: fe80::3432:4833:46d8:8602/64 scope: local
inet6 addr: ff02::1:ffd8:8602/128 scope: local [multicast]
inet6 addr: ff02::1a/128 scope: local [multicast]
Statistics for Layer 2
RX packets 110 bytes 4688
TX packets 28 (Multicast: 28) bytes 1681
TX succeeded 28 errors 0
Statistics for IPv6
RX packets 110 bytes 6998
TX packets 28 (Multicast: 28) bytes 1778
TX succeeded 28 errors 0
First, we can try to ping the other node. The ping
command is provided by RIOT:
> ping6 fe80::3432:4833:46d8:8602
bytes from fe80::3432:4833:46d8:8602: id=83 seq=1 hop limit=64 time = 14.036 ms
bytes from fe80::3432:4833:46d8:8602: id=83 seq=2 hop limit=64 time = 17.874 ms
bytes from fe80::3432:4833:46d8:8602: id=83 seq=3 hop limit=64 time = 16.595 ms
--- fe80::3432:4833:46d8:8602 ping statistics ---
packets transmitted, 3 received, 0% packet loss, time 2.0660412 s
rtt min/avg/max = 14.036/16.168/17.874 ms
If this works, we can also try to send a packet over UDP. Therefore, we open an UDP socket for reception using udp
on the first node:
>udp server start 1234
Success: started UDP server on port 1234
Now, we use the second node to send a text message over UDP to the first node - again using udp
:
>udp send fe80::3432:4833:46d8:8602 1234 hello
Success: sent 5 byte to [fe80::3432:4833:46d8:8602]:1234
On the first node we should see now something like this:
PKTDUMP: data received:
~~ SNIP 0 - size: 5 byte, type: NETTYPE_UNDEF (0)
68 65 6c 6c 6f
~~ SNIP 1 - size: 8 byte, type: NETTYPE_UDP (4)
src-port: 1234 dst-port: 1234
length: 13 cksum: 0x420dc
~~ SNIP 2 - size: 40 byte, type: NETTYPE_IPV6 (2)
traffic class: 0x00 (ECN: 0x0, DSCP: 0x00)
flow label: 0x00000
length: 13 next header: 17 hop limit: 64
source address: fe80::3432:4833:46d8:8802
destination address: fe80::3432:4833:46d8:8602
~~ SNIP 3 - size: 24 byte, type: NETTYPE_NETIF (-1)
if_pid: 7 rssi: 59 lqi: 255
flags: 0x0
src_l2addr: 36:32:48:33:46:d8:88:02
dst_l2addr: 36:32:48:33:46:d8:86:02
~~ PKT - 4 snips, total size: 77 byte
This time, the received packet has two additional protocol types: NETTYPE_IPv6
and NETTYPE_UDP
Virtualize your Network
Setting up the Application
The Makefile
To create your own RIOT example, you need only two files:
- A C (or C++) file with a main function
- A Makefile
You can find a template for a Makefile in dist/Makefile
.
####
#### Sample Makefile for building applications with the RIOT OS
####
#### The example file system layout is:
#### ./application Makefile
#### ../../RIOT
####
# Set the name of your application:
APPLICATION = foobar
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../../RIOT
# Uncomment this to enable scheduler statistics for ps:
#CFLAGS += -DSCHEDSTATISTICS
# If you want to use native with valgrind, you should recompile native
# with the target all-valgrind instead of all:
# make -B clean all-valgrind
# Uncomment this to enable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
#CFLAGS += -DDEVELHELP
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
# Modules to include:
#USEMODULE += shell
#USEMODULE += posix
#USEMODULE += xtimer
# If your application is very simple and doesn't use modules that use
# messaging, it can be disabled to save some memory:
#DISABLE_MODULE += core_msg
#export INCLUDES += -Iapplication_include
# Specify custom dependencies for your application here ...
# APPDEPS = app_data.h config.h
include $(RIOTBASE)/Makefile.include
# ... and define them here (after including Makefile.include,
# otherwise you modify the standard target):
#proj_data.h: script.py data.tar.gz
# ./script.py
The main()
Function
The only mandatory function in your application is a main
function with this prototype:
int main(void)
Standard C libraries can be used and parts of POSIX are available through a wrapper within RIOT.
#include <stdio.h>
#include <unistd.h>
#define WAIT_USEC (1000 * 1000)
int main(void)
{
puts("Hello!");
usleep(WAIT_USEC);
puts("Good night!");
return 0;
}
Doing it the RIOT way
Instead of POSIX functions, it is usually advisable (and more efficient) to just use the native RIOT functions. For this, include USEMODULE += xtimer
in the Makefile of your application and modify the main function to the following:
#include <stdio.h>
#include "xtimer.h"
int main(void)
{
puts("Let's throw a brick!");
xtimer_usleep(SEC_IN_USEC);
puts("System terminated");
return 0;
}
Some Helpful Features
Looking at source code of the gnrc_networking
example reveals some useful features of RIOT. In this section, we will take a closer look at the code to understand how the shell and the network parts can be used.
Starting the shell
The shell can be used on top of anything that provides functions to read or write characters. Usually this will be a serial interface. The shell is provided by adding USEMODULE += shell
in the Makefile. If we use USEMODULE += shell_commands
in the application's Makefile, RIOT will provide some default system shell handlers. Additionally, we can supply our own custom shell commands in the application, by creating and passing an array of shell_command_t
structs to the shell_run()
. Furthermore, a buffer for the shell must be supplied. Now, we're ready to run the shell (which will do so in an endless loop).
#include <stdio.h>
#include <shell.h>
...
const shell_command_t shell_commands[] = {
{"udp", "send data over UDP and listen on UDP ports", udp_cmd },
{NULL, NULL, NULL}
};
...
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
...and networking
If you specified to use IPv6 networking in your Makefile (see here for more information), RIOT's auto_init
feature will take care of the necessary initialization. This also includes the usage of 6LoWPAN as an adaptation layer for IPv6 on 802.15.4 devices.
Now, you might be interested in sniffing on the IPv6 layer for all incoming packets. In order to do so, you can register your thread to receive IPC messages that contain incoming IPv6 packets of the type GNRC_NETTYPE_IPV6. Your monitor thread will have to run msg_receive()
(preferably in an endless loop) to get the information. Since the thread might be registered to several layers or receive other messages from other destinations, it is necessary to check the message type after reception. Once we've checked that and it contains indeed an IPv6 packet, we can check and parse the content of the packet.
static msg_t _msg_q[8];
msg_init_queue(_msg_q, 8);
gnrc_netreg_entry reg = GNRC_NETREG_ENTRY_INIT_PID(GNRC_NETREG_DEMUX_CTX_ALL, sched_active_pid);
gnrc_netreg_register(GNRC_NETTYPE_IPV6, ®);
gnrc_pktsnip_t *pkt = NULL;
while (1) {
msg_receive(&msg);
switch (msg.type) {
case GNRC_NETAPI_MSG_TYPE_RCV:
pkt = msg.content.ptr;
assert (pkt->type == GNRC_NETTYPE_IPV6)
...
break;
...
}
}
You can find more information regarding the default networking stack (GNRC) of RIOT in the documentation.
Up to the Transport Layer
It is possible to interact directly with the GNRC network stack in order to communicate on the transport layer. For UDP, this is displayed in the udp.c
file of the gnrc_networking
example.
...
gnrc_pktsnip_t *payload, *udp, *ip;
payload = gnrc_pktbuf_add(NULL, data, strlen(data), GNRC_NETTYPE_UNDEF);
udp = gnrc_udp_hdr_build(payload, port, port);
ip = gnrc_ipv6_hdr_build(udp, NULL, &addr);
gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL, ip);
...
While this method is simple, it is only useful for applications that intend to only run with the GNRC network stack. A more portable option is to use the Sock API. If your application is also planned to compile for other non-RIOT systems, then there is also a POSIX wrapper for RIOT sockets.
As an example on how to use the posix abstraction of RIOT's sockets, there is an application called posix_sockets
in the examples folder of RIOT's root directory.
Exercise: Reading a Sensor
Get your hands dirty
After this tutorial you should be ready to write your own application - or extend an existing one. As an exercise you could try to extend the gnrc_networking example and test it on the IoT-LAB.
- Check the documentation for the sensor API, e.g. for the light sensor.
- Initialize all four sensors in the beginning of the application.
- Extend the udp command to send optionally measured sensor data.
- Print the measurements on the receiving side.
In case you have any questions, regarding this tutorial or about RIOT in general, you are cordially invited to contact the RIOT community and ask for help. Here are common ways to get in touch with the community.
Further Help
- Mailing Lists
- IRC on irc.freenode.org, #riot-os
- Regular video conferencing meetings