Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

RIOT Tutorial

0.00/5 (No votes)
27 Dec 2016 1  
Using and Developing with RIOT

Using and Developing with RIOT

RIOT logo

Author: Oliver "Oleg" Hahm

Date: 08/09/2014

Updated by: Cenk Gündoğan (12/19/2016)

Table of Content

Introduction

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.

Start the 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

GitHub Logo You have two options to obtain the latest RIOT version:

  1. Either you clone the git repository by running

    git clone https://github.com/RIOT-OS/RIOT.git

    or

  2. 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.

Understanding RIOT

Architecture and Folder Structure

In the following figure we illustrate the RIOT architecture based on a specific hardware, the IoT-LAB M3 node.

RIOT Architecture for the 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.

Using RIOT

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 typesNETTYPE_IPv6 and NETTYPE_UDP

Virtualize your Network

Wireshark

Writing an Application for RIOT

Setting up the Application

The Makefile

To create your own RIOT example, you need only two files:

  1. A C (or C++) file with a main function
  2. 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}
};
...

/* start shell */
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.

/* First, initialize a message queue for this thread */
static msg_t _msg_q[8];
msg_init_queue(_msg_q, 8);

/* register thread with currently active pid for messages of type GNRC_NETTYPE_IPV6 */
gnrc_netreg_entry reg = GNRC_NETREG_ENTRY_INIT_PID(GNRC_NETREG_DEMUX_CTX_ALL, sched_active_pid);
gnrc_netreg_register(GNRC_NETTYPE_IPV6, &reg);

/* packets are stored in gnrc_pktsnip_t structs */
gnrc_pktsnip_t *pkt = NULL;

/* we receive endlessly .. */
while (1) {
    msg_receive(&msg);
    switch (msg.type) {
        case GNRC_NETAPI_MSG_TYPE_RCV:
            pkt = msg.content.ptr;
            /* if pkt->type is of type GNRC_NETTYPE_IPV6,
             * then this message is an incoming IPv6 packet */
            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); /* source port - destination 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.

Join the RIOT

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here