Embedded-Rust: Let’s start with STM32F3DISCOVERY

Knoldus Blog Audio
Reading Time: 5 minutes

An embedded system is computer hardware with software embedded in it. Or we can say it is a combination of computer processors, computer memory, and input/output devices and it can be an independent system or a part of a large system. In other words, it is a micro-controller or micro-processor-based system which is designed to perform a specific task.

Hi folks, I hope you all are doing well on making yourself up-to-date with the emerging technologies, similarly to add one more interesting row in our checklist we’re gonna talk about (setting up the environment and building our first program) in STM32F3Discovery broad.

If you guys are not aware of STM32F3Discovery so I’d request you get some info about this board before proceeding with the article.

Alright, as we’re creating a binary for a bare metal, we need to keep few things in our mind such as:

  • setting up an environment for discovery board,
  • working on no_main & no_std environment,
  • providing custom panic_handler, lang_items & entry point,
  • building binary

Now, let’s achieve our target by following above steps:

Setting-up our environement

To work on the Discovery board we need to install a few components like (itmdump, cargo-builtins, gdb-multiarch, etc). Here we’ll not gonna talk about each component as this article pertains to building our binary for a STM32F3Discovery board.

We need to run these command to set our environment

Install itmdump

cargo install itm

Install cargo-builtins

rustup component add llvm-tools-preview // adding component
cargo install cargo-binutils // instaling builtins

Components which helps for debugging

sudo apt-get install \
  gdb-multiarch \  minicom \

Setting udev rules to ease development by getting root privileges

// to set these rules we need idVendor and idProduct
// to get idVendor and idProduct use
lsusb | grep ST-LINK
// output: Bus 001 Device 006: ID 0483:374b STMicroelectronics ST-LINK/V2.1 (Nucleo-F103RB)
// here idvendor is 0483 & idProduct is 374b

// create rules
sudo nano /etc/udev/rules.d/99-openocd.rules
// add this content:
// ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"

// reload rules
sudo udevadm control --reload-rule

Now we’ve set-upped our environment, so let’s move to our next step which is working on no_main & no_std environment.

no_main & no_std environment

As we are working on a hardware level it means we don’t get the required resources. So in order to work on these types of environment, we need to maintain our environment where are not dependent on the standard library and predefined entry point.
To achieve this environment we need to incorporate two attributes:

  • #![no_main] – used to disable the default entry points
  • #![no_std] – used to disable the standard library

So our program will look like this:


fn main() {
    println!("Hello world");

As we are dealing without a standard library and entry point, now we need to provide some required components like if we run our basic program with attributes we just talked about, we’ll get this type of error:

Let’s provide the required components to the compiler.

Provide required components(panic_handler, lang_item, & entry_point)

First of all, we’ll provide our entry point which we have disabled using no_main attribute then we’ll provide other required components.
Previously we need to add a definition of start lang_item to provide our entry point as the execution starts from C runtime but now we don’t need to worry about this because we have a crate present in our crate’s registry named cortex_m_rt which defines a custom entry point so we just need to provide #[entry] attribute to define entry point.
To get the crate we need to add it to our Cargo.toml file:

// Cargo.toml

cortex-m-rt = "0.6.13"

And main.rs will look like this:


use cortex_m_rt::entry;

fn main() -> ! {

    loop {}

The entry point function must have a signature fn() -> !, and this return type indicates that the function can’t return, use of loop {} means that the program never terminates.

Subsequently, we need to provide panic_handler and eh_personality lang_item. Moreover, if you want to know about these components please refer to this article.

Again we have two options to provide panic_handler, either we define by our own or we’ll use a predefined one, in order to make it short we’ll go with a predefined one.

panic_itm is the crate that provides both panic_handler and eh_personality. So let’s import this crate in our Cargo.toml

// Cargo.toml

panic-itm = "0.4.2"

And main.rs will look like this:


// panic handler
pub use panic_itm;
use cortex_m_rt::entry;

fn main() -> ! {

    loop {}

Build binary

Alright, now we are ready to build our binary and to build for the Discovery board we need to cross-compile the binary. Before cross-compiling let me tell you the Discovery board has Cortex-M4F processor in it, and rustc provide four different targets that cover the different processor families within that architecture, here is the list of targets that are available: (thumbv6m-none-eabi, thumbv7m-none-eabi, thumbv7em-none-eabi, & thumbv7em-none-eabihf).

We’ll use thumbv7em-none-eabihf which is for Cortex-M4F and Cortex-M7F processors, and before cross-compiling we need to download a pre-compiled version of standard library for this target.
Here is the command to download:

rustup target add thumbv7em-none-eabihf

After adding downloading the pre-compiled version we are ready to build our binary for this target, now we just need to hit cargo build by specifying the target using target option.

cargo build --target thumbv7em-none-eabihf

Or we can configure our target in the .cargo/Config.toml file so that we don’t need to specify target every time.

After success build we got result like this:

The binary is cross-compiled for the desired target and as a result, we will get the binary here: target/thumbv7em-none-eabihf/debug/stm32.

Now let’s verify our binary whether it is ready to flash in the board or not? And to verify the binary we use cargo readobj of cargo-binutils.

cargo readobj --target thumbv7em-none-eabihf --bin stm32 -- --file-header

cargo readobj is similar to readelf, here we are providing a target, binary name, and option file-header to get the headers.
The output we got from this command is:

We got all the headers of the binary but the most important header we need to look at here is Entry point address.
Entry point address denotes the starting point’s address, means from where the binary/process starts its execution, and in our binary the value of this header is 0x0 which means our binary has no starting point’s address.

No worries in the next article, we’ll look into this issue and try to flash our binary with a valid Entry point address header.

Thanks for reading!!!


If you want to read more content like this?  Subscribe Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7 .


Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than a year in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute towards the community which he attained from the community.

Leave a Reply