Rust Embedded Example: Building Embedded Applications with Rust
Rust is rapidly gaining popularity in the embedded systems domain due to its safety, performance, and modern language features. It offers a powerful alternative to C/C++ for developing reliable embedded applications.
In this guide, we’ll walk through setting up a simple embedded application using Rust, specifically targeting a microcontroller.
Why Use Rust for Embedded Development?
- Memory Safety: Prevents common bugs like buffer overflows and null pointer dereferences.
- Concurrency: Modern concurrency primitives with no data races.
- Ecosystem: A growing ecosystem of crates like
embedded-hal
andcortex-m
. - Tooling: Rust’s tooling, such as
cargo
, simplifies project management.
Prerequisites
- Hardware:
For this example, we’ll target an STM32 microcontroller. Any ARM Cortex-M-based microcontroller will work with minimal changes.
Install Required Tools:
Install cargo-embed
and probe-rs
for flashing and debugging:
cargo install cargo-embed
Add the Embedded Target:
Install the target for ARM Cortex-M microcontrollers (commonly used in embedded systems):
rustup target add thumbv7em-none-eabihf
Install Rust:
Ensure you have Rust installed via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Setting Up the Project
Step 1: Create a New Project
Create a new Rust project:
cargo new rust_embedded_example --bin
cd rust_embedded_example
Step 2: Update Cargo.toml
Modify the Cargo.toml
file to include the following dependencies:
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
[dependencies.embedded-hal]
version = "0.2"
[dependencies.stm32f4xx-hal]
version = "0.12"
features = ["rt", "stm32f411"]
[profile.dev]
debug = true
[profile.release]
lto = true
cortex-m
: Provides low-level ARM Cortex-M support.cortex-m-rt
: Runtime support for ARM Cortex-M.panic-halt
: Halts the program on a panic.embedded-hal
: Hardware Abstraction Layer (HAL) for embedded peripherals.stm32f4xx-hal
: HAL for STM32F4 microcontrollers.
Step 3: Configure the Embedded Target
Create a .cargo/config.toml
file to specify the embedded target:
[build]
target = "thumbv7em-none-eabihf"
[target.thumbv7em-none-eabihf]
runner = "probe-run --chip STM32F411CEUx"
Step 4: Write the Code
Replace the content of src/main.rs
with:
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _; // Halts on panic
use stm32f4xx_hal::{
gpio::{gpioc::PC13, Output, PushPull},
prelude::*,
stm32,
};
#[entry]
fn main() -> ! {
// Get access to the device's peripherals
let dp = stm32::Peripherals::take().unwrap();
// Configure the clock
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();
// Set up the GPIO pin
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
// Blink the LED
loop {
led.set_high().unwrap();
cortex_m::asm::delay(clocks.sysclk().0 / 2); // Delay
led.set_low().unwrap();
cortex_m::asm::delay(clocks.sysclk().0 / 2); // Delay
}
}
Explanation of the Code
- No Standard Library:
#![no_std]
indicates the application does not use Rust's standard library, suitable for embedded systems.
- Runtime Entry Point:
- The
#[entry]
attribute defines the main entry point for the program.
- The
- GPIO Configuration:
- Configures the GPIO pin connected to an onboard LED as an output.
- Blinking Logic:
- Toggles the LED state with a delay loop.
Building and Flashing the Program
Flash the Microcontroller:
Connect your STM32 microcontroller and run:
cargo embed
Build the Project:
cargo build --release
Testing the Application
Once flashed, the onboard LED should start blinking at a 1 Hz rate (1 second on, 1 second off). Modify the delay value to adjust the blinking frequency.
Extending the Example
- Add Peripherals:
- Use the
embedded-hal
crate to interact with peripherals like UART, SPI, or I2C.
- Use the
- Use RTIC for Concurrency:
- RTIC (Real-Time Interrupt-driven Concurrency) is a framework for managing tasks and interrupts in embedded Rust.
- Integrate Sensors:
- Add drivers for sensors like accelerometers or temperature sensors.
- Debugging:
- Use
probe-rs
for debugging and monitoring.
- Use
Best Practices for Rust Embedded Development
- Minimize Resource Usage:
- Optimize for limited memory and compute resources.
- Handle Panics Gracefully:
- Use
panic-halt
orpanic-reset
to define behavior on panics.
- Use
- Use HAL Libraries:
- Leverage hardware abstraction layers for cleaner code.
- Test on Hardware:
- Always test your code on actual hardware to ensure proper functionality.
Conclusion
Rust’s safety and modern language features make it an excellent choice for embedded systems. By following this guide, you can build and deploy a simple embedded application on a microcontroller. As the Rust embedded ecosystem grows, it is increasingly suitable for a wide range of embedded projects, from simple tasks to complex IoT applications. Start building your embedded Rust projects today!