Rust Embedded
Develop embedded systems firmware with Rust for safety, performance, and modern tooling.
Quick Start
//! Blinky example for STM32 microcontroller
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{
pac,
prelude::*,
timer::Timer,
};
use embedded_hal::digital::OutputPin;
#[entry]
fn main() -> ! {
// Get peripherals
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Configure clocks
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();
// Configure LED pin (PC13 on many STM32 boards)
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
// Configure timer
let mut timer = Timer::syst(cp.SYST, 1.Hz(), &clocks);
// Blink loop
loop {
led.set_high().unwrap();
timer.wait(); // 1 second delay
led.set_low().unwrap();
timer.wait();
}
}
# .cargo/config.toml — Cross-compilation target
[target.thumbv7em-none-eabihf]
runner = "gdb-multiarch"
rustflags = ["-C", "link-arg=-Tlink.x", "-C", "linker=arm-none-eabi-gcc"]
[build]
target = "thumbv7em-none-eabihf"
// Embedded HAL traits — abstraction for portability
use embedded_hal::{
blocking::delay::DelayMs,
digital::OutputPin,
spi::SpiDevice,
};
fn control_display<D: DelayMs<u32>, P: OutputPin>(
delay: &mut D,
reset: &mut P,
) {
reset.set_low().unwrap();
delay.delay_ms(10);
reset.set_high().unwrap();
delay.delay_ms(100);
}
Key Concepts
#![no_std] enables bare-metal code without the standard library. The Embedded HAL provides portable hardware abstraction. cortex-m-rt handles vector table and startup. Use probe-rs for flashing and debugging.
When to Use
- Firmware for ARM Cortex-M, RISC-V microcontrollers
- IoT sensor nodes and actuators
- Safety-critical embedded systems
- Replacing C/C++ with memory-safe Rust
Step-by-Step
- Pick the board/target: install
rustup target add thumbv7em-none-eabihffor Cortex-M4F or the RISC-V equivalent. - Configure the linker: add
.cargo/config.tomlwithrustflags(link.x) and default build target. - Scaffold a
no_stdcrate: use#![no_std]+#![no_main], addcortex-m-rtfor startup and a panic handler. - Get a HAL: enable
stm32f4xx_hal(ornrf-hal,esp-hal) peripheral features matching the chip. - Flash & debug: use
probe-rs/cargo embed(oropenocd + gdb-multiarch) with the target config. - Verify on device: blink an LED, then exercise a sensor/UART; check binary size with
cargo size.
Examples
// Read a button and toggle an LED via embedded-hal traits
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{pac, prelude::*, gpio::Edge};
use embedded_hal::digital::{InputPin, OutputPin};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();
let gpioa = dp.GPIOA.split(&clocks);
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
let btn = gpioa.pa0.into_pull_up_input();
loop {
if btn.is_low().unwrap() {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
cortex_m::asm::delay(100_000);
}
}
# Build, flash, and inspect the binary
cargo build --release
cargo size --release
cargo embed --release # flashes via probe-rs using the target config
Validation
cargo build --target thumbv7em-none-eabihfcompiles without std- Firmware flashes to device and runs (LED blinks)
- UART/sensor output matches expected values
- Binary size fits within target flash memory