I am developing a secure, cloud-connected home monitoring system using the B-U585I-IOT02A development board, which contains an STM32U585 Arm Cortex-M33 microcontroller. The firmware is divided into a C bootloader and a Rust application. The bootloader occupies the first 256 KiB of internal flash and launches the Rust application from 0x08040000.

The Rust application will handle onboard sensor acquisition and later Wi-Fi, MQTT over TLS and AWS IoT Core communication. The bootloader-to-application handover has been verified on the hardware, and the current work is establishing peripheral access and UART output from Rust. Further details are available in my previous post about the secure STM32 home monitoring project with Rust.

This is a short post to get you familiar with some of the fundamental terminology that you will come across in a typical Rust project. By no means is this comprehensive, but it is enough to get you going.

The Rust application uses Cargo as its build and dependency-management system.

Installing Rust and Cargo on Ubuntu

Cargo is installed with the Rust toolchain through rustup. On Ubuntu, install curl if it is not already available:

sudo apt update
sudo apt install curl

Install Rust, Cargo and rustup using the official installer:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

The command can be broken down as follows:

  • curl downloads content from a URL.
  • --proto '=https' permits only HTTPS.
  • --tlsv1.2 requires TLS 1.2 or later.
  • -s hides the normal download progress.
  • -S displays an error if the download fails.
  • -f makes the command fail for HTTP server errors.
  • https://sh.rustup.rs provides the official rustup installer script.
  • | sh sends the downloaded script directly to the shell for execution.

The script installs the Rust toolchain for the current user, normally under:

~/.rustup/
~/.cargo/

The installation includes:

An infographic explaining Rust programming tools: rustup for managing toolchains, cargo for building and managing projects, and rustc for compiling Rust code.

It also configures ~/.cargo/bin for inclusion in the shell’s PATH. It does not normally require sudo or install Rust system-wide. This is the installation command provided by the official Rust documentation.

If you prefer to inspect the script before executing it:

curl --proto '=https' --tlsv1.2 -sSf \
https://sh.rustup.rs \
-o rustup-install.sh
less rustup-install.sh
sh rustup-install.sh

Follow the on-screen instructions and select the default installation.

Load the Cargo environment and verify the installation

source "$HOME/.cargo/env"
rustc --version
cargo --version

Install the Arm Cortex-M33 compilation target

For this STM32U5 application, install the Arm Cortex-M33 compilation target:

rustup target add thumbv8m.main-none-eabihf

Project manifest file Cargo.toml

Rust projects use Cargo.toml as the manifest for package information, dependencies and build configuration. For this project, Cargo.toml looks like this:

[package]
name = "stm32u5_application"
version = "0.1.0"
edition = "2024"
[dependencies]
cortex-m = "0.7.9"
cortex-m-rt = "0.7.6"
panic-halt = "1.0.0"
stm32u5 = { version = "0.16.0", default-features = false, features = ["stm32u585"] }

Packages

The [package] section identifies the name of the firmware application, its current version and the Rust edition used by the source code.

Dependencies

The [dependencies] section lists the crates required by the firmware:

  • cortex-m is a Rust library that provides Cortex-M processor support.
  • cortex-m-rt provides the embedded runtime, startup code and reset-handler entry point.
  • panic-halt provides a panic handler that halts execution when a Rust panic occurs.
  • stm32u5 provides register-level access to STM32U5 peripherals.

Crate

A crate is a Rust compilation unit. It can be a reusable library or an executable program. The crates listed under [dependencies] are external libraries that Cargo downloads, compiles and makes available to the application.

The stm32u585 feature selects support for the STM32U585 device used by the B-U585I-IOT02A board. Setting default-features to false prevents the crate’s default feature set from being enabled, while the required device feature is selected explicitly.

Cargo configuration file .cargo/config.toml

Cargo.toml describes the package and its dependencies. It does not describe how the package is built for a particular board. That is the role of .cargo/config.toml, which sits in a .cargo directory inside the application directory:

~/stm32u5_iot_project/application/
├── .cargo/
│ └── config.toml
├── Cargo.toml
└── src/

Cargo searches for this file in the current directory and each parent directory, so placing it alongside Cargo.toml applies it to every build run from the application directory. For this project it contains:

[build]
target = "thumbv8m.main-none-eabihf"
[target.thumbv8m.main-none-eabihf]
rustflags = [
"-C",
"link-arg=-Tlink.x",
]

The [build] section sets the default compilation target, so cargo build produces an Arm Cortex-M33 binary without --target being given on the command line. Without it, Cargo would build for the host machine.

The [target.thumbv8m.main-none-eabihf] section passes an argument to the linker for that target only. -T link.x selects the linker script supplied by cortex-m-rt, which places the vector table and sections in the correct order and reads the memory layout from the project’s memory.x file. The memory.x file is where the application’s flash origin of 0x08040000 and its length are declared.

The .cargo directory begins with a dot, so it is hidden by default. Use ls -a to confirm it is present.

Cargo Build

The application is built from the application directory:

cd ~/stm32u5_iot_project/application
cargo build

Cargo reads the manifest and uses the dependency versions recorded in Cargo.lock. The embedded target configuration selects thumbv8m.main-none-eabihf for the Arm Cortex-M33 processor, while the linker uses the project’s application memory map.

The development build produces the following Arm ELF executable:

target/thumbv8m.main-none-eabihf/debug/stm32u5_application

In C or C++ terms, Cargo.toml is broadly comparable to a CMakeLists.txt file combined with a dependency manifest. Cargo manages dependency resolution, compiler invocation and creation of the STM32 application executable.

Project resources In active development

Project Source Code

Source code, implementation notes and milestone checkpoints are maintained on GitHub. If you are interested in collaborating, in online training, or in access to the full project package, please get in touch.

github.com/vivekbhadra/stm32u5_iot_project



Part of the series
Secure STM32 home monitoring with Rust


STM32 IoT development board


Discover more from Tech For Talk

Subscribe to get the latest posts sent to your email.

Leave a Reply