Embedded rust current situation?

I've seen various threads on this but I'm struggling to gather the most up to date picture.
What is the current situation regarding using Rust on devices such as the 8266, ESP32, Teensy 4.0 and pi pico?

For ARM processors (Teensy, Pi Pico) it is possible to compile, although the best support have ARM processors from ST and Nordic. For the esp8266/32 the support is just really bad, as the underlying Xtensa architecture is not really supported by Rust.

Take a look here: GitHub - rust-embedded/awesome-embedded-rust: Curated list of resources for Embedded and Low-level development in the Rust programming language

1 Like

That's great, thank you, I'll read that material.
I'm new to this, (web developer for 23 years), does this mean that the features of the boards are also supported, for example I2C, SPI, PWM through existing libraries?

I'm not familiar with the Teensy or Pi Pico, but in general, there are two types of libraries for hardware support in Embedded Rust (at least for ARM Cortex-M microcontrollers):

  1. Peripheral Access Crates (PACs): Those are automatically generated from a vendor-provided file (the SVD file) and the resulting PAC is as good or complete as the SVD file (which typically means, mostly complete but possibly rather buggy). It's also usually a pain to use. Very low-level, and can't be understood without a good understanding of the hardware itself.
  2. Hardware Abstraction Layer (HAL): Those are hand-written libraries built on top of a PAC, with an API that is typically more convenient and harder to get wrong. Writing those is a lot of work, so their completeness and quality varies a lot. Often they are started by a single person for a single project. For the more popular hardware, they are then built upon by others and can become quite good.

What you want to use depends heavily on the requirements of your project, and thus what hardware you want to use. I'll give you three pointers:

  1. stm32-rs: GitHub org for STM32 microcontrollers. Definitely the most active sub-community within embedded Rust, but quality, completeness and active maintenance vary between the different HALs.
  2. nrf-hal: HALs for Nordic microcontrollers. I haven't used it in a while, but I think it covers the basics (and probably more) very well.
  3. LPC8xx HAL: Last but not leased, a HAL I've been working on for a few years. Works great with the LPC845-BRK, which is quite cheap and easily available, but not very powerful. Covers the basics you mentioned (I2C, SPI, PWM) quite well, and has some more advanced features too. And in my biased opinion, it's also more thought out and better documented that most other HALs.

Other than that, @trembel already posted a link to Awesome Embedded Rust. Searching for the hardware you're interested in on crates.io might also be useful.

2 Likes

This post ended up being quite long, but it is full of information I wish I had known when starting in embedded Rust. Would have saved me a lot of learning.

So very helpfully, when you buy ARM IP to put on your chip, they mandate that you must provide an SVD file that describes your memory mapped peripherals (and possibly other things: I'm not an expert). The Rust embedded folks have built a tool called svd2rust that takes this SVD file (which is actually an XML file with a particular schema), and generates zero-cost rust code that allows you to manipulate registers without having to write the addresses by hand. (Chip providers also generally provide C headers that have the memory addresses defined, which is why C developers don't need the SVD files so often).

Crates generated from svd2rust are called 'peripheral access crates' and generally have a name like <board serial>-pac, e.g. nrf52832-pac, (but not always, e.g. stm32f1). You can usually find the crate by searching for '<board serial> rust' or variations thereof.

The rust embedded folks have been working on building another layer on top of the PACs called HAL (or hardware abstraction layer). The idea is that say you have a peripheral that talks to the chip over the SPI and you want to make a crate that other people with the same peripheral can use (e.g. driver crate). If you write it for your specific register memory addresses (from the PAC crate) then it would only work with your particular configuration, and even then only if you used the same pins/SPI peripheral. HAL defines traits for generic commands, which in the case of SPI include 'send/receive data', for example. By writing your driver using these traits, you allow the end-user to bring their own particular setup by implementing those traits and forwarding the commands to the correct registers. Most PACs also have a corresponding HAL crate that in contrast to the svd2rust-generated PAC is hand written, and implements the HAL traits (in the embedded-hal crate). Board HAL crates often also expose the PAC crate as a submodule (e.g. nrf52832_hal::pac in the nrf52832-hal crate).

The idea of board HAL and driver crates works really well when you are running a series of tasks sequentially, but some of the abstractions don't work so well when you want to perform multiple tasks concurrently, or sleep when there is nothing to do. For this, the basic recipe is (slightly simplified)

  1. Set up an interrupt handler for an event that marks the end of some peripheral activity (for example copying data from RAM to an SPI peripheral).
  2. Start the thing off doing what you want it to do.
  3. Go do something else or sleep.
  4. When the task is complete, the processor will stop what it is doing and run your interrupt handler. In the handler, you can either go back to 1 and set of another task, or maybe send some data to the main process and wake it up.

Software that does some of this stuff for you is called a 'Real-Time Operating System' and the Rust ecosystem has a really good one of these, called RTIC (formerly RTFM). This crate will help you set up all your interrupts and prioritise them, so that more important stuff runs first, which could be very important in, for example, a medical device.

RTIC does force you to think about interrupts however, and also it is best suited for hard-realtime requirements when you have to prove that things will happen quickly enough (see medical devices again). In other projects, for example a smartwatch, your primary focus might be to minimize power usage by powering down all peripherals and the processor where possible, while still running tasks concurrently. This space is currently being explored by projects like embassy using Rust's async machinery. It turns out that scheduling tasks concurrently is basically what a Rust async executor does. What we want to be able to do is things like

// Turning on a made-up LCD screen peripheral

// SPI commands take a while to run, because we have to copy data out of memory
spi.send_data(Command::POWER_UP).await;

// data sheet says we must wait at least 150ms before sending more commands
sleep(Duration::from_millis(150)).await;

spi.send_data(Command::TURN_ON_BACKLIGHT).await;
spi.send_data(Command::CONFIGURE_SCREEN).await;
spi.send_data(screen_configuration).await;

// we have to wait another 10ms before sending pixel data
sleep(Duration::from_millis(10)).await;

spi.send_data(pixel_data_for_first_frame).await;

and let the async executor handle things like getting on with another task while we write out the pixel data. (Aside In the above example we might have wanted to block the processor instead of awaiting for some of the commands since the overhead of the executor would actually take longer than just spinning the processor until the SPI operation has finished.)

This is where we are up to. One very current problem is that it is difficult to design an executor in Rust utilising interrupts that is safe with respect to memory leakage. This means that in practice you can get UB without unsafe code by using mem::forget or the other methods for leaking futures. Maybe this problem will be solved, or there will always be a caveat that you mustn't leak any futures in async embedded Rust (which is not hard to avoid, but does mean that safe code is actually unsafe).

4 Likes

Seems people have been making some headway with Rust for the Pi Pico:

Do they? I've seen vendors that don't provide one.

(and unfortunately the vast majority of SVD files is also riddled with bugs)

I'm sure someone once told me that they were mandatory when licensing ARM designs, but I can't find any written evidence to back up the claim. Shame. They are very useful.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.