Settings

Theme

Ask HN: Has any Rust developer moved to embedded device programming?

89 points by detuks 3 years ago · 63 comments (62 loaded) · 1 min read


Hi! Was wondering if any Rust developer have moved to embedded land. Did you switch to C/C++ or stayed with Rust? What MCU did you work with? Any tips?

I have good understanding of Rust and soon will need to program ESP32 chip. Write a driver and and http/tcp api on it.

Currently I jave seen mixed messages about Rust in embedded. Ecosystem moves fast, but semms like old C/C++ devs stay with their lang. So I'm curious what Rust devs have to say abou it.

reynoldsbd 3 years ago

I'm fortunate enough to have just landed on a new team working with embedded Rust. Here are some of my takes, in no particular order.

The language and ecosystem have come a long way in a very short time. It's easy to use safe `no_std` Rust on the stable toolchain for 98% of your code, and there are crates available for all kinds of things like memory management, register access, or even async runtimes.

Compared with C, Rust is absolutely a game changer in terms of reliability/safety/productivity. Every line of C code is a potential liability, because humans make mistakes. Having a compiler that smacks me down is invaluable; it results in a safer and more reliable program, and it helps me get it right the first time. Like many others, my experience with Rust is nearly always "if it compiles, it works". This is _especially_ valuable for embedded programming, because debugging is often way harder when working with hardware.

One major drawback is lack of support/engagement from hardware vendors. They pretty much assume you are using C, and all of their IDEs/SDKs/codegen tools/whatever are written with that assumption. This probably isn't going to change any time soon (ever?). What this means is that if you want to use Rust, you'll be on the hook for figuring out a lot of low-level things like linker scripts, boot sequence, or memory/clock initialization. Often this means reading or reverse-engineering the vendor's SDK to figure out how these work. If you're working at a big company on a serious product, you might have done this anyways. But for a hobbyist, this can be a huge barrier.

  • cogman10 3 years ago

    > One major drawback is lack of support/engagement from hardware vendors. They pretty much assume you are using C, and all of their IDEs/SDKs/codegen tools/whatever are written with that assumption. This probably isn't going to change any time soon (ever?).

    My assumption is that with the advent of rust getting into gcc, you will see at least some of these changes starting to make their way into toolchains. Probably not for the MSP430, but possibly for the next generation of embedded chips.

  • P_I_Staker 3 years ago

    Is rust interoperable with C and ASM? I would think it'd have to be. Some of those things (all of them?) are considerations the system designer needs to worry about regardless.

    It seems like you should be able to still use most of the stuff provided by the toolchain in a similar fashion. That said, trying to do a native implementation by porting the code, or just using a C/ASM bootloader could prove tricky for newcomers.

    Bootloaders are often black boxes. It could be flashing something written in COBAL, and it would be none the wiser. It seems like rust would need to provide a mechanism for entry pointing. I don't know how to handle memory mapping and linker scripting in rust.

    I'm not disagreeing that it could be a huge barrier, but theoretically it doesn't have to be that much, or at least most of it is stuff you'll often be dealing with anyway.

  • b20000 3 years ago

    so what you are saying, is that rust solves the issue of finding competent engineers. you can use a language like rust such that engineers don’t need to be any good. it reminds me of microsoft frontpage and all the other scripting languages and tools that have been created to solve this issue. but here we are in 2022 and nothing has changed. people still hand code css and html to get what they want.

    embedded software engineering is about understanding the hardware and leveraging its power through good architecture and smart thinking and understanding what the code you write will do. you can’t shortcut this by using a new programming language that does the work for you. you will still need to understand the low level details. so, learn C++ and familiarize yourself with the related embedded standards for reliability and safety.

    adding rust into the mix will now require you to maintain code in not one but multiple languages as you cannot get around C or C++ and possibly assembly. it’s already complicated enough as it is, and you should focus on understanding the principles instead of learning another set of syntax and implicit logic.

    • Thiez 3 years ago

      Nice gatekeeping. Perhaps you could provide some concrete examples of how Rust is worse than C++ for embedded programming, when it comes to "understanding the hardware and leveraging its power through good architecture and smart thinking and understanding what the code you write will do"?

      To me it is very strange that you would act as if Rust is some dumbed down toy language (not your literal words, but I conclude this from your MS frontpage example), while it is considered (not entirely deserved, in my opinion) difficult to learn with a very steep learning curve.

      Personally I find programming in C quite annoying because of all the sharp edges (e.g. implicit integer promotion, the various types of UB) that you have to keep in mind. To me this distracts from thinking about good architecture, rather than promoting it.

    • crest 3 years ago

      I take something very different from the grand-parent post: the language requires you to get the design mostly correct to even compile. Unexperienced developers won't be able to come up any non-trivial new designs of their own, but can reuse existing code and even experienced developers have to fight the language to get a prototype stood up, but once they've working code it's more reliable. Not every line of code could accidentally reconfigure your PLL or voltage regulator just enough to drive you insane debugging a glitching chip.

    • reynoldsbd 3 years ago

      > rust solves the issue of finding competent engineers

      That's a very cynical take, but I think there's a kernel of truth in there somewhere. Rust does indeed make it much harder for less experienced programmers to make certain classes of mistakes. Why on earth would that be a bad thing?

      > you can't shortcut this by...

      "Using Rust" and "understanding the hardware"/"good architecture" are not mutually exclusive, and Rust is not a shortcut. Embedded programming is still very hard. In some ways, Rust can make it even more difficult by forcing your code and architecture to follow additional rules.

      > cannot get around C or C++

      Speaking from experience, this is false. It's perfectly possible to write embedded applications using only Rust and assembly, without a single line of C/C++. I do it every day.

    • adolph 3 years ago

      > so what you are saying, is that rust solves the issue of finding competent engineers.

      It’ll be a depressing time when the demand for engineering is lower than supply. Until then people with needs larger than resources will find alternatives to “finding competent engineers.”

      That said, Espressif seems to be moving past pure embedded and positioning themselves in IoT, for example the latter part of ESP-IDF stands for IoT Dev Framework. This allows them to provide a single way of doing things regardless of implementation within the broad ESP32 family.

      Will the results be the equivalent of Frontpage websites? Sure, and as any of those start getting the resources to need and use “competent engineers” they will unlike the efforts that would never have gotten off the ground.

    • cogman10 3 years ago

      I don't think that's what GP was saying at all.

      The issue they bring up in more that hardware manufactures will have things like

          #define BLINKY_LIGHT_ADDR 0xDEADBEEF
      
      in a standard set of headers distributed with their toolkit. That magic address is where you write to turn a light on or off.

      Now, they will almost certainly also define that sort of thing in a datasheet, somewhere, but for the average embedded dev it's far simpler to pull in the SDK and use that.

      This isn't some sort of "good programmer bad programmer" filter. This is a "I don't want to read a 40 page pdf of a datasheet to find out what you named light 1 and where that address is"

      • bsder 3 years ago

        It's worse than that.

        Something like your Bluetooth communication is dependent upon a C-based "communications stack" that demands to control the event loop and all the registers aren't even documented.

        "Where is the LED?" is easy to work around. "How do I put my BLE system into low power advertising mode?" may not even be possible without accessing the C library.

        • steveklabnik 3 years ago

          That’s a fun example, because while you’re right, that may also be changing. Android’s new Bluetooth stack is in Rust, in my understanding. We’ll see if more vendors provide even more stuff in Rust in the future!

        • cogman10 3 years ago

          Ah, good point. That would be rather tricky to solve.

    • felipellrocha 3 years ago

      Lol. Talk about straw man argument.

    • solumunus 3 years ago

      One of the dumbest posts I've ever read on here.

quake 3 years ago

I've done some small embedded Rust applications on various STM32 and RP2040 boards. I've been doing embedded dev in C professionally for 7 years and Rust full-time for 2.5, mix or system and bare metal.

It's very hard to unlatch your brain from some of the common C/C++ embedded principles of static context variables and thinking of the hardware registers as "owned" memory, which you have to do in Rust. The auto-generated HAL crates aren't that great unless you're using the most common ones like stm32f4 or RP2040. Even then, it's hard to create a portable device driver without delving into generic-hell.

That all said, the ecosystem is moving fast, and a lot of my gripes above are just a product of the embedded rust ecosystem being very new in comparison to C. I do love rtic as a framework, and while I've given embassy a try I think it's trying to do too much aside from being a good async runtime, it should just focus on the runtime and not with stuff like creating its own entire HAL. Hubris and humility are fascinating but I just haven't gotten around to tinkering with them yet.

Lots of good tooling too, and the fact that most of your original C/C++ debugging tools are compatible with rust binaries is just the icing on the cake.

I know that there's the whole Ferrocene project, but until that produces results, stick with C if you're doing safety-critical applications, especially if they need to be certified

  • jsmith45 3 years ago

    Ouch. Making you treat hardware registers as owned (or use unsafe to access them) feels like it would be unpleasant if writing single threaded firmware, where the only preemption is the interrupt handler.

    I’d much rather have them exposed as some form of atomic that is restricted to operations that are atomic on this specific hardware.

    • quake 3 years ago

      Unsafe is exactly what they are in C. They're essentially volatile memory that can change at any time, and woe befall anyone messing around with the same register without synchronization when using an RTOS.

      Good HAL crates don't have the entire register set as one struct, thank goodness. And for something like printing a message to serial in a panic handler, there are still unsafe options to yank control of registers, same as C.

      What I really like about some of the HAL crates is the usage of builder patterns while configuring a peripheral. Setting up timer parameters before starting it by design is chef’s kiss wonderful.

      But if you want to put the whole peripheral set in a static global, you can do that with a RefCell<Mutex<T>> or something similar. Or just yolo it and use unsafe blocks to ditch the mutex.

    • mgsouth 3 years ago

      Atomic ops only address a small part of the problem.* Sure, your internal queue pointers will be sane, and two-stage register settings won't be interrupted. But locks are very fine-grained, and aren't going to help you keep co-ordinated in-the-large. In fact, you could argue that each mutex is a warning about muddled coordination. "Who's going to be changing this queue?" "Everybody!" "Better put a lock around it."

      Yes, it's all trade-offs. You aren't going to avoid all shared responsibility, and will always need micro-coordination. But safe Rust forces you to assign ownership to every structure, at every scale. Some similar effects to opaque data structures, or actors. But unlike those techniques, it allows you to dynamically transfer responsibility.

      The most obvious issue is use-after-"free", use-before-"allocate". "Free" and "allocate" don't just refer to malloc--it's any situation where you pinky-swear you aren't going to be touching something.

      But more problematic is this kind of code is easy, even natural, to write in C:

      - Task A is waiting for a state changes on resources Q1 and Q2. When it sees a particular set, it will modify resource Q3's state.

      - Task B is firing off events to Q1 and handling error responses. It might need to stop Q1.

      - Since Q1 is no longer changing state, what happens to Q3? Who's responsible for keeping stuff straight? What's "stuff"? Do we need to worry about Q2?

      Rust is going to very strongly push you to explicitly designate what owns and is responsible for Q1, Q2, Q3, at all times. "B's got Q1, so A can't even look at it. I'm @#!* going to have to make A tell B what it wants and let B handle it." That was painful, but a good thing.

      * Note to self: Write a blog post titled "Atomics won't save you now!" Start a band named "Useless Atomics".

    • zaarn 3 years ago

      Being owned and unsafe feels like the most natural fit. If multiple threads (or the interrupt handler) manipulate a register while you're already working on it could leave you in a very undefined state you have no idea about. In this case, rust makes you promise that you checked that this will be entirely safe.

      You could also write wrappers, for example one that automatically turns off interrupt when you do a write operation or gives you a guard that lets you write and read the register, which turns off interrupts until the guard is dropped. Or one that has a lock or uses atomics. Plenty of options you can use here.

      • crest 3 years ago

        On ARM v7m the "correct" way to would be to raise the current thread's BASEPRI the the highest exception priority you have to lock up. The M0+ cores on the RP2040 only support v6m and require masking those interrupts, but since M0+ cores are limited to the 16 internal exceptions and at most 32 external interrupts the code sequence to temporarily mask those interrupts isn't much longer. The annoying downside is that it forces a tighter coupling on programmers. The RP2040 specifically may offer a cleaner solution if you can afford to dedicate one of the hardware locks available in the single-cycle I/O block to each conflict that requires resolution. Such a solution should even work for both ARM cores.

thenewwazoo 3 years ago

I had the pleasure of writing embedded Rust in the automotive space for a couple of years about 4 years ago, and it was an absolute game-changer even at that early stage. Being able to write generic drivers that I could easily test outside an embedded context sped development up an incredible amount. Writing the business logic wasn't necessarily any easier with Rust than C, but anything having to do with hardware (and especially concurrency) turned into a if-it-compiles-it's-correct affair.

At that time, I was pushing the cutting edge of what was possible alongside what's now the Embedded WG, but the job didn't work out. I am incredibly interested in finding another embedded Rust role, but have had nothing fall into my lap (my current FAANG handcuffs are quite golden). If you have the opportunity, you should absolutely take it.

C code is a liability, but sometimes liabilities are worth the risk if the payoff is good enough. If you want to move to Rust, you will need to show how the tradeoff changes in Rust's favor. Sometimes that's easy, sometimes that's hard. It depends entirely on your industry and product. For my part, I absolutely believe it is already a competitive differentiator.

garphunkle 3 years ago

I've been working in embedded for 5 years and am curious how rust could solve my biggest headaches:

* Managing build configurations - I use CMake to build a single application for multiple hardware platforms. This is accomplished almost exclusively through linking, e.g., a single header file "ble-ncp-driver.h" with multiple "ble-ncp-driver.cpp" files for each target platform. I call this the "fat driver" approach which has proven to be easier to work with than creating a UART abstraction or ADC abstraction. Does rust's package system address this?

* Automated device testing - fluid leaks are similar to bugs in software. They are systemic in nature and cannot be easily understood through static analysis. We spent equal time maintaining a test bench as product development.

* Preemptive operating systems - more trouble than they are worth. Often, devs get bogged down writing messages queues to pass items between task contexts and timing analysis requires detailed event tracing.

Given I don't see teams struggle with memory ownership (easy to do if you never, ever malloc), what else can rust bring to embedded dev?

  • quake 3 years ago

    I've found Cargo more than up to the task of managing build configurations, and doesn't require monkeying around with CMake scripts or Makefiles. It was pointed out in another comment but you can gate features and crates based on the target you're compiling to. Cargo also supports custom build profiles so you can also pick and choose what you want even if it's all on the same target.

    Creating a heap in Rust on a cortex M is safe and cheap-ish with a crate supported by the rust-lang developers. Much easier than implementing your own free() method on a memory pool.

    I think you would like rtic. Not a pre-emptive rtos, but a way to manage context between ISR's without relying on some kind of module or global variable that can get corrupted by multiple accessors. Very minimal overhead compared to FreeRTOS

  • lifeinthevoid 3 years ago

    It's not just ownership, it's memory safety. I've worked in embedded development and I can't say I've never seen a segfault occur or worse, without an MMU, just random crashes and buggy behavior due to memory corruptions.

  • markjgx 3 years ago

    > Managing build configurations...

    In terms of package management, you can apply rules to what crates you want to include; including specific platform constraints.

      [target.'cfg(target_os = "linux")'.dependencies]
      nix = "0.5"
    
    On the code side it's pretty much the same as C++. You have a module that defines an interface and per-platform implementations that are included depending on a "configuration conditional check" #[cfg(target_os = "linux")] macro.

    https://github.com/tokio-rs/mio/blob/c6b5f13adf67483d927b176...

  • ecesena 3 years ago

    For configurations, rust supports features. To me they look very similar to ifdefs in C, except they're managed directly by cargo and can be passed down into dependencies and modules.

    You can decide how to use them, for example you can very much create "fat drivers".

    If you want to see an example, here's how we build for dev vs release, on two different boards. Cargo makes it really smooth. https://github.com/solokeys/solo2/blob/main/runners/lpc55/Ma...

    Similarly, for testing, one annoyance for us is that in theory the user should press a button for every action. We have a feature to disable that, just so we can run integration tests (either on PC or on device) more smoothly.

  • larve 3 years ago

    C/C++ embedded developer here. I've never used Rust in embedded because I didn't really see the need. But I'm currently taking a retreat and I started playing with embedded Rust.

    At work I had the same stance as you, and pushed against adding rust to our ecosystem (to avoid fragmenting what was 100% C++/python): - memory ownership bugs are not a problem (and even on the host, with unique_ptr and shared_ptr you can really get quite far) - C++ meta programming is really quite expressive to nip most bugs in the bud (say, writing to the wrong port, adding an i16 to an i32, or adding ms to us), - C++ meta programming is pretty good at building bigger abstraction, such as monadic tasks

    Here's the main advantages I see and which convinced me to take it seriously.

    - cargo for package management and building. It's extremely easy and "nice" to add packages, manage multiple configurations, build additional tools as part of the building, but run them on the host (say, a protocol parser generator etc...)

    This is just huge. I basically almost never reused any code except copy pasting source from other projects or from the vendor lib straight into the project, because anything else was just too brittle, even with CMake. Most embedded projects I worked on had their own idiosyncratic build system based on make, and you had to relearn it every time.

    - macros that are actually worth it. THis might be the most exciting thing. I often use patterns such as state machines and other formalisms, but the best I can do in C++ to make them nice to write is mix up some ugly ass macros with some templating, and it always ends up being a mess in the error messages. Rust gives you some really decent "lisp"-y metaprogramming.

    - rust works equally well for the bare metal and the highest level scripting. That means that my projects won't end up being a mix of cmake + bash + python + C++, I can do everything in rust.

    - the embedded code with an abstracted HAL looks REALLY nice. It's almost arduino-like, except this is actually the real thing. This is what my pairing partner and I came up with to control a SPI display:

        fn new(
            spim: spim::Spim<SPIM0>,
            timer: &'a mut hal::Timer<pac::TIMER0>,
            cs: gpio::Pin<gpio::Output<gpio::PushPull>>,
            rst: gpio::Pin<gpio::Output<gpio::PushPull>>,
            dc: gpio::Pin<gpio::Output<gpio::PushPull>>,
            busy: gpio::Pin<gpio::Input<gpio::PullUp>>,
        ) -> Display<'a> {
            return Display {
                spim,
                timer,
                cs,
                rst,
                dc,
                busy,
            };
        }
    
        fn init(&mut self) {
            self.reset();
    
            // BOOSTER SOFT START
            self.spi(&[0x06u8, 0x17, 0x17, 0x17]);
    
            // POWER ON
            self.spi(&[0x04]);
    
            // CHECK NOT BUSY
            self.check_not_busy();
    
    
    Not only is every GPIO configuration typechecked, but the HAL layer takes care of initializing the abstracted HAL peripheral correctly for this chip architecture (nrf52833). This is of course not rocket science, but dang it just felt nice to have it work, and not have to wrestle with some mud-tier vendor HAL monstrosity.

    - the community has reached critical mass, and I think it won't be too long until there are actually more rust developers on the market than C++ developers. Plus you kind of get the full-stack experience.

  • ostenning 3 years ago

    Regarding preemptive operating systems a lightweight solution is to use rtic.rs which I have found pretty great for my time critical applications

  • ctrlmeta 3 years ago

    > (easy to do if you never, ever malloc)

    Not an embedded systems developer so an honest question. What do you do instead of malloc? Have a large array on stack and manage memory within that manually?

    • larve 3 years ago

      I mostly allocate static areas in the BSS segment. That way, I know at compile time that I allocated my memory correctly, assuming that I have my stack under control.

      Then I follow my two rules of embedded development: - no recursion - everything has to be O(1)

      If I'm honest, I can't remember a project where I had to use even a pool allocator, which you would usually need if you were trying to do like, reorderable queues / lists / trees or so. I right now can't come up with a proper use case. If you do need to say, compute a variable length sequence of actions based on an incoming packet, then I would structure my code so that:

      a) only the current action and the next action get computed (so that there is no pause in between executing them)

      b) compute the next action when I switch over (basically with a ping-pong buffer)

      c) verify real-time invariants

      My most used structure is the ring buffer to smooth out "semi-realtime" stuff, and if the ring buffer overflows, well, the ring buffer overflows and it has to be dealt with. If I could have more memory I would just make the ring buffer bigger.

      I'm not sure how clear this explanation is :)

      • ctrlmeta 3 years ago

        > I mostly allocate static areas in the BSS segment.

        How is this done from C code? Any code examples someone can point me to?

        • zen_1 3 years ago

          BSS is the section of your program's address space where all the un/zero-initialized memory lives, so just a global std::array<u64, 1024> foo{}; would be placed in BSS by the compiler. BSS is also usually not included in the actual executable size (as it's marked as NOLOAD in the linkerscript), and needs to be zero-initialized by the C runtime if you want to guarantee that .

          https://en.wikipedia.org/wiki/.bss

        • flyingfences 3 years ago

          It's not done in the code itself, it's done in the compiler configuration. We specify a mapping of memory ranges (based on the hardware and how we want to use it) and the compiler assigns addresses to variables within those ranges as appropriate. gcc calls these files "linker scripts", armcc "scatter files" -- those are the keywords to look up for examples and documentation.

        • larve 3 years ago

          Honestly just a global

             char foobar[256] = {0};
    • dwheeler 3 years ago

      It's quite common in hard real-time systems, especially in aeronautics, to only allow malloc on startup if it's allowed at all. There are many problems with malloc() and especially free() - they typically don't have any maximum latency guarantees, and even worse, what happens when you can't get memory (e.g., due to leakage or poor packing)?

      In many systems this isn't a problem. The number of engines, flaps, etc., don't change at run-time :-). If they change, you're on the ground in maintenance mode and can reboot.

    • bigfishrunning 3 years ago

      Very small embedded systems tend to have a lot of short-lived items on the stack, and anything that lives longer then a function call exists in static memory at a fixed address. Memory pools are pretty common as well. Small systems tend to avoid a tradition heap, because they can get into trouble pretty easily.

    • flyingfences 3 years ago

      Function variables, with scope and lifetime limited to the call, get their place on the stack as usual. Everything else -- i.e., constants, static function variables, and anything with higher scope -- is allocated its own memory at compile time. We have no heap. We use no variable-length arrays or other, more dynamic data structures. Anything that needs to grow and shrink does so within its own fixed-length buffer.

gbin 3 years ago

I started to explore the area, mostly arm based (rp2040 and STM) and a little bit of ESP32.

Tool chain wise:

ESP32 support is very recent and still based on the C tool chain and this makes it very fragile (you can break your environment easily and it is never clear how to recover except recompiling the entire tooolchain from 0)

Arm is a little better because the support is native.

The community is trying to make a generic embedded Hal platform API and implement it for specific devices. And it is pretty bad: almost no documentation, very few examples, tons of autogenerated code where you need to come back to the C world to understand the actual concepts.

Once you start to get going Rust is a blast to program in and the generated code is pretty efficient.

A small project I shared to help people starting on a raspberry pi clone (lilygo): https://github.com/gbin/rp2040-mandel-pico

maneesh 3 years ago

Our company builds a wearable device using an NRF5x chip, and our firmware is written in C. We are moving to Rust for our newest device, in order to run graphical output and RT operations. It is looking pretty sweet!

  • metalloid 3 years ago

    And why not Carbon? :-)

    • proto_lambda 3 years ago

      From Carbon's FAQ[1]:

      > Why not Rust? If you can use Rust, ignore Carbon.

      So that's probably one reason.

      [1]: https://github.com/carbon-language/carbon-lang/blob/trunk/do...

    • supportlocal4h 3 years ago

      Sometimes it's hard to distinguish trolls from legitimate seekers of knowledge. I'd rather feed a troll than starve a student,

      1. Carbon does not aim to replace C. It targets C++ devs. 2. Carbon is still just an idea. Well, more than an idea, but less than ready. 3. Carbon is meant for people who cannot move to something like Rust. They don't intend to compete in enviroments that have modern options.

    • norman784 3 years ago

      Not OP, also I don't know if is sarcasm or not, but Carbon is just too new, I would understand somehow if you asked about Zig, but isn't also mature enough.

      • briantakita 3 years ago

        > if you asked about Zig, but isn't also mature enough

        If you want maturity, use C. The nice thing about Zig is it's a "better C" with better interoperability & less complexity compared to Rust. Here's someone who has rewritten keyboard firmware from Rust to Zig.

        https://kevinlynagh.com/rust-zig/

junon 3 years ago

Not exactly embedded but I'm writing an operating system kernel in Rust, having done so previously with C.

I love it so far. There are still some rough edges in the tooling, but overall I'm very happy. The resulting binaries are much larger than C projects, but I'm also opting into a lot of functionality you probably wouldn't always need in smaller environments.

  • gotts 3 years ago

    Can you name a few of those rough edges in the tooling? Thanks

    • junon 3 years ago

      Right now struggling to separate out architecture-agnostic code and architecture-specific stuff in a way that can be switched out with `--target` and still undergo LTO as though it were all inlined.

      Cargo recently got multi-target builds which is great, and makes this way less of a headache before (usually had to run cargo once per target, now it can do all of them at once).

      The next challenge is packaging things up. Cargo gives you a hook for running things prior to the actual crate compilation (e.g. using it plus graphicsmagick to generate a bit font for the boot sequence rasterizer) but lacks the ability to run anything after the build, which means no development disk image building or any image checking can occur without a second command.

      There's an open issue tracking this with countless use cases but the cargo team seems reluctant to "replace build systems" (which is ludicrous to me).

      • kibwen 3 years ago

        > Cargo recently got multi-target builds which is great, and makes this way less of a headache

        Are you referring to Cargo's unstable "bindeps" feature? I ask because my company's the one that sponsored that work, and for the purposes of eventually stabilizing that feature we'd like to get feedback from users who are using it. :)

        • junon 3 years ago

          Nope. Multi-Target is where you can specify an array for build.target in .cargo/config, or multiple --target options on the command line. It just got moved out of unstable into stable about a month ago.

ostenning 3 years ago

The Rust ESP32 ecosystem isn't as mature as STM32 unfortunately, I would be cautious about beginning a production ESP32 product written in Rust.

STM32s however are a different story... I'm using the stm32h7 microcontroller with Rust in a production product. Its really great.

Hopefully the ESP32 support matures sooner rather than later, which I think would be great for more Rust IoT uptake and Rust embedded as a whole.

I would love to see Rust become the defacto standard in embedded development.

AlbertoGP 3 years ago

I took a look and realized that I wasn’t yet ready for it.

Last year a recruiter contacted me about assisting Volkswagen transition from C++ to Rust for their CARIAD embedded automotive platform.

My experience with Rust is only on application development, not embedded systems, but the job sounded quite interesting and I took a look at Embedded Rust. After discussing it a bit with them, my response to them was to recommend going with someone else, because the way Rust is used in that domain amounts to almost a different language and my experience was not adequate. I said that what they needed was someone that had battled with those arcane details already, not a Rust generalist.

I do think Rust would be good for such systems, but that at the moment it’s weighted down by architecture astronautics.

contingencies 3 years ago

Not a Rust dev but I like the language. I've learned embedded over the last few years. I used to hate C and avoid it like the plague. On a good day now I kind of enjoy it, in some sick perverse twisted way. I'd say in general the embedded space is more project-centric in general thus new stacks are not at as significant a disadvantage as in backend land at the lower end of the project complexity and regulatory ingress spectrum. You will see gaps in terms of device driver availability and maturity and integration level with silicon vendor toolkits. However, since most projects do not use too much exotic hardware writing one or two I2C or SPI drivers won't take forever, you're probably OK to go Rust. But realistically, for complex stuff, you're going to run in to wall after wall that will eat time and money. If you can meld C/C++ and Rust in one binary or reach godlike device driver implementation status then you'll be relatively free of issues but while I'm sure that's possible once you're at the point where you're writing the whole stack you might be best to look for a job with a silicon vendor overhauling their toolkit for Rust support, since base library and code generation will help shift more product than any one shop. Try writing to TI/NXP/Renesas/STM/Freescale/Microchip/NXP/Atmel's HR department and asking for scope: I'd be surprised if you received zero interest.

ecesena 3 years ago

We rebuilt solo v2 [1] firmware in rust, compared to v1 in C [2]. I haven't written any rust code so take what I say with a grain of salt, but generally speaking I think our experience has been very positive.

Rust forced us to structure the code a bit better, that makes it easier for multiple people to collaborate on it. Slightly harder learning curve, but that wasn't an issue in our case.

In the history of solo, we had a couple security bugs that rust would probably have prevented, so this is a plus for the language. Moreover, the cryptography community is pretty active and we can leverage solid + well maintained libraries.

One downside has been collaboration with other OS projects. When we had the C firmware and fido2 library, in less than 1 year we've got 3 other products embedding our code and also a couple manufacturers making demos with it -- a great win. With rust to my knowledge we're not there yet, but of course we're very positive.

[1] https://github.com/solokeys/solo2

[2] https://github.com/solokeys/solo1

dcz_self 3 years ago

I'm not a Rust-only developer, but a general systems programmer, and my foray into embedded is limited to a side project (see my profile), but no, I haven't switched to C/C++. Rust brings too much goodness to give it up.

Rust-embedded is an easy ecosystem to work with (if immature), and if you want more flexibility, Tock OS [0] is trying to cover that space (also immature, but I'm working on it).

[0] https://www.tockos.org/

adolph 3 years ago

I’ve been super curious about both Rust and ESP. It seems like Espressif is interested enough to commission a Rust dev board (ESP32-C3-DevKit-RUST-1) and training using it.

https://github.com/esp-rs/esp-rust-board/

https://github.com/ferrous-systems/espressif-trainings

  • detuksOP 3 years ago

    That is what I felt as well. Managed to create simple project where MCU connects to WiFi, announces itself on network (mDNS) and gives me a tcp api to turn led on and off. I used ESP32-S3.

    Ability to use STD and their rust creates made it very easy to do.

    Tho most of their rust creates are just wrappers around C.

phoehne 3 years ago

I recently moved to an embedded software role. I sought out this move because I love writing code for very small computers, fiddling with registers, Blinky lights, etc. I didn't realize how important an accredited RTOS was for many commercial projects. It's not really about middle-aged devs being stuck on grungy-old tech. It's about being able to support a device for the next 10, 20, or 30 years with a consistent tool chain. This is especially true for devices that have some sort of industry accreditation, like medical devices. It's very expensive to get that accreditation and if you change the embedded language, that will probably start the process all over again.

That doesn't mean there aren't companies looking at Rust, because C code is hard to get right. We use a mix of emulators, FPGA simulators, unit testing, static analysis, manual code reviews, and various coding standards to try to avoid introducing a bug. It would be nice to work in a language that took certain classes of memory errors off the table. I'm sure Rust has some monsters lurking in the shadows, but at the level I've tried to adopt it, I haven't found them.

With that out of the way, I like Rust. I think there's good quality basic tooling like IDE support and debugging. but It's a little bit of a crap shoot as to what board support you can get. A lot of times I've been able to get by with "close enough" HAL crates. Chances are you will find a board but may have to write a driver for a particular device. (By driver I mean something that understands how that device works and knows what bits to write at which address to set registers, or I2C commands, or whatever). It's not as bad as it sounds, but you will learn to read spec sheets. Many CPU's are a mix of features supported by the IP the chip manufacturer decides to put into their product. Like they might not support some optional components in that specific chip.

Be careful about ESP32. I believe some are based on the Extensa cores and I think some are based on RISC-V. Those are different architectures. I agree with the people below that recommended STM32 which is a more predictable ecosystem since they're all ARM. M0, M3, M4, etc. are all well understood, highly supported cores. ST's licensing (I believe) requires you get a license for the IP before you can distribute a commercial product based on their architecture. I haven't looked at Espressif's restrictions.

_whiteCaps_ 3 years ago

Oxide has an embedded Rust RTOS:

https://hubris.oxide.computer/

LaffertyDev 3 years ago

I'm a rough beginner in the embedded space. Would love to use Rust, but in my experience it becomes more hassle than its worth for some of the more off-the-shelf controllers. Would love to find more knowledge / tutorials to help guide me in the right direction, though.

jmartin2683 3 years ago

I write Rust full-time for work and use it on embedded projects for fun. The ecosystem around embedded-hal is great.

nottorp 3 years ago

Being dependent on any programming language is a trap. For your esp you need intimate knowlegde of the hardware and the language is irellevant.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection