Why Choose Rust for Embedded Systems?
- Memory Safety: Rust prevents memory leaks and buffer overflows, which are common in embedded C/C++ programs.
- No Garbage Collection: Rust avoids the overhead of garbage collection, making it suitable for resource-constrained devices.
- Concurrency: Rust’s async and threading features allow efficient multitasking on embedded devices.
- Control Over Hardware: Rust provides low-level control while ensuring safety.
- Ecosystem: Libraries like
embedded-hal
make working with hardware peripherals simple.
Setting Up a Rust Embedded Project
To write Rust code for embedded systems, you’ll need to set up the environment properly.
Step 1: Install Rust Toolchain for Embedded Development
Install Rust and its associated tools:
rustup target add thumbv7em-none-eabihf
This adds the target for ARM Cortex-M microcontrollers. Other targets can be added similarly.
Step 2: Create a New Embedded Project
Use a template for embedded development:
cargo install cargo-generate
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
cd your_project_name
Step 3: Add Required Dependencies
Edit the Cargo.toml file to include necessary crates:
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "0.2"
panic-halt = "0.2"
Key Concepts in Rust Embedded Systems
1. embedded-hal
The embedded-hal (Hardware Abstraction Layer) crate provides an interface for working with hardware peripherals like GPIO pins, timers and SPI.
Example: Controlling an LED
use embedded_hal::digital::v2::OutputPin;
fn main() {
let mut led = LedPin::new(); // Assume `LedPin` is implemented for your board
led.set_high().unwrap(); // Turn on the LED
delay(); // Wait for some time
led.set_low().unwrap(); // Turn off the LED
}
fn delay() {
// Simple delay loop
for _ in 0..1_000_000 {
cortex_m::asm::nop();
}
}
2. Microcontroller-Specific Libraries
Each microcontroller family has its own Rust library. For example:
- STM32: Use the stm32f4xx-hal crate.
- ESP32: Use the esp32-hal crate.
Example: Blinking an LED on STM32
use stm32f4xx_hal::{pac, prelude::*, timer::Timer};
#[entry]
fn main() -> ! {
let peripherals = pac::Peripherals::take().unwrap();
let gpioa = peripherals.GPIOA.split();
let mut led = gpioa.pa5.into_push_pull_output();
let mut timer = Timer::syst(peripherals.SYST, 1.hz(), peripherals.CLOCK);
loop {
led.set_high().unwrap(); // Turn on
timer.wait().unwrap(); // Wait
led.set_low().unwrap(); // Turn off
timer.wait().unwrap(); // Wait
}
}
3. Interrupts
Rust supports handling hardware interrupts using the cortex-m-rt crate.
Example: Handling an Interrupt
use cortex_m_rt::entry;
use cortex_m::interrupt;
#[entry]
fn main() -> ! {
interrupt::free(|cs| {
// Configure and handle an interrupt
});
loop {}
}
4. Concurrency with RTIC
Real-Time Interrupt-driven Concurrency (RTIC) is a Rust framework for embedded systems that simplifies multitasking.
Example: Using RTIC
#[rtic::app(device = stm32f4xx_hal::pac)]
mod app {
#[resources]
struct Resources {
led: gpioa::PA5<Output<PushPull>>,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
let led = cx.device.GPIOA.split().pa5.into_push_pull_output();
init::LateResources { led }
}
#[task(resources = [led])]
fn blink(cx: blink::Context) {
cx.resources.led.set_high().unwrap();
cx.resources.led.set_low().unwrap();
}
}