Basic Embedded Rust Flow (Cortex-M)

🟠 A must-have template for creating a Rust project for microcontrollers.


Step Goal Result
Creating a project Starting with a clean Rust project Creating a `cargo new` structure with `main.rs`
Configuring `.cargo/config.toml` Specifying target and runner (`probe-rs`, `qemu`, etc.) Compiling for STM32 and automatically running the firmware
Creating `rust-toolchain.toml` Lock nightly version and components Unified environment on all machines
Add `memory.x` Detect microcontroller memory map Linking works correctly, firmware is loaded
Add `Embed.toml` Configure `probe-rs` (chip, speed, RTT) You can flash, run, view `defmt` logs
Add dependencies Connect HAL, cortex-m, panic, defmt You can access STM32 peripherals in Rust
Implementation of `#[panic_handler]` Ensure correct handling of `panic!` Error-free compilation, correct debugging
Definition of `#[entry] fn main()` Start point of the program Code runs after reset of the microcontroller
Configuring `RTT / semihosting` Add text output for debugging Messages can be printed via `defmt::info!`
Interrupt handling Add reactions to timers, UART, external events Interrupts work, you can react to events
Compilation and firmware Checking the entire pipeline Working STM32 firmware in Rust

πŸ“Œ 1. Project Setup

βœ… Project Creation

cargo new --bin my_project
cd my_project

βœ… Add target and toolchain

Create file rust-toolchain.toml:

[toolchain]
channel = "nightly-2022-06-28"   # or stable channel = "1.86"
components = ["rust-src", "rustfmt"]
targets = ["thumbv6m-none-eabi"]

Create file .cargo/config.toml:

[build]
target = "thumbv6m-none-eabi"

[target.thumbv6m-none-eabi]
rustflags = [
  "-C", "link-arg=--nmagic",
  "-C", "link-arg=-Tlink.x",
  "-C", "link-arg=-Tdefmt.x",
  "-C", "no-vectorize-loops",
]
runner = "elf2uf2-rs -d"   # ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠΉ, см. Π½ΠΈΠΆΠ΅
QEMU Runner
runner = """
  qemu-system-arm \
  -cpu cortex-m3 \
  -machine lm3s6965evb \
  -nographic \
  -semihosting-config enable=on,target=native
"""
probe-rs Runner
runner = "probe-rs run"
🧩 Embed.toml configuration

To run via probe-rs Create Embed.toml file in the project root:

[default]
chip = "nrf52840"   # or other: stm32f103c8, rp2040, atsamd21g18, etc.
probe = "Auto"      # you can specify a specific one (by serial or by name)
speed = 1000        # SWD/JTAG frequency in kHz

[default.rtt]
enabled = true
channels = [{ name = "defmt", up = true, down = false }]
cargo run β€” flashing and launching
cargo embed β€” flashing only
cargo embed --reset-halt β€” stop at startup
cargo embed --list-probes β€” list of available programmers
cargo embed --chip rp2040 β€” specify the chip manually

βœ… Linking and Memory

create file memory.x file in the project root

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 256K
  RAM   : ORIGIN = 0x20000000, LENGTH = 64K
}

Used by the linker (link.x) to determine the address space of the chip.


πŸ“Œ 2. Panic Handler

Without defining #[panic_handler] the project will not compile.

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

πŸ“Œ 3. Main Function

add dependencies:

cargo add cortex-m --features inline-asm
cargo add cortex-m-rt
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {}
}

πŸ“Œ 4. Text output: Semihosting

To output messages to the host (usually via QEMU or debug probe):

cargo add cortex-m-semihosting
use cortex_m_rt::entry;
use cortex_m_semihosting::hprintln;

#[entry]
fn main() -> ! {
    hprintln!("Start program").unwrap();
    loop {}
}

Semihosting is slow, but convenient for debugging. Works only with debugger/QEMU support.


πŸ“Œ 5. Interrupts / Exceptions (e.g. SysTick)

use cortex_m_rt::exception;
use cortex_m_semihosting::hprintln;

#[exception]
fn SysTick() {
    hprintln!("SysTick interrupt").ok();
}

πŸ”„ Summary of steps

  1. Initialize project: cargo new, configure .cargo/config.toml, memory.x, rust-toolchain.toml
  2. Add dependencies: cortex-m, cortex-m-rt, cortex-m-semihosting
  3. Define:
  4. #[entry]
  5. #[panic_handler]
  6. #[exception]
  7. Configure runner: depends on environment (probe-rs, QEMU, elf2uf2, etc.)
  8. Compile: cargo build, cargo run (if runner is configured)