Prerequisite
- You have installed the Rust and Drone-OS toolchain as discussed in this previous post: Drone-OS toolchain on Ubuntu 18.04.
- You have one of these STM32 Nucleo-144 developer boards: NUCLEO-L496ZG, or the newer NUCLEO-L4R5ZI.
- You have a Black Magic Probe adapter. It is possible to flash the binary with the onboard ST-LINK, but the scripts in this demo project are made to work with the Black Magic Probe (BMP).
What is this demo good for?
We are going to write and debug a trivial LED-blink firmware targeted for the STM32L4 Cortex-M MCU. No GUI-based IDE, no cloud tools, just the beautifully transparent command-line environment of Drone-OS’ toolchain! As you will see in later posts: Drone-OS gives you access to the full power of the Cortex-M core and peripherals in a safe manner. Of course, it has its price: this developer environment does not hide massively the inherent complexity of the target hardware by means of high abstraction. In that respect, Drone-OS’ developer environment is much more bare-bones. You are challenged to read the hardware user guides and the hardware reference manuals of your target MCU!
Watch the smooth fade-in, fade-out of the LED lights. It’s coded in Rust!
Create the project
Create a workspace directory. Change into it and use cargo to create the standard project directory structure for a new project drone-os-toolchain-demo:
cargo new drone-os-toolchain-demo cd drone-os-toolchain-demo cargo run
Those commands create, compile and link a ‘Hello, world!’ application that runs on the local system. That’s nice but didn’t we want to write a firmware for the NUCLEO-144 and run it on our developer board? Let’s get the files of a complete demo firmware from Github instead.
Get the LED-blink Demo from Github
Go to your workspace directory and clone th project from public Github. Before you execute ‘git clone’, remove or rename any existing directory with the name drone-os-toolchain-demo.
git clone https://github.com/centosa/demo-core-nucleo.git
Let’s inspect
HINT: All
A word about src/main.rs
You will probably start at src/main.rs if you wonder how the source code of this project looks like, You will see those extern “C” parts in the code. Why “C”, not “Rust”? Well, this main module allocates some application binary interface (ABI) data structures and declares the functions for the board reset and startup. The “C” part defines how to call the function at the assembly level. The “C” ABI is supported by Cortex-M natively, so it is the appropriate selection in our coding for interfacing with the hardware core. Quoting:
“While Rust can call C functions without overhead, the Rust compiler still needs to know about the existence of those functions. To tell
The purpose of this short post is not to discuss the coding of the application targeted to the Nucleo-144 developer board. First, let’s build, flash and debug a completed project.
The project’s file structure
It is beyond the scope of this article to go into all the details of the project’s
HINT: The source files that are typically present in a Rust project do not reflect the application’s object model (e.g. classes) as it is the case in Java or C++. They rather represent bundles of related types, data, and logic that the developer thoughtfully groups into modules. Rust source files will usually be short and easy to navigate.
The module tree is a handy structural concept for visualizing a Cargo-based Rust project. The module tree of the small firmware crate that is the subject of this post looks like this:
drone-os-toolchain-demo └── main.rs // ABI ├── reset └── start_trunk └── lib.rs // Declaring library modules, coded in Drone-OS libs ├── periph // MCU peripherals configuration │ └── src/periph/mod.rs // Declares peripheral modules used in this project │ └── flash │ │ └── src/periph/flash.rs // MCU flash configuration │ └── lse │ │ └── src/periph/lse.rs // Low speed ext. resonator configuration │ └── msi │ │ └── src/periph/msi.rs // Multispeed internal oscillator config. │ └── pll │ └── src/periph/pll.rs // Phase-locked loop clock configuration ├── drv // MCU peripherals setup │ └── src/periph/mod.rs // Declares peripheral modules used in this project │ └── flash │ │ └── src/periph/flash.rs // MCU flash setup │ │ └── new │ │ └── free │ │ └── init │ └── lse │ │ └── src/periph/lse.rs // Low speed ext. resonator setup │ │ └── new │ │ └── free │ │ └── init │ └── msi │ │ └── src/periph/msi.rs // Multispeed internal oscillator setup │ │ └── new │ │ └── free │ │ └── init │ └── pll │ └── src/periph/pll.rs // Phase-locked loop clock setup │ └── new │ └── free │ └── init ├── consts // Constants │ └── src/consts.rs // Declaration of constants used in this project ├── heap // The heap allocator │ └── src/heap.rs │ └── pools // Memory allocation for application's heap ├── reg // Memory-mapped registers │ └── src/reg.rs // Registration of MCU-type-specific token structure ├── sv // Supervisor │ └── src/sv.rs // Supervisor services setup └── thr // Interrupt driven threads │ └── src/thr/mod.rs // Declares the modules used in this project │ └── button │ └── src/thr/button.rs // Nucleo-144 board PUSH-BUTTON actions │ └── led │ └── src/thr/led.rs // Nucleo-144 LED button actions │ └── trunk │ └── src/thr/trunk.rs // Application configuration and setup thread.
In addition to the manually compiled module tree, there is the cargo doc generator. I will discuss the art of documenting Rust crates in a later post.
The build process
These installation and target configuration steps have to be executed only once before any new project can be built and flashed to the target. Assuming you are in the root directory of the project, run these shell commands:
rustup component add rust-src clippy rustfmt llvm-tools-preview && \ rustup target add i686-unknown-linux-musl thumbv7m-none-eabi thumbv7em-none-eabihf
The build process can now be started. In the project’s root directory, execute this shell command:
just build
As it is the first-time build, cargo will;
- Read the file rust-toolchain. If the indicated version of the toolchain is not already installed, it will automatically go and fetch it from the Rust repository.
- Read the file Cargo.toml and fetch and compile all external crates that are listed as dependencies.
- Cross-compile our own project and generate the binary file for the download to the target MCU.
The full compilation can take several minutes. Finally, you should see these lines:

The binary file can be found here (your workspace might have a different name):

There is this command if you want to look at the contents of the binary file, including the memory allocations. The command will only work after the cargo-
cargo install cargo-binutils just inspect
Connect the Debug Probe to the Nucleo board
I recommend using the official Black Magic Debug Probe hardware or a decent clone of it, and the most recent firmware version. Then you either disable the onboard ST-LINK as described in the user manual of the Nucleo board, or you cut off the ST-LINK portion (see section Cuttable PCB of the user manual). Carefully read the section about External power supply inputs. I wired the BMP and the Nucleo on a cut-out Nucleo for SWD debugging mode as follows:
BMP Pins Nucleo Headers Trace to MCU Pin ------------------------------------------------------ RST ............. CN11 pin 14 RESET TDI ............. not connected TDO ............. not connected TCK ............. CN11 pin 15 PA14* TMS ............. CN11 pin 13 PA13* UART RXD ........ CN7 pin 15 PB3 TPWR ............ CN11 pin 16 VDD MCU**
(*) PA13 and PA14 are shared with SWD signals connected to ST-LINK/V2-1. If ST-LINK part is not cut, it is not recommended to use them as I/O pins.
(**) 3.3 V on Nucleo-144
Flash the firmware
The shell command for flashing and starting the application is:
just flash
The LED on the Nucleo should now blink and the output on the console should look like this;

Inspect the ITM logging output
The source code of this demo project includes a println statement which regularly prints a counter value to the ITM logging channel. The output is sent to the debug adapter (the BMP) which forwards it to the host PC over USB. You can see the output with this shell command:
just swo
The listener

That is the result. So you got an impression how the Drone-OS and the Rust toolchain are applied for building, flashing and tracing your own firmware for a ARM Cortex-M. To conclude this post, I add this contemplative image of a much simpler toolchain 🙂

0 Comments