Rust for embedded development: Where we are and what's missing


#41

Currently, I use the binary release of armv7-unknown-linux-musleabihf. That one works, but we want to link against the dynamic musl. This doesn’t have a very high priority ATM, because it somehow works.

The PowerPC is more problematic. There’s only powerpc-unknown-linux-gnu, which dynamically links against glibc. That doesn’t work, since we use uclibc on that device (and want to switch to musl in the future). There’s no statically-linked target available for powerpc. Currently, using the toolchain for all other packages and feeding it to Xargo, pretending to build powerpc-unknown-linux-gnu against that uclibc works, I assume uclibc has a different ABI, so it needs to compile against its headers and link against that.

My aim was to get to a similar state with the ARM device ‒ build it using the same toolchain and link against the relevant libraries as the rest of the packages. But the experiments have somewhat low priority, once it was proven we can get a working binary (basically, I concentrate on the development and left it to a colleague maintains the packages and the build machinery).

Yes. The whole firmware/distribution (based on OpenWRT) is compiled by something called „buildroot“. That is a bit of a beast ‒ it starts by building its own cross-compiler and all the libraries. Unless I use the same compiler/linker, the compilation either fails or produces something that crashes at runtime. I probably could persuade some kind of system-wide cross-compiler to work, but I don’t really know where to start (despite coding for routers, I have little experience with cross-compiling ‒ most of the time, I just write and test the code on my desktop and then leave it up to the package maintainers to make sure it compiles) and it would likely be more work than just using the already-working tool chain. It also feels more „right“ to use the same as the rest of the compiled system.

That’s what I’ve used eventually. But that’s not really the first place I look for documentation, to be honest.

Currently, I don’t use any custom triple, it was part of the experimentation ‒ but some day I might want to tweak some of the target CPU features (For example, we can’t run debian binaries on that powerPC, because it is missing some common extensions. I don’t know why the rust program runs, if it’s because the produced binary doesn’t rely on them or I haven’t used any feature that’d benefit from them).

That’s good news. I didn’t pay attention to it lately, I just hope this gets available before we get to release O:-).

Anyway, we already decided Rust is in a good enough state to use for these needs as it is, but any improvements will be very welcome anyway. If you are interested in further details, I’ll be happy to share them (after all, what we do here is open source).


#42

@japaric thanks for putting this together, I’m glad someone has full time to focus on Rust on Embedded. To be honest, this is probably the biggest thing missing in the ecosystem - someone who can contribute full time with both code and documentation publicly. I’m sure there are already people working on embedded in Rust, but sadly can’t share as it is company knowledge/property. Also thanks for the blog post plug :slight_smile:

Currently I am working on integrating Rust with the Nordic Soft Device for the nRF52, and will be presenting this to the DC Rust community mid-June. Hopefully this will be useful for people like @Michael-F-Bryan and @therealjpster who are looking for information there. Hopefully after I get the Nordic Soft Device integration to a point I am happy with, I would like to focus on an RTOS like MyNewt, Zephyr, or RIOT-OS - since I think that integrating rust above the HAL/OS layer will actually be very useful for a lot of developers, and reduces the surface area where Rust has to interface with C code directly. It also gives us a soft introduction to Rust on embedded, where we still play within the constraints of an embedded environment, without having to rewrite existing drivers or existing libraries.

I wrote a quick intro here: https://jamesmunns.com/update/2017/05/23/rust-embedded-1.html - but it is meant to be background information for those not very familiar with embedded and/or Rust. I will mostly be focusing on Cortex-M.

I also agree with @jcsoo - Cargo does provide most of the package management utilities needed today, but I think we will begin to hit struggles when combining crates which act like board support packages, system libraries (like hardware drivers), “userspace” libraries (such as encryption libraries) and other general purpose components. Right now the best approach is what @japaric is using, with a combination of Crates and Templates. For example, I ended up “injecting” a linkerfile from the BSP crate into the host project, See here, which I’m not sure is a great practice.

Unfortunately this is still a “hobby time” project for me, which means my progress is slower than I would like. Hopefully I can help put together some tools and approaches which other people can take and build upon.


#43

Here’s what I’ve found when doing local tests with a no_std lib:

  1. When tests fail it doesn’t print out the left and right side of an assert_eq!() call. So the test results do look different and prevent basic debugging until you use GDB. And I don’t usually use GDB, mostly because I’m not very comfortable with it (though I should really get better at it!), instead preferring printing debugging info. People who come into this from the conventional programming side will likely take this approach as well, so I think it’s important that this is useful.
  2. Using std in test code (to use println!() for example) is impossible if you have the crate no_std. This is where the #cfg[] stuff I mentioned in my first post came from. What I’d like is to declare the library no_std but then have the test portion actually be std.
  3. Related to 2, I’d expect a crate compiled with no_std to also work when compiled with std, but that doesn’t seem to work because core isn’t included in those contexts. Is there a reason for that?

I should have been more clear, I did think this was standard practice for embedded devs, I just never saw anything come out for Rust embedded devs. I’ve only seen non-host testing discussed, even though I always thought that work would come after host-based unit testing, which as I talked about above is pretty unergonomic.

The reason I brought up utest is that it was in your list of crates useful to embedded devs. This isn’t really a problem, but nowhere does that crate talk about best practices for unit testing on the host and that the crate isn’t the solution to that with a link referring people to guidelines elsewhere. This is not really a fault of utest, I just wanted to point out that there aren’t any best practices discussed anywhere for Rust embedded devs to do host-based unit testing.


#44

@jolson

Thanks for letting me know. I have created an issue with some ideas to move
your case forward. Further details would be appreciated! (you can comment on
that issue).

@vorner

Currently, using the toolchain for all other packages and feeding it to Xargo, pretending to build powerpc-unknown-linux-gnu against that uclibc works

I would advise against doing that. libstd calls C functions using the signatures
defined in the libc crate. With your approach you are compiling libc for a
glibc target (cfg_env = "gnu") thus your programs will be calling uclibc
functions using glibc’s signatures. That can result in crash / segfault at
runtime due to ABI mismatch. The proper solution is to add uclibc support to
libc
and to use an uclibc target (env: "uclibc" in the target
specification). To be clear: using a custom C toolchain won’t save you from this
problem; libc is what needs to be fixed.

The whole firmware/distribution (based on OpenWRT) is compiled by something
called „buildroot“.

I know there are people working
on integrating rustc / cargo into yocto, and I know that yocto targets
the same embedded Linux / router space. But, I suppose you can’t use yocto for
your work because OpenWRT is already using buildroot?

Unless I use the same compiler/linker,

That makes sense. You should definitively continue doing that. You should
compile the C bits of std with the openwrt C compiler, and use the openwrt C
toolchain to link std programs.

we already decided Rust is in a good enough state to use for these needs as it

:+1:

If you are interested in further details, I’ll be happy to share them

Sure. You can just point to some repo and I’ll take a look.

@jahmez

Currently I am working on integrating Rust with the Nordic Soft Device for the nRF52

Awesome! I’ll be looking at the nrf51 so hopefully there will be common stuff
that we can share.

See here, which I’m not sure is a great practice.

The cortex-m-rt crate does the same thing. I don’t think there are any better options ATM.

I also agree with @jcsoo

Well, hopefully their tool will solve all our problems. We won’t know until they
release it.


#45

@susurrus

When tests fail it doesn’t print out the left and right side of an assert_eq!() call.

Do you have any code where that happens that I could look at? The library code below prints error messages as if it were a std library:

// remove this and you get the same output
#![no_std]

#[test]
fn it_works() {
    assert_eq!(1, 2);
}

Using std in test code (to use println!() for example) is impossible if you have the crate no_std

Right. You can only use core code in no_std contexts, even within #[test] context. Unless you link your no_std crate to std only when testing:

#![cfg_attr(not(test), no_std)]

#[test]
fn it_works() {
    println!("Hello");

    assert_eq!(1, 2);
}

Related to 2, I’d expect a crate compiled with no_std to also work when compiled with std, but that doesn’t seem to work because core isn’t included in those contexts.

If you want your crate to work both with and without no_std it’s better if you don’t have imports like use core::fmt where the name of core is hardcoded. But that’s usually unavoidable so you can use one of these in the root of the crate:

#![cfg_attr(not(test), no_std)]

#[cfg(test)]
use std as core;

// OR
#[cfg(test)]
extern crate core;

I agree that we should have more docs about this!


#46

How do you plan to use Rust in your embedded application?

  • I want to write pure Rust applications, on Cortex M0(+),M3,M4 style micros

And, what’s preventing you from using Rust?

  • I just haven’t come around to it yet due to time constraints

#47

Awesome! Even without a blogpost this is really cool! Any half baked code snippets available somewhere? I’m attending the “Gulash Programmer Nacht” This (extended) weekend, so will have multiple consecutive hours to polish something.

Using DMA sounds like the perfect way to do it. I’ve seen chatter about neopixel DMA libs in the general Particle ecosystem, but haven’t tried them yet.


#48

I do not see the TESSEL2 mentioned, so I like to add it.
Hardware Overview of Tessel 2


#49

I am working on a personal project about tensegrity robotics. At the moment is a simple Arduino prototype, but I would like to grow it in a more complete project.

Being a personal project, I am using a mix of technologies that I would like to test. I would like to use Android Things for UI, together with android-rs-glue to control things and main computation.

When I will have more feedback to give, I will back to this page :slight_smile:


#50

@andete

I just haven’t come around to it yet due to time constraints

I feel you. Are you targetting any specific application space for your future embedded Rust adventures?

Any half baked code snippets available somewhere?

No :-(. I made that application before RTFM v0.1.0 when the framework looked before different from what it looks today so the code no longer compiles and it’s not trivial to update unless you know about the old RTFM API. I’ll see if I can clean it up and put it up somewhere before the weekend.

But the basic gist is: you set the PWM frequency to something that makes sense for the WS2812 (50KHz? 100KHz? IDR the exact number right now). Each PWM period is one bit send to the WS2812. The bit will be 0 or 1 depending on the duty cycle of PWM period (e.g. 20% = 0, 60% = 1). So you set the DMA to change the duty cycle on each PWM period. The DMA will change the duty cycle according to some buffer where each byte of the buffer is equal to one PWM duty cycle which is equal to one bit send to the WS2812.

@MicMac

Has the Tessel community run into any particular problem while using Rust? Given that the Tessel runs Linux I expect most of their problems might be due to binary sizes and lack of dynamic linking.

@wbigger

a simple Arduino prototype,

Is that Arduino an AVR microcontroller or an ARM Cortex-M one (Arduino Zero, IIRC)?

When I will have more feedback to give, I will back to this page

:+1:


#51

Is that Arduino an AVR microcontroller or an ARM Cortex-M one (Arduino Zero, IIRC)?

I am using both Arduino Uno and Arduino 101. The second one is particularly useful when I need bluetooth connection (the chip and antenna are integrated in the board)


#52

Re: OpenOCD reimplementation: You might find the black magic code interesting, even if just for reference. https://github.com/blacksphere/blackmagic

It’s gdb oriented, but is a fairly compact codebase targeting debugging on the ARM Cortex family. I’ve had one one of their kickstartered V2.1 JTAG/SWD probes sitting on my desk for a week ready to try out, but the codebase seems to support a number of probes & targets.

I just need to find some time to sit down and fire it up for my hobby project to get some Rust code running on a Teensy…


#53

I have a Black Magic Probe v2.0 sitting on my desk and a BMP v2.1 + 1Bitsy on preorder, and I’ve looked through their code many times. There are upsides and downsides to having the GDB protocol be your main interface; implementing a pure Rust GDB client library could be useful in the long run.

Rust runs great on a Teensy. However, I don’t think any of the current Teensys support SWD or JTAG debugging because the main MCU debugging pins are used by the bootloader chip to load code into memory. The FRDM boards support the same MCUs and have a built-in OpenSDA-compatible debugger, so I often use them to work through driver development and then use the code unmodified on the Teensy.


#54

If you haven’t seen it, Paul Stoffregen’s OctoWS2811 LED Library takes a slightly different approach using DMA triggered by timers to drive 8 LED strips in parallel, writing a byte at a time directly into GPIO. I might need to order myself some LED strips to see how well this works.


#55

@jcsoo If you’re planning on using the black magic probe with the cortex-m-semihosting crate, then you’ll be interested in: https://github.com/japaric/cortex-m-semihosting/issues/5


#56

@jcsoo

a slightly different approach using DMA triggered by timers to drive 8 LED strips in parallel

That’s smart. The WS2812 LEDs I have come arranged as a ring and only expose a single data pin so I can’t use that approach in my case.

cortex-m-semihosting

Semihosting is soooooo slow. Everyone should be using ITM instead, if you only need one way logging / printing. It would be nice to have something like Segger’s RTT (Real Time Transfer) in Rust; Segger claims is even faster than ITM, and that makes from looking at how it’s implemented. It would probably require some integration with OpenOCD though. cc @whitequark ^ Semihosting alternative


#57

Ah well, now I see it on the Teensy schematic. Ah, I could access the bootloader’s SWD pins :slight_smile: I’ve been away from the smaller side of embedded dev for a few years now. It’s so odd that see the bootloader flash chip is oh, just another even smaller MCU…

The GDB access is a bit atypical, I’d agree, but interesting enough to pick up the black magic, and as an excuse to play around with it and Rust on the smaller embedded side.


#58

Yes, unfortunately both semihosting and ITM have issues; semihosting is very slow and requires that you are running a debugger, and ITM is not available on the M0+ and requires a debug probe that supports it, plus it’s one way. RTT seems nice but requires a debugger with fast target memory access and is currently only supported directly by Segger’s libraries; once I get around to understanding the various debug probe APIs, I will probably try implementing something like it for STLink and CMSIS-DAP to see how well they work for this purpose.

Realistically, the closest you will get to a universal solution is a two-way serial protocol so that you can send test parameters (enabling / disabling tests, changing log levels, etc.) and collect results + traps + panics. With a little more work you could add the ability to upload code directly to flash or SRAM so that you don’t need to use an external flash loader.


#59

@japaric I am not able to use ITM. The cheap adapters (FTDI and the like) do not seem to support ITM together with OpenOCD (and with all due respect I simply do not have the resources to fix OpenOCD; I quite rather feel this is within the scope of the larger Rust-on-Cortex-M project). Conversely, I would love to have a Black Magic probe, but right now they’re unobtainium.

Segger’s J-Link is a nightmare and I will never touch it again. Last time I bought a device from a distributor it turned out to be counterfeit. Their driver instantly tries a firmware upgrade with no prompt or option to turn off and bricks a device. Restoring it by reverse-engineering firmware dumps found on random Internet forums and breaking their protection mechanism once was a hassle; when it turned out I have no way to download an old J-Link software version and the oldest one they had bricked it again I tossed the thing into trash. Segger can go choke on their intellectual property.


#60

Various other projects (notably Ubuntu and LibreOffice) have had good success highlighting “papercuts” or relatively small issues that cause friction and can add up to a negative experience. I wanted to do the same for Rust embedded. A tracking issue of the various problems I and some others have encountered (some of which I mentioned earlier in this thread) are in the tracking issue rust-embedded/rfcs#26. I’d love to hear thoughts from people on those papercuts and any other ones that they may have experienced. These should be relatively minor inconveniences, difficulties, or annoyances from the user’s perspective, not from an implementation perspective.