Environment Setup for STM Developement in Rust

Wu Yu Wei published on
6 min, 1029 words

STM32 is the recommended choice to start with Embedded System Programming in Rust consider there are many supported libraries for Cortex-m series and STM targets and hals among the ecosystem. If you are already familiar with programing with them, all you have to do is add a few config files in your cargo projects and it's done. If not, fear not then. This post will list all the tools and environment you have to setup. I'm going to use STM32F3DISCOVERY as an example but other STM series should follow similar steps.

Tooling

First of all, we need to install some tools if you didn't have any embedded setup on your computer before. The common choice for our debuggers are usually GDB with OpenOCD. minicom may be helpful if you want to also try out some serail module. The GDB you have to install may be different depends on your distribution. On Ubuntu/Debian, it will be gdb-multiarch:

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

While others like arch linux will use arm-none-eabi-gdb:

$ sudo pacman -S \
  arm-none-eabi-gdb \
  minicom \
  openocd

If you want to communicate with bluetooth, there are some more packages recommended to install: bluez, bluez-utils, rfkill.

udev rules

Next, we want to set some rules to let us use those devices directly without root privilege sudo. Here's the example for STM32F3:

$ cat /etc/udev/rules.d/99-ftdi.rules:
# FT232 - USB <-> Serial Converter
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE:="0666"

$ cat /etc/udev/rules.d/99-openocd.rules
# STM32F3DISCOVERY rev A/B - ST-LINK/V2
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666"

# STM32F3DISCOVERY rev C+ - ST-LINK/V2-1
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"

Then reload the rules:

$ sudo udevadm control --reload-rules

Finally, let's plug the device and verify the permissions:

$ lsusb | grep -i stm
Bus 001 Device 006: ID 0483:374b STMicroelectronics ST-LINK/V2.1

This means my device is connected to Bus 001 and enumerated as Device 006. Let's check the permissions according to the number we have:

ls -l /dev/bus/usb/001/006
crw-rw-rw-+ 1 root root 189, 5 Jun 30 17:31 /dev/bus/usb/001/006

We should see that current user does have read/write permissions as crw-rw-rw- shows.

OpenOCD connection

Once we install the packages, we could start to connect to our device using OpenOCD. The cfg file might be different depends on your device. But if everything works as planned, you should see something like following:

$ openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 2.910427
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints

You should see it says how many breakpoints and watchpoints are there.

ITM

For ITM protocol, there's itmdump we could install from cargo directly:

$ cargo install itm

And then make sure you start the tool in the same directory of where you start OpenOCD:

$ touch itm.txt && itmdump -F -f itm.txt

Other LLVM tools

If you also want to use some LLVM tools like objdump, size and more. There's also a cargo subcommand already:

$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview

Then you could use thos tools like this:

```bash
$ cargo objdump --target thumbv7em-none-eabihf --bin app --release -- -disassemble -no-show-raw-insn
$ cargo size --target thumbv7em-none-eabihf --bin app --release -- -A -x
...

Rust Toolchains & Configurations

After installing packages that we needs for general embedded development, we can now start tweaking our rust related stuffs. Like other projects we do in Rust, we usually create a cargo project since it can integrate several important build tools especially toolchains when we want to do cross compilation. For STM boards, or Arm Cortex-m architectures more precisely, here are the targets we could choose:

  • Cortex-M0, M0+, and M1 (ARMv6-M architecture): thumbv6m-none-eabi
  • Cortex-M3 (ARMv7-M architecture): thumbv7m-none-eabi
  • Cortex-M4 and M7 without hardware floating point (ARMv7E-M architecture): thumbv7em-none-eabi
  • Cortex-M4F and M7F with hardware floating point (ARMv7E-M architecture): thumbv7em-none-eabihf

To install the toolchain, simply run:

$ rustup target add thumbv7em-none-eabihf

Config files

Once toolchain is installed, we can cross compile the project to specific target using cargo build --target. But this will become tedious and there are more steps need to do when you entering gdb. So instead of typing these command manually each time, we could setup config files in the cargo project like this.

.cargo/config:

[target.thumbv7em-none-eabihf]
runner = "arm-none-eabi-gdb -q -x openocd.gdb"
rustflags = [
  "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv7em-none-eabihf"

openocd.gdb:

target remote :3333
set print asm-demangle on
set print pretty on
monitor tpiu config internal itm.txt uart off 8000000
monitor itm port 0 on
load
break main
continue

This means we will build the project to specific target thumbv7em-none-eabihf and then run with arm-none-eabi-gdb with gdb config file openocd.gdb. Then we will just need to use cargo run to enter the gdb and see how it goes!

More References

One of great reasons to develop embedded system in Rust is always its community. There are plenty of resources you can find in the ecosystem. Here are some I think are useful and might help you too.