Rust bare metal on ARM microcontroller
Recently at the CVRA we decided to rewrite one important external library writen in C++. We wanted to rewrite in C, but since Rust recently hit beta I wanted to see if it was feasible to use it for our application. To do this, I decided to write a little demo application using Rust on a Texas Instruments Tiva Launchpad dev board.
Install native rustc
Nothing special here, just don’t use the beta version, as we will use some unstable features. I installed latest Rust nightly on OSX via the following commands:
Download rust target description
This file is needed to tell Rust/LLVM about our Cortex M4 CPU, it’s pointer size and so on. We will use the one from the Zync project:
Building libcore
Libcore provides the most fundamental data types and functions supported by Rust.
You can read more about it in the documentation.
Since it is really the foundation of the compiler’s result, they must have the same version, down to the commit.
You can check which version of rustc
you have on your computer by running rustc --version -v
and looking for the commit-hash field.
First download the correct version of Rust’s source code:
Then we can build libcore using the target description we used before.
Provide needed runtime
For this little example I will reuse the runtime I build for my Tivaware template before. We will only need a few more functions needed by the Rust runtime, mostly for panic functions.
Put the following in runtime.rs
:
You can now try compiling this first Rust file for ARM by running the following commands:
We can now add Rust support to the Makefile.
This is just adding a rule to make .o
object file from .rs
source using the Rust compiler with the above flags.
You can see how I did it in my commit.
Note: We don’t need to compile each of our Rust file on it’s own.
All I had to do was build main.rs
and “include” the other files with mod
directives.
This was a bit surprising at first and caused me quite some problems.
Porting our main function to Rust
My objective was reimplementing the “Hello, world” of embedded systems: blinking a LED. Basically this requires three things:
- Clock setup.
- General Purpose Input / Output setup.
- GPIO writing in a main loop.
All of this is usually done using either direct register access or a library. As I said I am interested by Rust’s compatibility with C, so I decided to use Texas Instruments’ Tivaware, which is a basic library to deal with low level hardware settings.
I only wrote bindings for what I used, since I probably won’t use Tivaware for my more “real” projects (we don’t use Texas Instruments chips, but STM32).
Sysctl binding
The SysCtl
subsystem as its name implies is related to system control, like clock, peripherals and interrupts.
For this project I only need two Tivaware functions, SysCtlClockSet
to configure system oscillator and SysCtlPeripheralEnable
to enable the GPIO port on which my LED is connected.
I will also need a few constants.
My particular board has an external crystal at 16 Mhz. I will use the PLL to rise it up to 400 Mhz, then divide it by 5 to have 80 Mhz, which is the max speed of this particular microcontroller.
From my previous article on Tivaware, I knew that this required the following constants: SYSCTL_SYSDIV_2_5
, SYSCTL_USE_PLL
, SYSCTL_XTAL_16MHZ
and SYSCTL_OSC_MAIN
.
We can get the value of those constants by opening Tivaware’s sysctl.h
.
Since we are here, we will also lookup the value of SYSCTL_PERIPH_GPIOF
, which we will need to turn on the LED.
We can put them as public constants in sysctl.rs
:
The functions binding are not complicated.
You just declare them as extern
and you can then call them from unsafe blocks.
I will also write safe wrappers around them for ease of use.
Since Rust functions are module private by default, we don’t have to worry about someone directly using the C functions incorrectly.
GPIO bindings
I applied the same process as before, but later reworked it to have a bit more type safety.
By using u32
directly like in the sysctl module, we gain little to nothing over C, so I decided that for the GPIO driver I will use custom types for ports and pins.
I will just put the different constants in enumerations and use those enumerations in my public API.
Here is the code:
Putting it together
First we write a simple LED driver to hide the hardware details from main. This driver needs to configure a given pin as output, enable the port on which the led is connected and provide functions to set the LED state.
And finally the main, which just a loop doing busy wait. I had to add a few statements to remove the standard library and allow bare metal Rust (mostly taken from my references).
Just run make all load
and the LED will start blinking! The complete project is available on Github.
What’s next ?
This was just a basic experiment with Rust and ARM microcontrollers. I started working on interrupts and bindings to ChibiOS. I also would like to design safer APIs instead of simply translating the code to Rust.
Update: A friend of mine @nuft has done this work.
It is now possible to write ChibiOS threads in Rust.
His work can be found in the chibi-rust
branch of the repository.
Sources:
- http://spin.atomicobject.com/2015/02/20/rust-language-c-embedded/
- https://doc.rust-lang.org/core/
- My previous post about Stellaris
- StackOverflow, as always