👨‍💻blog + programming

Getting started with rust on the stm32f411 'black pill' + fibonacci

Jack Everett Fletcher November 22, 2021 [Learning] #rust

Guide to setup stm32f411 + fibonacci program

intro

This is just a helpful resource to setup programming your stm32f411 with Rust.

Generate your project

Follow along closely with the README from the knurling app-template here.

After installing the recommended tools and generating your project, you'll have to change some things specific to the stm32f411. You may either do it yourself (it's not too hard) or follow along below. ⬇️

Change .cargo/config.toml

You'll need to set the runner to be runner = "probe-run --chip stm32f411".

You also should set the target to take advantage of the capabilities of the board. Set your target such that target = "thumbv7em-none-eabihf"

Your .cargo/config.toml should look like this:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output)
runner = "probe-run --chip stm32f411"
rustflags = [
    "-C", "linker=flip-link",
    "-C", "link-arg=-Tlink.x",
    "-C", "link-arg=-Tdefmt.x",
    # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
    # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
    "-C", "link-arg=--nmagic",
]

[build]
# TODO(3) Adjust the compilation target.
# (`thumbv6m-*` is compatible with all ARM Cortex-M chips but using the right
# target improves performance)
#target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

[alias]
rb = "run --bin"
rrb = "run --release --bin"

You'll likely have to install the desired toolchain. Run rustup target add thumbv7em-none-eabihf to get it.

Change Cargo.toml

You'll have to change # some-hal = "1.2.3" to instead use stm32f4xx-hal.

🚨NOTE🚨 At the time of writing, the latest published version stm32f4xx-hal = "0.10.1" does NOT support the latest cortex-m-rt = "0.7.0", only 0.6.15.

Until they publish a new version on crates.io, I recommend you point that dependency to their github, and lock into a specific commit. It'll look something like this:

[dependencies.stm32f4xx-hal]
git = "https://github.com/stm32-rs/stm32f4xx-hal"
rev = "7ac6eff758d7159e53c557db4c24474edfbd42bc"
features = ["rt", "stm32f411"]

Your full Cargo.toml will look similar to the following:

[package]
# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate`
authors = ["Your Name <your_name@email.com>"]
name = "generated-stm32f411-fun"
edition = "2018"
version = "0.1.0"

[workspace]
members = ["testsuite"]

[dependencies]
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
defmt = "0.3.0"
defmt-rtt = "0.3.0"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }

[dependencies.stm32f4xx-hal]
git = "https://github.com/stm32-rs/stm32f4xx-hal"
rev = "7ac6eff758d7159e53c557db4c24474edfbd42bc"
features = ["rt", "stm32f411"]

# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 3 # <-
overflow-checks = true # <-

# cargo test
[profile.test]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 3 # <-
overflow-checks = true # <-

# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-

# cargo test --release
[profile.bench]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-

# uncomment this to switch from the crates.io version of defmt to its git version
# check app-template's README for instructions
# [patch.crates-io]
# defmt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
# defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
# defmt-test = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
# panic-probe = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }

Change src/lib

Your project should use the right hal package. Update use some_hal as _; to use stm32f4xx_hal as _;

Your src/lib.rs should look like this:

#![no_std]

use defmt_rtt as _; // global logger

// TODO(5) adjust HAL import
use stm32f4xx_hal as _; // memory layout

use panic_probe as _;

// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
#[defmt::panic_handler]
fn panic() -> ! {
    cortex_m::asm::udf()
}

/// Terminates the application and makes `probe-run` exit with exit-code = 0
pub fn exit() -> ! {
    loop {
        cortex_m::asm::bkpt();
    }
}

Add memory.x

On the stm32f411, your memory.x should be placed at the root of your project. It'll look like this:

MEMORY
{
  /* NOTE K = KiBi = 1024 bytes */
  FLASH : ORIGIN = 0x08000000, LENGTH = 512K
  RAM : ORIGIN = 0x20000000, LENGTH = 128K
}

/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

Debugger setup

Both st-link and stm32f411 in one pic: both

code 🦀

Below is the full file. Create src/bin/fib_fun.rs You can run it with cargo run fib_fun

src/bin/fib_fun.rs

#![no_main]
#![no_std]

fn fib(n: u32) -> u32 {
    if n <= 1 {
        return n;
    }
    fib(n - 1) + fib(n - 2)
}

#[cortex_m_rt::entry]
fn main() -> ! {
    // create a mutable array of full of 0's of type u8, with 255 elements.
    // note that the size of the array, 255, is apart the actual type of the nums variable
    let mut nums = [0u8; 255];

    // update array to be consecutive integers from 0 to 254
    (0..255).enumerate().for_each(|(i, elem)| nums[i] = elem);
    
    defmt::println!("{}", nums);

    nums.iter()
        .enumerate()
        .for_each(|(i, elem)| defmt::println!("Iteration: {}\nValue: {}", i, fib(*elem as u32)));

    // this is just the project name, change yours appropriately
    generated_stm32f411_fun::exit()
}

Interesting note about the below snippet:

(0..255).enumerate().for_each(|(i, elem)| nums[i] = elem);

Why didn't we just skip the .enumerate() call, and write the following?

(0u8..255).for_each(|elem| nums[elem] = elem);

The index of a slice must absolutely be of type usize; the program won't compile if it's anything else. You'd get the following error if you tried:

error[E0277]: the type `[u8]` cannot be indexed by `u8`
  --> src/bin/fib_fun.rs:17:9
   |
17 |         nums[elem] = elem;
   |         ^^^^^^^^^^ slice indices are of type `usize` or ranges of `usize`
   |
   = help: the trait `SliceIndex<[u8]>` is not implemented for `u8`
   = note: required because of the requirements on the impl of `Index<u8>` for `[u8]`

Back to top