Get started with embedded Rust in 5 simple steps.

1. Pick a HAL.

Feel free to seach one in the list below. If you don't find your HAL listed, you might be lucky directly looking on

If you cannot find an existing HAL, please read about the rust embedded layers here, so you can create your own.

2. Create a new cargo project.

mkdir embedded-project
cd embedded-project
cargo init

3. Make sure the linker and target architecture are set up correctly.

To tell cargo to pass the correct linkerscript to the linker, we create a file called `.cargo/config` with the following contents:

runner = 'arm-none-eabi-gdb'
rustflags = [
    "-C", "link-arg=-Tlink.x",

target = "thumbv6m-none-eabi"

4. Add a memory.x to tell the linker your memory-layout.

We told the linker to use the link.x linker file, but this file expects that we provide it a memory.x to be included during the linker process.

Normally it is well enough to just set the FLASH and RAM ORIGIN & SIZE. For advanced usages you can set some more parameters. Of course you can also provide a 100% custom linker script!

    /* NOTE K = KiBi = 1024 bytes */
    /* TODO Adjust these memory regions to match your device memory layout */
    FLASH : ORIGIN = 0x8000000, LENGTH = 256K 
    RAM : ORIGIN = 0x20000000, LENGTH = 64K

/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* You may want to use this variable to locate the call stack and static
    variables in different memory regions. Below is shown the default value */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */

/* You can use this symbol to customize the location of the .text section */
/* If omitted the .text section will be placed right after the .vector_table
    section */
/* This is required only on microcontrollers that store some configuration right
    after the vector table */
/* _stext = ORIGIN(FLASH) + 0x400; */

/* Size of the heap (in bytes) */
/* _heap_size = 1024; */

5. Include the basic crates.

We already told cargo what linker script to use in step 3. and 4. But where does the link.x come from? This is where we need the cortex-m-rt (ARM Cortex M Runtime) crate, which provides a linkerscript for the cortex-m architecture with a few customization possibilities left to the user, such as FLASH and RAM size. It also provides many other things, amongst them an init script! You could also write your own custom linker script and init procedures, but that's out of scope here. To use basic ARM procedures in our code, we also include the cortex-m crate which gives us access to a few utilities to use ARM functionality such as the NVIC or system reset peripherals in a handy & safe way. And last but not least, we need to provide the HAL itself, which provides some additional includes for the link.x, but more importantly convenient functions to use your MCU.

We add those crates as dependencies to our Cargo.toml:

cortex-m = "0.6.0"
cortex-m-rt = "0.6.10"
stm32l4xx-hal = {version="*", features=["stm32l4x2", "rt"] }

Now you are set up to write actual code!

6. Write our embedded "Hello World", a blinky demo!

Now we can write a very basic main, which just toggles an IO. Of course this can vary from HAL to HAL, depending on the underlying MCU, but most HALs try to adhere to the traits defined in the embedded-hal crate. Of course, sometimes these traits reach their limitations, so HALs are forced to implement their own architecture-specific (or even application-specific) implementattions to provide more flexibility and performance.

We add those crates as dependencies to our Cargo.toml:


use microbit::hal::delay::Delay;
use microbit::hal::prelude::*;

use cortex_m_rt::entry;

fn main() -> ! {
    if let Some(p) = microbit::Peripherals::take() {
        let gpio = p.GPIO.split();
        let mut led = gpio.pin13.into_push_pull_output();

        loop {
            let _ = led.set_low();
            let _ = led.set_high();

    loop {

Now you are set up to write actual code!