This repository contains my own version of the classic embedded Hello World! — the blink an LED example. There is a blog post accompanying this example.
Most examples I found when I first started learning Rust relied on ready-made abstractions such as a HAL (Hardware Abstraction Layer) or a BSP (Board Support Package). That’s a great way to get started: install the necessary tools, copy the example, and just like that you have a blinking LED.
My goal, however, was to go a bit deeper.
When it comes to hardware-related low-level programming, I find it difficult to accept seemingly magical abstractions without first understanding how to access the hardware directly if needed.
I believe there is real value in exploring different layers of abstraction, especially when you’re new to the language. This project is my best effort to make an LED blink — but by working through various levels of abstraction rather than relying solely on prebuilt layers.
- STM32F3 Discovery Board + Mini USB Cable
- Rust Compiler with "thumb7em-none-eabihf" Target installed
- probe-rs installed
- ST-Link Driver installed
This is not a complete step-by-step guide! It shows the rough steps, but more may be necessary in detail.
- Installing rustup (Tool to install and update the rust compiler)
https://www.rust-lang.org/learn/get-started
Download and execute Rustup-init.exe 64 Bit (you may need to install “Visual Studio C++ Build tools” as described) 2) Add MCU Target
Run the following command:
rustup target add thumbv7em-none-eabihf
Allows for cross-compilation for the MCU
thumbv7em-none-eabihf => Triplet for Cortex-M4 processor with FPU
Fun Fact regarding naming of the triplet: thumbv7em => Defines the used instruction set. In this case it is the reduced arm instruction set which is called thumb. It is a smaller instruction set and therefore a thumb is smaller than an arm.
- Install probe-rs
Run the following command:
cargo install probe-rs-tools --locked
Installs probe-rs which is a tool to directly flash/debug the MCU via STLink/JLink probes
After everything has been installed, the following command can be executed in the root of the repo:
This is as basic as it can get. Blinking an LED by accessing the specific memory locations via raw pointers.
cargo run --bin minimal
PAC = Peripheral Access crate This crate is automatically generated from the SVD file of the MCU with the SVD2Rust tool. Gives you rudimentary access functions for the registers. It is no longer necessary to work with addresses, pointers or masks. Still this register level manipulation should feel familiar to low-level C-programmers.
cargo run --bin pac
A HAL crate provides typical peripheral functions directly without having to deal with registers. A HAL crate typically implements the traits of the crate embedded-hal.
cargo run --bin hal
A Board Support Package is based on the HAL and defines functions that are provided by the specific board. Here we use the STM32F3Discovery Board and an LED component.
cargo run --bin bsp
Rust is an extremely good choice for using asynchronous programming in embedded applications. Async is very powerful in modeling I/O bound systems and Rust makes it possible with very simple executors suitable even for small MCUs.
This examples shows a minimalistic spinning loop async executor that runs two LED tasks in parallel.
cargo run --bin async
All commands can be built with the addition --release as a maximum optimized variant (especially interesting for the minimum example)
All examples should flash the orange LED on the connected board with approx. 1Hz (surprise depending on --release included)
For a deeper dive into the memory footprint of a rust program cargo-binutils is an execellent tool.
cargo-binutils is:
Cargo subcommands to invoke the LLVM tools shipped with the Rust toolchain
Installation via this cmd:
cargo install cargo-binutils
This adds alot of options to cargo to get information of the binary. One example is:
cargo size --bin minimal
For an release build:
cargo size --bin minimal --release