New Project
$ cargo new microrust-start
Created binary (application) `microrust-start` project
$ cd microrust-start
Cargo.toml src
This has created a binary crate.
Now we could $ cargo build
this, and even $ cargo run
it,
but everything is being compiled for, and run on, your computer.
Targets
The micro:bit has a different architecture than your computer, so the first step will be to cross compile for the micro:bit's architecture. If you were to do an internet search, you would find a platform support list for Rust. Looking into this page, you will find the micro:bit's nRF51822 Cortex-M0 microprocessor:
thumbv6m-none-eabi [*] [ ] [ ] Bare Cortex-M0, M0+, M1
"thumbv6m-none-eabi" is known a a target triple. Note what the star represents:
These are bare-metal microcontroller targets that only have access to the core library, not std.
To install this target:
$ rustup target add thumbv6m-none-eabi
Build 1
Now how should we use this? Well, if you were to take a look at $ cargo build -h
, you would try:
$ cargo build --target thumbv6m-none-eabi
error[E0463]: can't find crate for `std`
|
= note: the `thumbv6m-none-eabi` target may not be installed
error: aborting due to previous error
For more information about this error, try `rustc --explain E0463`.
error: Could not compile `microrust-start`.
To learn more, run the command again with --verbose.
The help note is rather unhelpful because we just installed that target. We also just noted that the thumbv6m-none-eabi target does not include std, only the core crate, which is has a platform independent subset of the std features. Why is it still looking for the std crate when we build?
no_std
It turns out, rust will always look for the std crate unless explicitly disabled, so we will add the no_std attribute
src/main.rs
#![no_std] fn main() { println!("Hello, world!"); }
Build 2
$ cargo build --target thumbv6m-none-eabi
error: cannot find macro `println!` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
println
is a macro found in the std crate.
We don't need it at the moment, so we can remove it and try to build again.
Build 3
$ cargo build --target thumbv6m-none-eabi
error: `#[panic_handler]` function required, but not found
This error, is because rustc required a panic handler to be implemented.
panic_impl
We could try and implement the panic macro ourselves, but it's easier and more portable to use a crate that does it for us.
If we look on crates.io for the panic-impl keyword we will find some examples. Let us pic the simplest one, and add it to our Cargo.toml. If you have forgotten how to do this, try looking at the cargo book.
Cargo.toml
[dependencies]
panic-halt = "~0.2"
src/main.rs
#![no_std] extern crate panic_halt; fn main() { }
Build 4
$ cargo build --target thumbv6m-none-eabi
error: requires `start` lang_item
no_main
In the normal command line rust binaries you would be used to making,
executing the binary usually has the operating system start by executing the C runtime library (crt0).
This in turn invokes the Rust runtime, as marked by the start
language item,
which in turn invokes the main function.
Having enabled no_std
, as we are targeting on a microcontroller,
neither the crt0 nor the rust runtime are available,
so even implementing start
would not help us.
We need to replace the operating system entry point.
You could for example name a function after the default entry point,
which for linux is _start
, and start that way.
Note, you would also need to disable name mangling:
# #![allow(unused_variables)] #![no_std] #![no_main] #fn main() { #[no_mangle] pub extern "C" fn _start() -> ! { loop {} } #}
This is the end of the road for trying to get this to work on our own. At this point we need the help of a board-specific support crate and a few cargo tweaks to get this working.
microbit crate
Let us add a dependency on the board crate for the micro:bit.
[dependencies]
panic-halt = "~0.2"
microbit="~0.7"
The microbit crate has 2 notable dependencies:
embedded-hal
This crate is a HAL implementation crate, where HAL stands for hardware abstraction layer. As rust becomes more and more popular in embedded development, it is desireable to have as little hardware specific implementation as possible.
For this reason, the embedded-hal
crate contains a range of hardware abstraction traits which can
be implemented by board specific crates.
cortex-m-rt
This crate implements the minimal startup / runtime for Cortex-M microcontrollers. Among other things this crate provides:
- the
#[entry]
attribute, to define the entry point of the program. - a definition of the hard fault handler
- a definition of the default exception handler
This crate requires:
- a definition of the specific microcontroller's memory layout as a memory.x file. fortunately this is usually provided by the board support crates
To use the #[entry]
attribute, we will need to add this as a dependency.
For more detailed information, you can use the helpful cortex-m-quickstart crate and its documentation.
cargo config
Before we go any further,
we are going to tweak the cargo's configuration by editing microrust-start/.cargo/config
.
For more information, you can read the documentation here.
.cargo/config
# Configure builds for our target, the micro:bit's architecture
[target.thumbv6m-none-eabi]
# Execute binary using gdb when calling cargo run
runner = "arm-none-eabi-gdb"
# Tweak to the linking process required by the cortex-m-rt crate
rustflags = [
"-C", "link-arg=-Tlink.x",
# The LLD linker is selected by default
#"-C", "linker=arm-none-eabi-ld",
]
# Automatically select this target when cargo building this project
[build]
target = "thumbv6m-none-eabi"
arm-none-eabi-gdb
This is a version of gdb (the GNU debugger) for the ARM EABI (embedded application binary interface). It will allow us to debug the code running on our micro:bit, from your computer.
Build target
Now, all you need to do is run $ cargo build
,
and cargo will automatically add --target thumbv6m-none-eabi
.
Build 5
Cargo.toml
[dependencies]
panic-halt = "~0.2"
microbit="~0.7"
cortex-m-rt="~0.6"
src/main.rs
#![no_std] #![no_main] extern crate panic_halt; use cortex_m_rt::entry; #[entry] fn main() { }
$ cargo build
error: custom attribute panicked
--> src/main.rs:7:1
|
7 | #[entry]
| ^^^^^^^^
|
= help: message: `#[entry]` function must have signature `[unsafe] fn() -> !`
!
return type
A little known rust feature, so I will forgive you if you do not know what this means.
A return type of !
means that the function cannot return.
An easy way to implement this is to use an infinite loop.
src/main.rs
#![no_std] #![no_main] extern crate panic_halt; use cortex_m_rt::entry; #[entry] fn main() -> ! { loop {} }
Build 6
If you try building now, you should finally be greeted with Finished
!
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Build Complete
As a sanity check, let's verify that the produced executable is actually an ARM binary:
$ file target/thumbv6m-none-eabi/debug/microrust-start
target/thumbv6m-none-eabi/debug/microrust-start: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
^^^ ^^^^^