As of 2018-09-18 the Rust compiler supports cross compiling to these embedded
architectures (see rustup target list
):
- ARM Cortex-M (since 1.27)
thumbv6m-none-eabi
, Cortex-M0thumbv7m-none-eabi
, Cortex-M3thumbv7em-none-eabi
, Cortex-M4 and Cortex-M7thumbv7em-none-eabihf
, Cortex-M4F and Cortex-M7F
- ARM Cortex-R (1.30-beta)
armebv7r-none-eabi
, big endian Cortex-R4 and Cortex-R5armebv7r-none-eabihf
, big endian Cortex-R4F and Cortex-R5Farmv7r-none-eabi
, little endian Cortex-R4 and Cortex-R5armv7r-none-eabihf
, little endian Cortex-R4F and Cortex-R5F
- ARM Linux
- ARMv5TE (e.g. ARM926EJ-S),
- ARMv6 (e.g. ARM11 as found in the Raspberry Pi 1 / Zero),
- ARMv7-A (e.g. Cortex-A8 as found in the Beaglebones),
- and ARMv8 (e.g. Cortex-A53 as found in the ODROID-C2) ...
- ... in GNU and MUSL flavors and in soft float and hard float variants;
- notably, support for ARMv4T (e.g. ARM7) and older versions is missing.
- RISCV (1.30-beta)
riscv32imac-unknown-none-elf
, RV32I base instruction set with M, A and C extensionsriscv32imc-unknown-none-elf
, RV32I base instruction set with M, and C extensions
rustc
also supports code generation for the MSP430 architecture (see rustc --print target-list
).
In general, ARM Cortex-M and ARM Linux have the most mature ecosystems whereas the ARM Cortex-R, MSP430 and RISCV ecosystems are in early stages or not as mature.
For specific device support check awesome-embedded-rust.
We can talk about support at different levels: does the compiler support my device? does the crate ecosystem support my device?
Let's start with compiler support. The compiler supports architectures or ISA (Instruction Set Architectures) rather than specific devices. Compiler support can be further divided in two levels: compilation target support and architecture support.
By compilation target support we mean that you can readily cross compile a crate
for a certain compilation target using Cargo. To keep the default installation
slim only the native compilation target is installed and other targets have to
be installed using the rustup target
subcommand. If rustup target list
lists
a compilation target that matches your device then Cargo supports cross
compiling to your device.
For example, let's say we want to know if rustc
supports cross compiling to
32-bit RISCV. We check rustup target list
$ rustup default 1.29.0
$ rustup target list | grep -i riscv || echo not supported
not supported
$ rustup default nightly-2018-09-18 # this date is just an example
$ rustc -V
rustc 1.30.0-nightly (2224a42c3 2018-09-17)
$ rustup target list | grep -i riscv || echo not supported
riscv32imac-unknown-none-elf
riscv32imc-unknown-none-elf
This indicates that 1.29 doesn't support 32-bit RISCV but that 1.30 will.
Once you have installed a compilation target using rustup target add $T
you'll
be able to cross compile crates to it using cargo build --target $T
.
$ rustup target add riscv32imac-unknown-none-elf
$ cargo build --target riscv32imac-unknown-none-elf
If your device doesn't appear in rustup target list
that doesn't mean that
rustc
doesn't support your device at all. It could still support code
generation for your device architecture. rustc
uses LLVM to generate machine
code; if the LLVM backend for your device architecture is enabled in rustc
then rustc
can produce assembly and/or object files for that architecture.
The easiest way to list the architectures that LLVM supports is to run
cargo objdump -- -version
where cargo-objdump
is one of cargo-binutils
subcommands.
$ rustup default nightly-2018-09-18 # this date is just an example
$ rustup component add llvm-tools
$ cargo install cargo-binutils
$ cargo objdump -- -version
LLVM (http://llvm.org/):
LLVM version 8.0.0svn
Optimized build.
Default target: x86_64-unknown-linux-gnu
Host CPU: skylake
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_be - AArch64 (big endian)
arm - ARM
arm64 - ARM64 (little endian)
armeb - ARM (big endian)
hexagon - Hexagon
mips - Mips
mips64 - Mips64 [experimental]
mips64el - Mips64el [experimental]
mipsel - Mipsel
msp430 - MSP430 [experimental]
nvptx - NVIDIA PTX 32-bit
nvptx64 - NVIDIA PTX 64-bit
ppc32 - PowerPC 32
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
sparc - Sparc
sparcel - Sparc LE
sparcv9 - Sparc V9
systemz - SystemZ
thumb - Thumb
thumbeb - Thumb (big endian)
wasm32 - WebAssembly 32-bit
wasm64 - WebAssembly 64-bit
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
If your device architecture is not there that means rustc
doesn't support your
device. It could be that LLVM doesn't support the architecture (e.g. Xtensa,
ESP8266's architecture) or that LLVM's support for the architecture is not
considered stable enough and has not been enabled in rustc
(e.g. AVR, the
architecture most commonly found in Arduino microcontrollers).
If your device architecture is there then that means that, in principle, rustc
supports your device. However, an architecture like ARM can be very broad
covering several ISAs and extensions. Instead, you'll want to work with a
compilation target tailored to your device. Custom compilation targets are out
of scope for this document; you should refer to the embedonomicon for more
information.
Crate ecosystem support can range from generic support for the architecture to device-specific support. We recommend that you search on crates.io for the architecture (e.g. ARM or Cortex-M), for the microcontroller vendor (e.g. STM32), for the target device (e.g. STM32F103) and the target development board (e.g. STM32F3DISCOVERY). We also suggest that you check the awesome-embedded-rust list and the crates maintained by the embedded Working Group.
As of 2020-07-24 the support for AVR have been merged into the nightly version of
the official Rust compiler, rustc
. See the RFC for merging the avr-rust fork.
As of 2020-08-24 the official Rust compiler, rustc
, relies on LLVM for
generating machine code. It's a requirement that LLVM supports an architecture
for rustc
to support it.
There is no support for the Xtensa architecture in LLVM proper. You may be able
to find several forks of LLVM with varying levels of support for the Xtensa
architecture but rustc
will not be able to use any of those forks due to the
maintenance and infrastructure costs of developing rustc
against different
versions of LLVM.
TL;DR rustc
will support the Xtensa architecture when the official LLVM gains
support for the Xtensa architecture. As LLVM is a project independent of the
Rust project we can't give you any estimate on when that might happen. A list of
submitted patches can be found on this LLVM dashboard.
We sometimes get questions like this one: "My Rust program is 500 KB but my microcontroller only has 16 KB of Flash; how can I make it fit?".
The first thing to confirm is that correctly measuring the size of your program.
rustc
produces ELF files for most embedded targets. ELF files have metadata
and contain debug information so measuring their size on disk with e.g. ls -l
will give you the wrong number.
$ # 500 KB?
$ ls -hl target/thumbv7m-none-eabi/debug/app
-rwxr-xr-x 2 japaric japaric 554K Sep 19 13:37 target/thumbv7m-none-eabi/debug/app
The correct way to measure the size of an embedded program is to use the size
program or the cargo size
subcommand.
$ # ~ 2 KB of Flash
$ cargo size --bin app -- -A
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
app :
section size addr
.vector_table 1024 0x8000000
.text 776 0x8000400
.rodata 208 0x8000708
.data 0 0x20000000
.bss 0 0x20000000
.debug_str 145354 0x0
.debug_abbrev 11264 0x0
.debug_info 139259 0x0
.debug_macinfo 33 0x0
.debug_pubnames 40901 0x0
.debug_pubtypes 14326 0x0
.ARM.attributes 50 0x0
.debug_frame 21224 0x0
.debug_line 117666 0x0
.debug_ranges 63800 0x0
.comment 75 0x0
Total 555960
Of the standard sections, .text
, .rodata
and .data
will occupy Flash /
ROM; .bss
and .data
will occupy RAM; .debug_*
, .ARM.attributes
and
.comments
can be ignored as they won't be loaded into the target device
memory. For the other sections you'll have to check your dependencies' docs.
In this examples the program will occupy 2008
bytes of Flash.
Note that most (all?) runtime crates, like cortex-m-rt
, will check at link
time that the program fits in the target device memory. If it doesn't fit you'll
get a linker error and no output binary. So, provided that you correctly entered
the size of the memory regions of your device then if it links it should fit in
the target device!
If you are measuring size using the right method and your program is still too big then check out our section on optimizations.
TODO(resources team) add link to the optimizations section
It is quite common that by oversight the linker configuration is not suitable
for the target device. Indications for such a problem are loading errors in gdb
:
(gdb) load
Loading section .vector_table, size 0x400 lma 0x0
Loading section .text, size 0x2064 lma 0x400
Load failed
Please note the lma
which is the loading address and needs to match up with the
start address of the flash.
Similarly openocd
will indicate such a problem:
...
auto erase enabled
Info : device id = 0x10036422
Info : flash size = 256kbytes
Warn : no flash bank found for address 0
wrote 0 bytes from file target/thumbv7em-none-eabihf/debug/examples/hello in 0.001434s (0.000 KiB/s)
If you see such a message you will need to check your linker configuration, usually in
memory.x
for the correct addresses (and ideally also sizes). Please also note that
after a change you will need to cargo clean
and rebuild to use the changed addresses.
If you're using semihosting
, switch to a different input / output mechanism.
An embedded MCU will typically stop working if exceptions (which is the MCUs way
of signalling special or abnormal events) occur which are not handled and cleared,
either by an exception handler or a connected debugger. The latter case is especially
interesting because there's a method of interacting with a connected computer
dubbed semihosting
which will work iff a debugger is properly connected
and debugging software running and correctly set up. This method uses special
processor instructions (e.g. a service or breakpoint call) to alert the connected
system about input and/or output requests from the device. If no debugger is
available to handle such a situation, the system either wait indefinitely or
generate an even stronger exception which can only be cleared by a reset.
There're two common scenarios which cause such a lockup unintentionally:
- The use of semihosting as input / output channel, e.g. via
cortex-m-semihosting
crate - A
panic
in the program with thepanic-semihosting
crate in use, however the occurence of apanic
means that the program is defunct anyway
Please note that examples may make use of the semihosting
concept but this
should not be used in production setups due to the lack of connected debugger and
a host running appropriate debugging software. The use of a serial interface is
often a better choice. However if you just would like to see your example running
please make sure your setup is ready to deal with the semihosting
requirements.