Configuring the toolchain

We will ensure that we are able to compile and link an empty program, and upload it to the board.

Choosing the target for the library

Our board uses a STM32L475VGT6 microcontroller which contains a Cortex-M4F core. We need to download the corresponding target so that Rust can cross-compile for it.

❎ Add the thumbv7em-none-eabihf target using rustup:

$ rustup target add thumbv7em-none-eabihf

We will build our code for this target by default, this can be configured in .cargo/config.toml as this is specific to our build process.

❎ Create .cargo/config.toml and configure the default target to use when compiling:

[build]
target = "thumbv7em-none-eabihf" # Cortex-M4F/M7F (with FPU)

❎ Check that Rust can cross-compile your current code by using cargo build. You will notice a target/thumbv7em-none-eabihf directory which contains the build artifacts.

Choosing the runtime support package to build an executable

We are not able to build a library, but we do not have an executable program yet. In order to do this, we will need to provide:

  • a linker script
  • the linker arguments
  • a main program
  • an implementation of the panic handler so that Rust knows what to do if there is a panic

Linker script and linker arguments

We could write a whole linker script as was done in the 4SE07 lab, but this is not necessary. The cortex-m-rt crate provides a linker script as well as a #[entry] attribute and builds a complete program for Cortex-M based microcontrollers, including a vector table.

The linker script is named link.x and will be placed in the linker search path. However, this script includes a memory.x which describes the memory regions, and we will have to provide this linker script fragment and place it at the right place.

❎ Add a dependency to the cortex-m-rt crate.

❎ Write a memory.x file, next to Cargo.toml, containing:

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 1M
  RAM   : ORIGIN = 0x20000000, LENGTH = 96K
}

We must tell the linker to use the link.x script provided by the cortex-m-rt crate when compiling for arm-none-….

❎ Add the following conditional section to the .cargo/config.toml file:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = ["-C", "link-arg=-Tlink.x"]

Peripheral access crate

The only thing missing that cortex-m-rt link scripts will require is a vector table. Since this depends on our device, we must add one for the STM32L475VGT6 microcontroller by importing a peripheral access crate (PAC).

❎ Add embassy-stm32 as a dependency with the feature stm32l475vg. ❎ The crate embassy-stm32 requires a way to define a critical section. You can define one by adding the cortex-m crate with the critical-section-single-core feature as a dependency.

Main program

We want to have an executable program named tp-led-matrix, located in src/main.rs. While a crate may contain only one library, it may contain several executables. The TOML syntax to describe an element of a list is to use a double bracket.

❎ Give the name of the executable for Cargo in your Cargo.toml:

[[bin]]
name = "tp-led-matrix"

Now, we can write our main program. We will run in no_std mode, and with no_main. We will use cortex-m-rt's entry attribute to define what our entry point should be. This entry point must never return, hence the use of the ! (never) type.

❎ Create src/main.rs with this code in it:

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use embassy_stm32 as _;   // Just to link it in the executable (it provides the vector table)

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

#[entry]
fn main() -> ! {
    panic!("The program stopped");
}

Note that we need to define a panic handler, because otherwise Rust doesn't know what to do in case of a panic. You can either define one yourself as we did there, or use and import a crate such as panic-halt which also does an infinite loop.

Program building

❎ Build the program in both debug and release modes using cargo build and cargo build --release.

❎ Look at the executable size with the arm-none-eabi-size program. The executables for the debug and the release modes are stored respectively in target/thumbv7em-none-eabihf/debug/tp-led-matrix and target/thumbv7em-none-eabihf/release/tp-led-matrix.

❎ Stop hurting yourself trying to type long path names, and use cargo size and cargo size --release instead. This tool builds the right version (debug or release mode) and calls size on it.

Documentation

The PAC and HAL crates have been written with many microcontrollers in mind. For example, the embassy-stm32 crate supports several microcontrollers from the STM32L4 family, including our STM32L475VGT6. Features flags will cause the use of macros to generate the various methods and modules appropriate for the targeted microcontroller.

However, this makes the online documentation unsuitable for proper use: only the generic methods and modules will be included, and it will be hard to find help on a functionality which is specific to our microcontrollers. Fortunately, cargo doc can generate documentation according to our crate dependencies and their feature flags.

❎ Generate the documentation using cargo doc.

❎ Open the file target/thumbv7em-none-eabihf/doc/tp_led_matrix/index.html in your browser, for example by using firefox target/thumbv7em-none-eabihf/doc/tp_led_matrix/index.html, and search for a method (for example gamma_correct).

You should regenerate the documentation every time you update your dependencies and when you significantly update your code, by rerunning cargo doc. It will only regenerate the documentation for things that have changed.