This series of blog posts will take you through the steps needed to install a toolset for embedded development on Cortex-M boards and create a simple application using Rust. But first let’s look at the basic question: why?
Rust and its ecosystem
Rust as a programming language makes claims that are unheard of in our industry. Combining native compiled code with strong claims about memory safety and preventing data races is unique. But that is not enough to make a programming language productive for software development in general and embedded development in particular. To be productive you need a toolset that makes the entire edit-compile-run/debug cycle efficient.
Nowadays that means:
- a compiler with meaningful error messages,
- an IDE with syntax-highlighting, completion, re-factoring and integrated debugging,
- a built tool that knows how to build the mostly used targets by default,
- support for unit-testing, continuous integration and benchmarking,
- a dependency manager that makes it easy to integrate existing libraries into your code base
- and an ecosystem with a huge amount of open source libraries.
Today (November 2017) Rust can check 5 of the 6 bullets points above. For people coming from a background in programming languages like Java and C# the IDE support will still feel like we are living in the early 90s. Today, IDE’s like Visual Studio Code and IntelliJ IDEA (community edition) provide Rust plugins with basic completion, re-factoring and debugging support. The Rust community has recognized that ‘great IDE support’ is necessary for adoption of the language and is tracking its progress on https://areweideyet.com/.
Setting up the tools
This post is going to walk you through the necessary steps to get all the tools up and running. This post assumes a real operating system like Linux or OS X.
Step 1: Getting stuff to compile
In the edit-compile-run/debug cycle we will first focus on ‘compile’.
First: the rust toolchain manager, rustup. Go to https://rustup.rs/ and install rustup. This will install a series of tools:
- rustup, the toolchain manager,
- the default stable compiler for your platform,
- the built-tool cargo,
- the API documentation and
- the sources for the standard libraries
Check your installation with the
rustup show command. My output is:
Default host: x86_64-apple-darwin stable-x86_64-apple-darwin (default) rustc 1.22.1 (05e2e1c41 2017-11-22)
The nightly compiler and Rust sources
For embedded development, you currently need features that are not yet stabilised and are therefore not available in the stable compiler. Execute
rustup toolchain add nightly to add the nightly compiler and
rustup component add rust-src to add the Rust sources. Again, use
rustup show to check your installation:
Default host: x86_64-apple-darwin installed toolchains -------------------- stable-x86_64-apple-darwin nightly-x86_64-apple-darwin active toolchain ---------------- stable-x86_64-apple-darwin (default) rustc 1.22.1 (05e2e1c41 2017-11-22)
rustup component list to check the installed components:
>>SNIP<< rust-docs-x86_64-apple-darwin (default) rust-src (installed) >>SNIP<<
Staying up to date
Keeping your compilers up to date is simple:
rustup update and wait. You can pick (and pin) specific versions of the compiler using rustup. Example:
rustup toolchain add 1.20.
Installing the ARM gcc toolchain
Rust’s compiler will use the platform native linker to link to final binaries. For ARM on embedded that is arm-none-eabi-ld. Head to https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads and install the latest ARM toolchain for your platform. Making the binaries available in your
$PATH makes life a lot easier.
Cross-compiling using Xargo
Xargo is a cross-compiling tool for Rust. Xargo is a wrapper around cargo that handles compiling Rust’s core libraries for your target. Installing Xargo is as simple as executing
cargo install xargo in your shell.
Step 2: Running your code on the board
Getting your code to run on the board, means getting it on the board first. This post assumes that you are using the BlackMagicProbe/Bluepill to connect to your target board. If that is the case you need to tell the debugger, arm-none-eabi-gdb to use the BlackMagicProbe using the USB-serial connection. That means, entering this at the GDB prompt:
target extended-remote /dev/ENTER_YOUR_DEVICE_HERE
There is a tool available that scans your system for available debuggers. It is called bobbin-cli and can simply be installed using Rust’s build tool cargo:
cargo install bobbin-cli. This will download, compile and install bobbin-cli using your default compiler.
Bobbin needs dfu-util to work, so on Linux do
sudo apt install dfu-util (or equivalent), on a Mac do:
brew install dfu-util or equivalent. Run
bobbin check to see what tools it finds. My output:
Bobbin 0.8.0 Rust 1.23.0-nightly (246a6d19c 2017-11-23) Cargo 0.24.0-nightly (abd137ad1 2017-11-12) Xargo 0.3.8 GCC 6.3.1 20170215 (release) [ARM/embedded-6-branch revision 245512] OpenOCD Not Found JLink Not Found Bossa Not Found Teensy Not Found dfu-util 0.9
Finding your board
Now connect your debugger board and execute:
bobbin list. On my system this shows:
ID VID:PID Vendor / Product Serial Number 2700e45b 1d50:6018 Black Sphere Technologies / Black Magic Probe (Bluepill), (Firmware v1.6-rc0-338-g798d883) C4D6A8F0
bobbin info shows on what device the debugger ports are available:
ID 2700e45bdf2fa10f6dbe1cb90d3894c8679762f2 Vendor ID 1d50 Product ID 6018 Vendor Black Sphere Technologies Product Black Magic Probe (Bluepill), (Firmware v1.6-rc0-338-g798d883) Serial Number C4D6A8F0 Type BlackMagicProbe Loader Type blackmagic Debugger Type blackmagic CDC Device /dev/cu.usbmodemC4D6A8F3 GDB Device /dev/cu.usbmodemC4D6A8F1
Unsurprisingly, to debug we use the GDB Device.
In the next post we will be creating some example code and running it.