Are we embedded yet?
Updated Jan 2024
With the rise of the connectivity of embedded devices in 'IoT' and smart industry it's becoming increasingly important to write correct and secure software. Not only does Rust help us in that department, it is also a modern language that gives us lots of great developer tools and an easy-to-use toolchain.
"Thanks to its direct access to both hardware and memory, Rust is well suited for embedded systems and bare-metal development." - GitHub Engineering Blog
In case you're wondering whether Rust is currently being used in the embedded industry, the answer is yes. At Tweede golf, we used Rust to write the software for a heart rate monitor for Glanum, and to build on-board firmware for a solar sail satellite for Gama. And we're not the only ones:
- System76 use Rust in parts of their laptop firmware
- Fusion engineering create drones and write the flight software in Rust
- The Oxide Computer Company are reinventing servers and write many of their on-board firmwares in Rust
- And many more places (like Linux drivers and Fuchsia OS by Google)
But Rust is a young language, so let's check what works and what doesn't work when using Rust on embedded devices and answer the following questions:
- Is the language ready?
- Do we have the platform support?
- Are our devices/peripheral supported?
- Can we use our prefered communcation method?
- What about user interface displays?
- Are we allowed to use Rust?
1. Is the language ready?
Std and Core π
Rust has a small but solid standard library that contains portable APIs for things like filesystems, threads and networking. These, however, are not always available on embedded devices and if they are, they will likely have different constraints than the ones you'll find in the major operating systems.
Because the standard library definitions are not that useful for bare-metal development, Rust has the ability to turn off the standard library. This mode is called no-std
.
Now luckily we won't be left with nothing. What remains is the so called core
library. This has everything you expect from the standard library except for anything that is bound to an OS and anything that does allocation.
Of course, easily using crates is still available, so some of this lost functionality can be regained by pulling in useful crates like heapless
.
Alloc π
Often embedded firmware doesn't do allocation, but we can if we want to. After we turn off the standard library, we can opt-in to using the alloc
library. This gives us back datatypes that allocate, like Box
, Vec
and String
.
You'll have to provide your own global allocator that the library will then use. There are several to pick from, but a decent one is embedded-alloc.
Historically, though, Rust was made to be run in user mode and so made the (then reasonable) assumption that allocation was infallible and that you only ever need one global allocator. This is not great for kernel development, but might be OK for your embedded application depending on the constraints.
In the future this will be improved where multiple different allocators can be used and where fallible APIs are present. Currently this is all still behind an unstable flag.
Async π
Rust has support for async programming which is similar to what C# and JavaScript have to offer, and this gives us language semantics for concurrency. It does have some rough edges. Not all parts of the language are ergonomic when using async and async cannot be applied everywhere. Many of these rough edges can be taken away with allocations, but embedded typically doesn't use allocations.
Improving async is one of the top priorities of the Rust project, but it's already in a good enough state that if you use a nightly compiler, you can write perfectly good async embedded Rust code.
This can be seen from the popularity of the async runtime Embassy. (If you're interested, we have two blog posts about this, one by Folkert and one by me.) Another popular runtime, RTIC, has released a new 2.0 release that introduces async support.
It seems pretty clear that async will be a big part of the future of embedded Rust.
Conclusion: state of the language
So whether your project is hosted on an OS or running bare-metal, Rust is ready for your needs. The biggest pain areas are fallible allocation and full async support.
2. Do we have the platform support?
Wanting to use Rust is one thing, but is there support for it from vendors? Let's look at some platforms and see what Rust can do.
Cortex-M π
The ARM Cortex-M microcontrollers are very well supported. This popular embedded platform was, as far as I know, the first one that people had serious programs running on. If you look around in the community today, you'll see that most users run Cortex-M.
The main Cortex-M support comes from the cortex-m
and cortex-m-rt
crates where the former has the register definitions and the latter is a runtime to boot the microcontroller.
In principle Rust can run on any Cortex-M microcontroller. However, that is not the only support we need. We want to be able to use the peripherals of the chip as well.
Support for this differs by manufacturer and series and the support is mostly community-driven. You'll see that newer and lesser-used chip series aren't supported as well as the popular ones.
For example, Infineon recently started giving official Rust support for some of their microcontrollers (Cortex-M and TriCore). For more info go to their promopage.
One thing that is usually supported (or that you can easily make yourself) is a Peripheral Access Crate for the chips. These PACs contain register definitions for the peripherals and are usually generated from the ARM-mandated SVD files using svd2rust
.
Hardware Abstraction Layers are built on top of these PACs. For popular series there's usually a good HAL out there on crates.io.
For flashing the programs to the microcontroller there's also very good support from the probe-rs
project.
RISC-V π
RISC-V has become quite popular over the past few years. But it's newer than Cortex-M and noticeable in terms of Rust support.
The good news is that the compiler can target RISC-V just fine. From the embedded community we also have similar crates to Cortex-M with riscv
and riscv-rt
.
When looking at which HALs are available, we see a bit of a lack of support. There are some, for example the HAL for the E310x family of microcontrollers from SiFive. But that's probably because, aside from one series we'll get back to, there aren't many really popular RISC-V chips that I'm aware of.
Because of this some landmark Rust embedded runtimes don't work on RISC-V, namely RTIC and Embassy. (Though I hear Embassy support might be in progress).
This brings us to the RISC-V series that enjoys the only manufacturer support out there (so far)! And that is the RISC-V chips from Espressif. See their github page here.
All in all, how well RISC-V is supported really depends on the series you're using.
The others
Aside from Cortex-M and RISC-V there are no other platforms of which I would say the support is 'good'. It really depends on how much support you need. There are, of course, some platforms that I don't have any experience with.
In some way or other there is support for:
- msp430 π§‘
- AVR π§‘
- MIPS/PIC32 π§‘
- Xtensa (like the ESP8266 or ESP32) 𧑠(or π if you don't care that you need to use a forked compiler)
You'll have to evaluate them for yourself. Keep in mind that I may have missed some platforms.
Out of scope of this blog post are the bigger application processors. But of course you can write bare-metal x86-64 and aarch64 code as well.
For a full list of what the official compiler supports, go here. Note that Xtensa isn't on this list. They keep their own fork on github.
3. Are our devices/peripherals supported? π
Embedded Rust is still quite new, so it doesn't have the enormous user base that C enjoys. And because most users use C (or C++), chip manufacturers are more likely to give their support in C (if they support it in code at all). So that's something that Rust has against it.
But something that Rust does very well is preventing fragmentation in its ecosystem, something that is lacking in C with its many incompatible build systems and the absence of common portable abstractions.
The fragmentation is kept in check in Rust by the use of embedded-hal
, which is a crate that just contains standard interfaces for things like SPI, I2C and more. Pretty much every device driver accepts these interfaces and pretty much every hardware abstraction layer implements the interfaces for its microcontroller.
This means that if there is a driver, it will likely work on your platform.
There's a nice list with a lot of drivers here. But the best option you have is looking on crates.io or github for a driver for your device.
Of course, creating a driver from scratch isn't the end of the world either if your chip is relatively simple.
Conclusion: devices and peripherals
Using Rust, it's much more likely there isn't a driver for a device you're using. But if there is a driver, it will likely be compatible with your setup.
4. Can we use our preferred communication method?
TCP/IP π
There is an excellent TCP/IP stack written in Rust that can be made to work with a lot of hardware, called smoltcp. It fits in the same place as a library like LWiP.
There is even experimental support for IEEE 802.15.4 & 6LoWPAN.
Once you're able to connect to the internet, you can use various crates to have TLS support or to do e.g. http requests or mqtt.
LoRaWAN π§‘
There are crates out there that do LoRaWAN and they are perfectly suitable for creating nice prototypes. Here's an example from Embassy: github
However, I'm not aware of any available Rust stack that has been certified.
Modbus π
There is a really awesome modbus implementation with the rmodbus crate that works on almost any device. It provides support for the ascii, tcp and rtu modes.
EtherCAT π
If your device needs to operate in a PLC network, you might need to speak EtherCAT. Luckily there is a crate named ethercrab, which is well-documented and supports a large number of features.
BLE, Thread, Zigbee, Matter, Wifi π§‘
We can be short about these. There are no serious implementations for any of these.
For some there are good wrappers around existing C libraries. For example, you can use BLE on nRF52 devices using the softdevice library.
Wifi is officially supported in the Espressif Risc-V series.
LTE π
Often LTE (or other modem types) come in chips on their own and you need to talk with AT commands to them.
One exception is the nRF91 series which has a built-in modem. Nordic has a binary and some header files available around which I've made a async high-level wrapper (nrf-modem).
Conclusion: communication methods
This is a bit of a rough area in Rust, but most is due to only having open source projects which clashes with the often expensive and extensive certification requirements.
Luckily there's often a good C library available that can be wrapped so that we can still get things done.
5. What about user interface displays? π
Similar to the embedded-hal crate there is the embedded-graphics crate that serves as a fixed interface for graphics crates and display drivers. Because this crate has existed for so long already, much of the ecosystem is compatible with it.
In its README you'll find a lot of links to working display drivers and helper crates to extend the functionality.
Also make sure to check out slint, which is a fully fledged UI library that can run on desktop, web and - most crucially - on embedded devices.
6. Are we allowed to use Rust? π
In other words, does Rust have a certified compiler?
Well, as of October 2023, Ferrous Systems have released Ferrocene, an ISO 26262 and IEC 61508 qualified version of the existing open-source compiler, rustc.
Currently, it is for sale 'in bulk', meaning you can buy 10 licenses. If that's what you need, contact the folks behind Ferrocene. Individual licenses should be made available soon (early 2024).
All in all...
In short, what works and what doesnβt work when using Rust on embedded devices depends heavily on exactly what you are using, what you are trying to build with it, and how much you are willing to do yourself and how much you want to rely on others' (open source) work.
From experience, I can say that we are definitely embedded, even though there are still rough edges that might lead to people choosing an alternative over embedded Rust in some cases.
Looking for Rust embedded engineering?
We offer:
- training for your team
- Rust teams for hire
- or a combination!