Skip to content

Creating a Project

Step 1: Create a new project

Create a new git repository

We start by creating a new git repository for our project. If you have never worked with git before, you can find a good introduction here or here.

Let’s create a new directory for our project in which we will store our code. We will call this directory hello_world.

Terminal window
mkdir hello_world && cd hello_world

Next, we initialize a new git repository in this directory. This will allow us to track changes to our code and collaborate with others and also allows us to easily get RIOT as a submodule.

Terminal window
git init

The output of the git init command

Congratulations! You have now created a new empty git repository. In the next step, we will add RIOT as a submodule to our project.

Add RIOT as a submodule

We want to import RIOT as a submodule to our project. This will allow us to easily update to newer versions of RIOT and also allows us to easily share our project with others on GitHub, Gitlab, or any other git hosting service.

To add RIOT as a submodule, we use the following command:

Terminal window
git submodule add https://github.com/RIOT-OS/RIOT.git

Adding the submodule

When looking into our directory via ls, we can see that a new directory called RIOT has been created. This directory contains the RIOT source code. If you were to push your project to a git hosting service, the RIOT directory would not be included in the repository. Instead, the repository would contain a reference to the commit of the RIOT repository that you have added as a submodule. This way, the repository stays small and only contains the code that you have written and not the entire RIOT source code.

Open VS Code

Now that we have added RIOT as a submodule to our project, we can start writing our hello world program. You can use any text editor to create this file. We will use Visual Studio Code in this example. To open Visual Studio Code in the directory, you can use the following command:

Open Visual Studio Code
code .

Step 2: Initialize Rust

Now that Visual Studio Code is open, we need to tell Rust what kind of project we want to create. We do this by running the following command in the terminal:

Create a new Rust project
cargo new hello_world --lib

This command creates a new project called hello_world with a library crate type. The --lib flag tells Rust that we want to create a library crate instead of a binary crate. A library crate is a collection of functions and types that can be used by other programs, while a binary crate is an executable program. RIOT then calls our main function when the program starts.

You should now have 3 new files within your project directory:

  • Cargo.toml: This file contains metadata about your project and its dependencies.
  • src/lib.rs: This file contains the source code for your library crate.
  • Cargo.lock: This file contains information about the exact versions of your dependencies.
    • This file is automatically generated by Cargo and should not be edited manually so don’t worry about it for now.

The project structure

Step 3: Creating the Makefile

Now that we have created our hello world program, we need to create a Makefile to build our program. The Makefile is a build automation tool that allows us to define how our program should be built. We create a new file called Makefile in the root directory of our project and add the following code:

Makefile
# name of your application
APPLICATION = hello-world
# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_')
#
# The presence of this triggers building Rust code contained in this
# application in addition to any C code.
APPLICATION_RUST_MODULE = rust_hello_world
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# If you want to build in a Docker container, set this to 1.
# This is useful if you want to build on a system that does not have the
# RIOT toolchain installed, or if you want to ensure that the build is
# reproducible.
BUILD_IN_DOCKER ?= 1
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../../../..
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
include $(RIOTBASE)/Makefile.include

Now RIOT knows that you want to build a Rust application and will use the hello_world crate as the main module.

The Makefile in Visual Studio Code

Step 3: Adjusting the Cargo.toml

Next, we need to adjust the Cargo.toml file to tell Cargo that we want to build for RIOT. Open the Cargo.toml file and replace its contents with the following:

Cargo.toml
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[profile.release]
# Setting the panic mode has little effect on the built code (as Rust on RIOT
# supports no unwinding), but setting it allows builds on native without using
# the nightly-only lang_items feature.
panic = "abort"
[dependencies]
riot-wrappers = { version = "0.9.1", features = [ "set_panic_handler", "panic_handler_format" ] }
rust_riotmodules = { path = "./RIOT/sys/rust_riotmodules/" }

The most important part here are the dependencies. As mentioned in Rust in Riot, riot-wrappers is a crate that provides a set of wrappers around RIOT’s C functions to make them usable from Rust in a safe way.

The other configuration options are not that important for now, but you can read more about them in the Cargo documentation.

IDE Setup

Due to the way the RIOT Rust integration works, you need to amend your normal Rust setup in your IDE to make it work with RIOT. Luckily RIOT provides a small makefile command that will help you with that. Run the following command in your terminal:

Terminal window
make info-rust

The output will look something like this:

Output of make info-rust
[ann@ann-laptop13 rust01-hello-world]$ make info-rust
cargo version
cargo 1.81.0 (2dbb1af80 2024-08-20)
c2rust --version
C2Rust 0.19.0
To use this setup of Rust in an IDE, add these command line arguments to the `cargo check` or `rust-analyzer`:
--profile release
and export these environment variables:
CARGO_BUILD_TARGET="thumbv7em-none-eabihf"
RIOT_COMPILE_COMMANDS_JSON="/home/ann/projects/exercises/rust01-hello-world/bin/feather-nrf52840-sense/cargo-compile-commands.json"
RIOTBUILD_CONFIG_HEADER_C="/home/ann/projects/exercises/rust01-hello-world/bin/feather-nrf52840-sense/riotbuild/riotbuild.h"
You can also call cargo related commands with `make cargo-command CARGO_COMMAND="cargo check"`.
Beware that the way command line arguments are passed in is not consistent across cargo commands, so adding `--profile release` or other flags from above as part of CARGO_COMMAND may be necessary.

Add the environment variables to your shell configuration file for this project or use the make cargo-command command to run cargo commands with the correct arguments.

In VSCode you can now go to the workspace settings .vscode/settings.json and add the following settings:

.vscode/settings.json
{
"rust-analyzer.cargo.extraArgs": [
"--profile",
"release"
],
"rust-analyzer.cargo.target": "thumbv7em-none-eabihf",
}

Step 4: Writing the Hello World Program

We are nearly done with setting up our project. The last thing we need to do is to write the actual hello world program. Open the src/lib.rs file and replace its contents with the following code:

src/lib.rs
#![no_std]
use riot_wrappers::riot_main;
use riot_wrappers::println;
extern crate rust_riotmodules;

#![no_std] tells the Rust compiler that we are building a program that does not depend on the standard library, which is not available on the hardware we are targeting. This does mean that we can’t use some of the standard library features like std::io or std::collections, however, there are a lot of no_std compatible crates available that provide similar functionality, including riot-wrappers 😉

After that we import the functions that we will soon use to print to the console. The riot_main macro helps RIOT figure out what our main function is and println is a macro that we can use to print to the console, this replaces the println! macro from the standard library, since, as mentioned before, we can’t use it on embedded systems.

Now we need to actually write the main function. Add the following code to the src/lib.rs file:

src/lib.rs
riot_main!(main);
fn main() {
println!(
"You are running RIOT using Rust on a(n) {} board.",
riot_wrappers::BOARD
);
}

The Full Code in VSCode

Congratulations! You have now created your first Rust program for RIOT. 🎉

Step 5: Building the Program

To build our program, we use the following command:

Build and flash the program
make flash

After building the program, we can run it using the following command to start the RIOT shell:

Connect to the RIOT shell
make term

You should see the following output:

Output in the terminal
You are running RIOT using Rust on a(n) native board.

The Output in the Terminal

Conclusion

In this tutorial, you have learned how to create a new Rust project for RIOT and how to build and run it. You have also learned how to write a simple hello world program in Rust and how to use the riot-wrappers crate to interact with RIOT’s C functions.