Blog

Oxidizing Bare Metal: Rust Programming for ARM Microcontrollers

Oxidizing Bare Metal: Rust Programming for ARM Microcontrollers

Being an embedded systems developer is always an exciting challenge. One of the downsides though is that in general, programming and development tools are somewhat limited. Developers are almost always restricted to C/C++ and often can't rely on more extensive open source libraries. However, recently there have been many efforts to expand high-level language support to ARM-based bare metal targets.

Projects using MicroPython and JerryScript attempt to provide a means to write Python and JavaScript code and provide frameworks for “compiling” and running scripts on a variety of bare-metal targets. However, these types of frameworks have some inherent drawbacks. JavaScript and Python are both interpreted languages with garbage collection and weak typing. Running these languages requires relying on a run time or more complex cross compiler.

A New Language for Embedded Systems

More recently, Rust has emerged as an appealing language for applications that require memory safety and efficiency. Both of these features are often critical for embedded devices. Manual memory management often creates bugs which are hard to isolate and are not always easily remedied due to the resource constraints of embedded devices.

Additionally, C/C++, even given all of its faults, remains a highly efficient language due to the control it provides developers and because it can use many target-specific optimizations at compile time. C/C++ sets a high benchmark for speed that is difficult for interpreted languages to match.

Rust, fortunately, solves both problems in ways that are particularly well suited to microcontrollers. Rust does not require any run-time engine for garbage collection or memory management. Rust instead chooses to enforce memory safety at compile time. Furthermore, because Rust compiles to LLVM, target optimizations rivaling those achievable with C/C++ are possible. Recently, the Rust core team has made support for new target architectures a priority, and there are already many open source projects aimed at expanding support for embedded devices in Rust.

Nucleo PCB Running on Rust

Getting Started with Rust

The Embedded Rust Book provides many details regarding getting started with Rust on embedded devices. For this blog, I’ll highlight a few key components that make up the Rust embedded landscape. Specifically, I’ll be discussing support for ARM Cortex M based microcontrollers.

The Rust ecosystem is built on an extensive library of packages called “Crates.” A few specific crates, “cortex-m” and “cortex-m-rt”, make up the core support for ARM targets. These packages provide the bare minimum of code to get a basic program up and running. It allows access and control of the core ARM registers.

Below is an example of setting up the systick peripheral taken from the Rust Embedded Book.

use cortex_m::peripheral::{syst, Peripherals};
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    let mut peripherals = Peripherals::take().unwrap();
    let mut systick = peripherals.SYST;
    systick.set_clock_source(syst::SystClkSource::Core);
    systick.set_reload(1_000);
    systick.clear_current();
    systick.enable_counter();
    while !systick.has_wrapped() {
        // Loop
    }

    loop {}
}

To implement something more interesting than a simple timer, we’ll need a Peripheral Access crate. These crates are typically specific to a vendor and family of microcontrollers. For example, the STM32F4 crate provides an API for accessing various peripherals such as GPIO, UART, and I2C for the STM32F4 family of devices.

Pulling in a Peripheral Access crate, we can build a program for toggling an LED:

fn main() -> ! {
    let mut peripherals = stm32f429::Peripherals::take().unwrap();

    //Enable gpio b clock
    let rcc = &peripherals.RCC;
    rcc.ahb1enr.write(|w| w.gpioben().bit(true));

    //set pin 14 to output
    let gpio = &peripherals.GPIOB;
    gpio.moder.write(|w| w.moder14().bits(1));

    //Turn on led
    gpio.odr.write(|w| w.odr14().set_bit());

    loop {

        delay(10000);
        if  gpio.odr.read().odr14().bit()
        {
            gpio.odr.write(|w| w.odr14().clear_bit());
        }
        else {
            gpio.odr.write(|w| w.odr14().set_bit());
        }
        
    }
}

fn delay(count: u32) {
     for _ in 0..count { cortex_m::asm::nop() }
}

Although Rust is an exciting new option for embedded development, it will take some time for it to supplant C/C++ as the standard. A lack of official support from major silicon vendors like ST, NXP, and Silicon Labs means that adding support for new microcontrollers can be a tedious process. Additionally, C/C++ frameworks like ARM's mbed offer large, general purpose libraries and drivers that can be used across a variety of ARM targets with little to no modification.

Learn more about DMC's Embedded Product Development Services and Embedded Systems Platforms expertise.

Comments

There are currently no comments, be the first to post one.

Post a comment

Name (required)

Email (required)

CAPTCHA image
Enter the code shown above:

Related Blog Posts