1. 59
    1. 6

      I’m curious if anyone around here has much experience with Rust on Embedded. For context, I’ve done a fair bit of bare metal code on a handful of architectures (primarily AVR and then ARM Cortex M), and have for the last few years worked primarily with Zephyr on ARM and ESP32. I’ve also played around with Rust a bit but haven’t ever used it professionally or on non-toy learning projects.

      One of the biggest questions I’ve got is what approach people are using for things like a HAL or supporting subsystems like an IP stack. Is all of that stuff happening in Rust as well? Or is it linking against, say, the NXP MCUX HAL or LwIP and exposing that to the application layer? It seems plausible to do basically everything except the initial “jump to main assembly” in Rust, but it also seems like there would be a lot of wheel reinvention happening to get there.

      Definitely curious what peoples’ experiences are with this!

      1. 20

        In my opinion ecosystem gets better everyday thanks to the effort of the countless individuals. For well supported platforms (Cortex-M for example), a lot of stuff is basically plug & play.

        probe-rs has gone a really long way in terms of device programmers & target support.

        Embassy is a very competent HAL-collection (at least for STs and nRFs) as well as a provider of high-level abstractions (just to name embassy-net that provides async network APIs on top of smol, TCP/UDP IP stack library) and a runtime (embassy-executor). One could say that it is to Embedded Rust what Tokio is to non-bare-metal Rust.

        RTIC is an RTOS based on stack resource policy principle which allows for race-free and effective resource sharing which is compile-time checked. It works for any Cortex-M based MCU as it only requires a standardized interrupt controller (FTR, firmware from the article is using it).

        defmt, together with RTT (or any other log sinks) allows for efficient and trivial to integrate logging where formatting is deferred to the host side consuming the logs saving the bandwidth in transfer as well space occupied by the firmware in flash.

        Ferrocene seems to be opening new doors for Embedded Rust applications in areas that require certain certifications and I’m very curious where it will lead to. It’s a beginning of course but it shows to the industry that the technology is here to stay and that it’s worth investing time and money into.

        It’s difficult to list all the projects that are going on in the ecosystem but it’s really fascinating.

        As I usually say, power of this tech-stack is not even memory safety but the ability for library developers to create APIs that are difficult to misuse. For example, many HALs leverage type system to prevent invalid peripheral configurations (for instance setting up GPIOs in an illegal way). Move semantics really help with preventing abstraction abuse/misuse. Some pitfalls obviously still remain, like linker scripts for example and similar things that can possibly break certain assumptions that Rust code might have. Nonetheless what can be achieved already is quite amazing and I’m sure that situation with these issues will also improve, at least to some extent, with time.

        I dare to say that we are reaching the point where platforms like Arduino slowly become irrelevant because “actual MCUs” are not absolutely atrocious to achieve anything useful with. Tooling is really pleasant to use, the community is very helpful. There are still areas that beg for improvement but time will definitely help. If anything I am also looking forward to any Rust competition that will learn from Rust mistakes and improve on them (although it might be difficult to compete if Rust grows proper roots in the industry and the improvements won’t be “good enough to be worth another shift”). Personally I am a big fan of trying to build “contracts” with type system and I would love to see more of it. For example, together with fellow crustceans we ran an experiment to build a compile-time checked clock system configuration API for ATSAMD family of MCUs. Result is quite good and despite of a challenging versatility of the peripheral, abstraction is permissive enough to enable most if not all legal configurations.

        I’ve had a privilege to work in the project with Julius and the team, it was an amazing journey and I think we’ve all learned a lot along the way. Also, for people that have not seen an earlier inteview here’s the link.

        1. 17

          At Oxide, we’re doing pure Rust, no using the NXP HAL. We do use crates like https://crates.io/crates/stm32, but not the higher level HAL stuff like https://github.com/rust-embedded/embedded-hal

          We did create our own OS, even the “initial jump to main assembly” is in Rust, in the sense that it’s inline assembly in the Rust codebase: https://hubris.oxide.computer/ There are several projects like this, such as TockOS and embassy, each with their own tradeoffs.

          1. 4

            I’m going to have to give Hubris a closer look here. I remember looking briefly at it a few months ago but didn’t go much further beyond “Huh! Well that’s cool!”

          2. 5

            I’m just a hobby embedded programmer. I got this board, and followed this book. There were a few weird edge cases, but it was pretty easy overall. Would definitely recommend, but does take a bit more learning/research than just using C. I think there is also less board support - for example that board linked above is the ESP32-C3 which uses RISC-V.

            1. 1

              I started on my journey of learning Rust and just got a board as you did, inspired from your comment! Looking forward to playing with it - I told my father the same :)

            2. 5

              Is all of that stuff happening in Rust as well? Or is it linking against, say, the NXP MCUX HAL or LwIP and exposing that to the application layer?

              Both are possible. If you target few enough devices, maintaining your own HAL isn’t that big of a burden and enough of the boilerplate can be auto-generated. FWIW, manufacturer-supplied HALs aren’t, uh, created equal, some of them are worth avoiding even in C land :-). Lots of relevant Rust projects do “all of that stuff” in Rust (see e.g. TockOS). In fact, since Cargo makes it reasonably easy to reuse these things, well-supported architectures (Cortex, RISC-V to some degree) have publicly-available crates for low-level hardware access primitives, so maintaining your own HAL isn’t as big a deal as it seems if you only look at your target devices’ reference manuals.

              There’s good reason for that, IMHO HALs are one of the things that I’d really want to use Rust (or something comparable) for, to the point where if I only had time and resources to adopt just “a little bit” of Rust, I’d rather get some of the HAL written in Rust and leave the C application logic alone. A type-safe HAL interface is both a net safety improvement and something that will likely expose a bunch of subtle and hard-to-trigger application-level bugs.

              It seems plausible to do basically everything except the initial “jump to main assembly” in Rust, but it also seems like there would be a lot of wheel reinvention happening to get there.

              Sort of, but I think there’s also real potential for getting the wheels to also roll better :-).

              This is a little higher on the resource consumption ladder than MCUs, but probably a relevant example. I’ve been experimenting with an early loader for a toy kernel for the last couple of months (on and off again, time’s been a little scarce this year) – basically the equivalent of locore.S in BSD land – and the only bits of actual assembly that I’m left with are some minimal setup for the loader runtime (think zeroing the BSS section), a jump to the loader’s main function, and some code that enables memory translation. Some of that could be Rustified, too, but I don’t really see the benefit.

              In exchange, I get to do things like setting up initial page tables and the initial kernel runtime in something that’s not ASM. I don’t mind the lack of safety at that stage (not for a toy kernel, in any case), but changing things in a blob of assembly tends to be tedious and failure-prone. Whereas this super bloated locore.rs :-) pretty much worked on the first pass, and can hand off relevant runtime data to kernel land with minimal impedance mismatch.

              It’s also a lot more readable, and while I’m not opposed to TECO-looking code on principle, early loader code isn’t something one touches particularly often once it actually works, so it makes coming back to it over long intervals a lot more bearable.

              1. 4

                I was recently exploring rust on esp32 - everything works nicely with pure esp-idf. I also had to support esphome and it turns out it doesn’t support building rust so here I am once again in C ;(

                1. 3

                  +1 for embassy. I use it on a custom STM32 board I made and love it. Did a talk about that process at Oxidize conference (which I highly recommend):

                  https://youtu.be/ECOr_49W_-k