MacBobby Chibuzor Go, Solidity, and Haskell developer interested in the cloud native world and blockchain technology. A fanatic for technical writing and open source contribution.

A complete guide to running Rust on Arduino

5 min read 1575

A complete guide to running Rust on Arduino

Arduino is an AVR microcontroller made simpler for beginners in embedded systems. While the Arduino UNO is used in this article, it is easier to follow the procedure specified here for other boards.

Rust is a systems programming language that perfectly suits low level systems and communicates properly with hardware systems. It was designed to be a modern-day alternative to C++, but with more memory safety and efficiency, as well as speed.

This tutorial is aimed at beginner- to intermediate-level Rust or embedded systems developers looking to learn how to run Rust on a microcontroller. The prerequisites for going through this article includes:

  • Knowledge of Rust language
  • Development environment for Rust language
  • Basic knowledge of Arduino
  • Basic usage of a Linux environment

Jump ahead: 

Why Rust for embedded systems?

Embedded systems technology has lacked novelty for decades. The language of choice for programming lightning-fast, embedded devices has been C/C++ for a long time, but Rust provides even faster development support.

Rust is a great fit for embedded systems development because it is:

  • Highly interoperable with C codebases
  • Portable and lightweight
  • A powerful concurrency model
  • Robust support for different microcontrollers
  • Memory safe, so there will be no problems due to memory

If you have already programmed Arduinos in C++, it will be relatively easy to transition to doing so with Rust, once you master the basics. You can learn more about embedded Rust here. You can also see the available microcontroller crates here.

What is the Arduino UNO?

The Arduino UNO is based on the ATMega328P under the AVR microcontrollers family developed by Atmel. The Arduino can be programmed with its programming language, which is derived from C++ using the Arduino IDE editor, but since it is also an open source project, other systems-compatible programming languages can also be used to program the Arduino.

The normal procedure for embedded systems development with the Arduino involves the following steps:

  1. Sketching the electrical diagram of the intended circuit
  2. Connecting the electrical components to match the diagram
  3. Writing the program logic to control the circuit as desired
  4. Connecting the microcontroller via the USB cord to the computer
  5. Flashing (or uploading) the program from the computer onto the board’s Flash memory

This article focuses on the third through fifth steps, but the wiring for the tutorial will be made available.

Tooling

To follow this article, you will need some software installed along with some hardware.

Software needs

  • A machine for writing, compiling and flashing the program to the board
  • Cargo installed (see https://rustup.rs)
  • Rust nightly compiler version installed

Hardware needs

  • The Arduino board

Installation and setup

Using Avrdude

A Hardware Abstraction Layer (HAL) is required to run Rust on AVR microcontrollers and other common boards. To get this, you need the nightly Rust compiler that compiles Rust code to AVR on your machine. Run the command below to install it:

rustup toolchain install nightly

On Windows

On Windows, you will need to install Scoop via Powershell using the command:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # Needed to run a remote script the first time
irm get.scoop.sh | iex 

The output at the end should be:

Windows output

Afterwards, run the following commands to install avr-gcc and avrdude:

scoop install avr-gcc
scoop install avrdude

On macOS

On macOS, simply set up the homebrew-avr tap and run the following commands:

xcode-select --install # for the fist time
brew tap osx-cross/avr
brew install avr-binutils avr-gcc avrdude

On Linux

If you use a Linux distribution like Ubuntu, the command is:

sudo apt install avr-libc gcc-avr pkg-config avrdude libudev-dev build-essential

There is an installation guide for all operating systems here, should you come across an obstacle following the guide above.

After either of these steps, the next step is to install the ravedudetool for flashing the microcontroller board against cargo:

cargo +stable install ravedude

This tool handles everything from finding the board, flashing the board, and listening to connections. You simply have to run the cargo run command.

Starting a new Arduino project with avrdude

Starting a new project is made simpler with the cargo-generate crate. Simply run the following commands consecutively to create a new project:

cargo install cargo-generate
cargo generate --git <https://github.com/Rahix/avr-hal-template.git>

After running the command, you should see an input field to specify a name for your project. This article uses rust-x-arduino as the project name.

After inputting your preferred name, click Enter. The next log shows a list of microcontrollers available under the avrdude template. This article uses the Arduino UNO, a variant that is readily available to everyone.

Navigate into the project after the build, and open the folder as a project in your preferred code editor. The project structure should look like the image below:

Arduino project structure

NB: If there is an error in installing the libudev-sys crate, you will have to include it in your cargo.toml file under dependencies:

[dependencies]
libudev-sys = "0.1"

The libudev Rust binding is a crate that provides declarations and linkage for the libudev C library. It is Linux-specific, so it is not available for Windows or OSX operating systems.

Alternatively, you can run the command below to install the libudev-sys crate:

sudo apt-get install libudev-dev

You can consult the libudev-sys repository in case of further issues arising from pkg-config.

Moving on, you can build the project with the build command:

cargo build

It takes a while, since it is a CPU-intensive task. Afterward, you will find a .elf file under target/avr-atmega328p/debug/. To run your own program, you can edit the main.rs file, which already contains an example code for a basic LED Blinking program:

#![no_std]
#![no_main]

use panic_halt as _;

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);

    let mut led = pins.d13.into_output();

    loop {
        led.toggle();
        arduino_hal::delay_ms(1000);
    }
}

Understanding the above embedded Rust code

From the first two lines of code, it is clear that there is no standard library and no main, since it is an embedded project with no operating system.



The line #[arduino_hal::entry] specifies the entry point in the program. The line uses panic_halt as _; is used to handle panics.

In the main function, the Peripherals are unwrapped. In embedded Rust, Peripherals refer to the components that make sense of their surroundings and interact with humans. They include sensors, actuators, motor controllers, as well as the fundamental parts of the microcontroller like the CPU, RAM, or Flash Memory. You can learn more about Peripherals in the embedded Rust book.

Next, we gain access to the pins in the Arduino board in order to set the digital output for the default pin (D13) to high.

Every Arduino program contains the void setup() and the void loop(). We have just gone through the setup; the other part of the code shows the looping.

The toggle method in the loop is used to turn the LED on and off, while the delay_ms method is used to delay the loop by the specified milliseconds.

Configuring the microcontroller for flashing

When working with the Arduino microcontroller in the official Arduino IDE, you simply have to write the program in Arduino, which is C++-based, and upload the program source file to the board via the USB port.

With Rust, we will be following a longer but similar procedure. Start by listing the open USB ports in your machine with the Linux command:

lsusb

If you have your Arduino board plugged into your device via the USB, you should see the name of the USB connected to the Arduino board like in the image below:

Name of the USB connected to the Arduino board

Next, we will be setting the serial com port for ravedude with the script:

export RAVEDUDE_PORT=/dev/ttyUSB0

This tells ravedude which port the Arduino is connected to. Running the command below will build and flash the program into the Arduino:

cargo run

Output on the microcontroller

When the program is uploaded into the microcontroller, the Arduino will behave as programmed. In this case, the LED lights on the board will blink according to the time intervals specified in the program.

Rust Arduino uno final result

Other projects

It is worth mentioning that there is an examples directory on the avr-hal crate/repository, where you can find example Arduino project programs to try out. For example, if you are using the Arduino UNO, you can select it, navigate to the src/bin directory, and find different programs compatible with the Arduino UNO board.

If you wish to write your own project, you have to generate the AVR-HAL template like we did in the previous section.

Further support for embedded Rust development

The official documentation for embedded Rust development is available here, and the book is here. There is also a GitHub repository where all resources related to embedded Rust are stored; it can serve as a useful starting point for trying out new things. It is based off of the official Rust on Embedded Devices Working Group.

If you wish to go further into embedded development with Rust, you can check out an entire playlist by Vers Binarii here.

LogRocket: Full visibility into production Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Modernize how you debug your Rust apps — .

MacBobby Chibuzor Go, Solidity, and Haskell developer interested in the cloud native world and blockchain technology. A fanatic for technical writing and open source contribution.

Leave a Reply