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


#61

The problem with debugging on the Teensy is that the bootloader chip is currently driving the SWD pins. Paul (the creator of the Teensy) put in a facility to allow the boot chip to stop driving the pins so that an external debugger can be used, but the firmware for the boot chip hasn’t been developed yet.

See: https://mcuoneclipse.com/2017/04/29/modifying-the-teensy-3-5-and-3-6-for-arm-swd-debugging/

I like the STM32 series because it has on-chip bootloaders in ROM, so you don’t need any special external hardware to flash the devices (other than USB or serial). I normally use DFU with the STM32F4 series.


#62

Thanks for that link. The KL02Z Pins PTA0/14 and PTA2/16 seem to be the SWD for the bootloader chip itself. I’m not sure if the schematic is showing those pins as routed to a pad or something else, I guess I’ll look when I’m home. Maybe I can hack in putting the K02 side of the K20 SWD lines into hi-z.

I do also have an STM32F407 demo board laying around too. So I think that could always be a start point for just poking around w/ rust & the black magic probe. The STM32’s are nice to work with, but I also understand the attraction of having the external bootloader, at least with respect to trying guarantee that the device is recoverable after any given program update.


#63

With the onboard bootloaders in the STM32 I’ve been able to recover from pretty much anything. Even locked devices can be mass erased. The only unrecoverable thing I’ve found is that if you set the read-protection to the max level, then it disables ROM bootloader as well as the JTAG.

I haven’t yet tried my Black Magic probe with the discovery board (I also have one of those), but it should work.


#64

Good to know ‒ it makes some sense the rust code doesn’t take it from header files. It didn’t feel exactly right that way, but it works as the initial proof of concept (and the application seems not to crash). But I’ll keep it in mind for when someone gets to the proper support… but it’s also possible we’ll switch that device to musl prior to that.

No, we can’t. But we could get inspiration from their work, at least.

The docker file for the build slave is an internal repo (there’s probably nothing secret, but I don’t have the rights to make it public now). This is the hacky way.

FROM ubuntu:16.04

ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.cargo/bin RUST_BACKTRACE=full CARGO_INCREMENTAL=1 RUST_TARGET_PATH=/etc/rustc/
ENV CC_powerpc_unknown_linux_gnu=/OpenWrt-SDK-mpc85xx-p2020-nand_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/staging_dir/toolchain-powerpc_8540_gcc-4.8-linaro_uClibc-0.9.33.2/bin/powerpc-openwrt-linux-gcc CFLAGS_powerpc_unknown_linux_gnu="-mcpu=8540 -fno-caller-saves -fhonour-copts -Wno-error=unused-but-set-variable -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2" NIGHTLY=nightly-2017-04-15
RUN apt-get -y update && apt-get -y install --no-install-recommends curl ca-certificates build-essential git pkg-config libzmq3-dev && curl https://sh.rustup.rs -sSf | sh -s -- -y
RUN rustup toolchain install $NIGHTLY && rustup default $NIGHTLY && rustup component add rust-src && for pkg in clippy rustfmt xargo cargo-external-doc ; do cargo install $pkg ; done && cargo install cargo-bundle --git https://github.com/burtonageo/cargo-bundle && rustup default stable && rustup target add armv7-unknown-linux-musleabihf && rustup target add i686-unknown-linux-musl

# Download the cross compilers/SDKs for the openwrts
RUN curl http://repo.turris.cz/turris/OpenWrt-SDK-mpc85xx-p2020-nand_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64.tar.bz2 | bunzip2 -c | tar x
RUN curl http://repo.turris.cz/omnia/OpenWrt-SDK-mvebu_gcc-4.8-linaro_musl-1.1.15_eabi.Linux-x86_64.tar.bz2 | bunzip2 -c | tar x

# This one just points to the linker
COPY cargo-config /root/.cargo/config

Its used in this repository:
https://gitlab.labs.nic.cz/turris/pakon-aggregator (called from the .gitlab-ci.yml).

One of my colleagues works on the proper integration into build bot, but I can’t find his code right now and he took some time off :frowning: . But AFAIK it’s nothing working yet. I’ll ask him once he returns.

To be clear, this all looks a bit broken from outside and it probably is, but the main problem (except for a lack of beginner style tutorial „how do I cross-compile for my device“, which is fully understandable) is just too much work and too few people to do it, so the proper solution simply didn’t get a high priority yet. I mostly wanted to let you know there are needs like ours so you have the complete picture.


#65

Another useful library for developing embedded tooling that seems to be missing is one for working with device trees. There is a device tree crate on crates.io but it hasn’t been updated in a year and was only downloaded 800 times, so I’m not sure how usable it would be at this point.


#66

@japaric I was wondering if there are any good crates for serialization/deserialization of data on microcontrollers? I opened this issue on capt’n proto to see if it is possible to make that work on microcontrollers but I was wondering if there is an out of the box solution.

Thanks!


#67

@japaric It sounds like you’re familiar with the work being done on AVR but don’t plan on making that a priority for your work; is that correct? It looks like AVR is currently expected to be stabilized in the 1.20 release, but it’s already been pushed back a few times, and I understand that even with the recent merging of the LLVM 4.0 PR there are still quite a few critical issues left to address. I’d personally really like to see AVR support get stabilized this year because I think it might help persuade more people at my company that the language is worth looking into.


#68

Hey @japaric, love your work and recently became a patron!

I’ve done C work in microcontrollers, and am starting to dable in developing libs in rust. My focus is on embedded communication (rpc) and memory management, as I think those will have the highest impact on usability.

  • defrag-rs is a safe defragmenting memory manager for microcontrollers which alows that space to use dynamic memory allocation without concerns of fragmentation. This library is written and passes unit tests, although I’ve never actually used it on a microcontroller
  • ubr (placeholder name) is in the early design phase (haven’t written a line of code yet) but I think after my THIRD attempt at designing a truly light weight bridged communication protocol that I have suceeded. I think this design is actually implementable, and doesn’t even require dynamic memory allocation! Horray!

Unsurprisingly, I think these are the two areas that microcontroller support needs love in general (not just in rust)


#69

@vorner

But I’ll keep it in mind for when someone gets to the proper support

BTW, someone landed mips-uclibc support in libc and it’s now available since release 0.2.23. Now if you try to compile libc for an unsupported uclibc target like power-uclibc you’ll get a compiler error instead of possible runtime segfaults.

@vitiral

I was wondering if there are any good crates for serialization/deserialization of data on microcontrollers?

I haven’t seen anything that I particularly like but ssmarshal seems usable in no_std without dynamic allocation. I would prefer something like I sketched here (also read this); that is something that e.g. doesn’t return a Result when serializing if the operation can’t fail. Unlike like in std-land in embedded errors are likely fatal and/or have to be dealt with locally – you can’t just bubble them up and report them to the user – so the less Results I have to dealt with the better.

I personally just use byteorder and write the struct serialization / deserialization myself.

Hey @japaric, love your work and recently became a patron!

Thank you! :heart:

My focus is on embedded communication (rpc) and memory management

You should tag those crates with the “embedded” and/or “no-std” categories to increase their discoverability on crates.io. You should post them in this thread.

It sounds like you’re familiar with the work being done on AVR

I wouldn’t say I’m familiar; I keep an eye on it as several other people do.

but don’t plan on making that a priority for your work; is that correct?

What AVR on Rust needs most right is fixing bug in the LLVM AVR backend so one can compile the libcore crate for AVR. That’s something I can’t really help with as I’m not familiar with LLVM internals or with the AVR instuction set.

However, once the AVR backend is in tree I plan to look into the tooling / project templating for it.

It looks like AVR is currently expected to be stabilized in the 1.20 release,

That sounds rather misleading as the AVR backend has not even been enabled in rustc and there’s no built-in target for it.


#70

I definitely would like to get involved with Rust’s embedded story. A little bit of background:

  • I have been programming for ~10 years, I started self-taught, but now I have one bachelor’s degree in Computer Engineering and another in Electrical Engineering
  • I have designed a fair amount of embedded hardware using KiCAD. (from Apollo to a tiny Cortex-M0+ bluetooth breathalyzer, and several things in between)
  • I think Rust is awesome

I work as a full-time software engineer these days, and I travel a lot on weekends, so I don’t have a ton of free time, but I would love to find ways to make Rust the embedded language people reach for first. All of these embedded boards that come pre-flashed with Python and Lua interpreters are a cry for help: No one would ever claim those languages are a good fit for resource-starved environments like a microcontroller. Yet, they’re there.

In my opinion, this is because the existing C/C++ toolchains are a nightmare for embedded dev for people who are not SMEs. Even once you have the toolchain up and running, you either use mbed or Arduino or you spend all your time bit-twiddling registers at specific memory addresses, and none of those options are great.

I’ve had huge problems with reproducibility of mbed builds. Building offline is nearly impossible. Building through the online compiler works really well, 90% of the time. I’ve had several instances where a project simply stops working a few months later, and the only way to get it working again is to create a new project, and copy-and-paste the old code into it. Updating the dependencies didn’t fix it. Nothing would. That is a scary notion for commercial projects.

With the Arduino APIs, the problem often boils down to performance and AVR-centrism. Reading and writing from pins is monumentally slow through the standard API, for instance. With the AVR-centrism, many libraries only work on AVR, or work suboptimally on non-AVR platforms.

Bit-twiddling on memory mapped registers by reading a 1,000 page datasheet is pretty self-explanatory. No one relishes this task, and there is a number asymptotically close to zero that represents the number of high-level developers that are excited about microcontrollers so much that they get into this. MSP430 in a nutshell is bit-twiddling, from what I’ve seen. Like, no libraries whatsoever. I think after much digging I finally found an official or semi-official MSP430 library that abstracted away more than absolutely nothing, but I had given up by then.

All of this is to preface the idea that Rust is a prime candidate for making embedded development suck less. I didn’t even mention all of the memory unsafety issues that are common in embedded C/C++, which just causes the microcontroller to crash if you’re not careful enough.

My metric library is even designed to work on #[no_std], so it should be good for embedded applications at zero runtime cost.

I have a Saleae digital logic analyzer, I have a fair chunk of microcontroller dev boards lying around, including an STM32-Nucleo F334R8, an MSP432 (ARM) board or two, Arduino Unos, a PIC 16F1619 Curiosity board, Raspberry Pi 1/2/3/0/0W, among others. I have access to Linux and Windows computers.

I just need direction on what to do to help embedded Rust.

And finally, when can we make embedded Rust development possible from Stable? Even @steveklabnik has mentioned (IIRC) in the past that Embedded development is considered a priority target for the Rust core team, but forgive me if I say that it doesn’t seem that way when embedded development is wholly relegated to Nightly. I can understand wanting to use features Nightly has to offer, but it shouldn’t be mandatory, and there just cannot be any huge obstacles to stabilizing a bare minimum at this point, IMHO. Even the asm! macro is long overdue on being stabilized. But, I’m sure these things will… eventually… come with time.


#71

I’ve been looking for something that supports PWM, might be time to write it myself. I’m not really sure yet where to start. I have access to a few kinds of ARM dev boards. Mainly a particle photon, and a teensy 3.1. I’d love to start playing with the teensy more, but the library support is still really bare.


#72

I ported MicroPython to the Teensy 3,1, including the timer code used to generate PWM.
https://github.com/micropython/micropython/blob/master/teensy/timer.c
with the lower level code which does all of the register manipulation here:
https://github.com/micropython/micropython/blob/master/teensy/hal_ftm.c
In particular, look at the HAL_FTM_PWM_Xxx routines.
In the datasheet (for the mk20dx256) you’ll want to look at the FTM module.

The overall essence is that you configure the timer period and prescaler to setup the overall period of the PWM, and then you use the channel CV (value) register to set the duty cycle. Each timer can have multiple channels.

This is a MicroPython file for setting up PWM on the Teensy 3.1:

I haven’t played with rust on the teensy yet, but I’ve done a bit of rust on some of the STM32 processors. If needed, I can whip up a C example, but would need to work a bit more to do one in rust. Feel free to ask questions.


#73

One thing that is sorely missing is lldb/gdbserver support. gdb causes me nightmares…


#74

have you tried https://github.com/m4b/scroll ?

It should work in no std, allows zero-copy str reading, reading at offsets, custom deserialization for structs (even with lifetimes for zero-copy!), and if you don’t like results it exposes a pread_unsafe api. Also I suppose I’m biased but I just find it easier to use because I don’t have to type out the type in the function name, and because it’s extensible it’s always the same API which makes refactors easier and localized.

Also the errors don’t allocate anyway, and should work in no_std.

I’d love to have some feedback in no_std settings, etc.

Unfortunately seems you can’t use proc macros in no_std (or at least I can’t get it working :/), otherwise, custom reading/writing is as simple as:

#[derive(Pread, Pwrite)]
struct Foo {
  some_field: u32,
  another_field: f64
}

let foo = some_bytes.pread::<Foo>(0)?;

#75

@bestouff

Hey, I was reviewing our list of embedded crates and found a crate for working with the PRU on the Beablebone (Black): prusst. It’s on crates.io so that may be a good starting point.

@coder543

Welcome aboard! Two things that need to be prioritized right now are (a) improving discoverability of the embedded ecosystem, and (b) work on a HAL that we can all agree on.

For (a) we need a single place where one can get information about the embedded ecosystem (e.g. areweembedded.com): What tooling do I use? How do I start developing programs for microcontrollers? What crates should I use? What development boards / platforms are supported? This last part is important to form clusters of developers around development boards so they can collaborate and improve support for them.

About (b) I think that the tooling and crates for bit-twiddling are in good shape. There’s one crate for doing concurrency and one embedded OS out there as well. You can certainly build applications using registers but we need to go one step further and create a slightly higher API that works across different devices to ease the development process: that’s the HAL. I have worked with quite a few peripherals (UART, Timers, SPI, PWM, Quadrature Encoder, ADC, etc.) of the Blue Pill. I want to pull the APIs I have been using into a crate and use that as a starting point for the HAL. That crate will then undergo a lot iteration to make sure it works for several devices and that the API is appropriate for different applications.

I’m working on those two points right now. I’ll post some links here after I get to a more concrete starting point.

@m4b

Nice. I’ll give it a try this week. Sometimes that I haven’t seen in the libraries that I have checked out, like serde and ssmarshal, is that if the operation ([u8] -> T or T -> [u8]) can’t fail then I want either Result<T, !> or no Result at all in the return type. Neither library gives me that; I hope scroll does better in that regard :-).


#76

Yeah I saw this one, but it’s just a crate for code uploading into and setting up of the PRU.
Maybe the PRU doesn’t need to be programmed in rust after all, its assembly is quite simple.


#77

I haven’t used Rust on an embedded target yet, so I’m not really up to date what’s currently possible and what’s not. However I haven’t seen much discussion around using RTOSes, so here goes:

At work, we use C++ (and some C) on top of an RTOS to program our microprocessors. Since there is no language support in C++ for threads and mutexes (at least pre-C++11), one normally links against the RTOS code and calls the API of the RTOS to do whatever one wants to do (create tasks, lock mutexes, and so on…).

While that could probably work as well in Rust (through FFI bindings), I’m not really sure how the Rust compiler would behave if you tried to share data across tasks (would it even realize that?) and whether it could enforce data race freedom in such a setup. (I would intuitively say no, which would mean that you would lose a lot of the benefits of Rust if you went down that route…)

Given that Rust supports threads and mutexes natively, it would be nice if one could just write thread::spawn() and Rust would automatically call the corresponding FreeRTOS, Micrium, etc. functions. Same with mutexes and events and the likes. (Even though I wonder how one would handle task priorities or stack sizes in such a setup.)

Is such a thing already possible or could it be possible one day?


#78

(a) improving discoverability of the embedded ecosystem

I have made some progress on this and I would like some feedback.

First, I have created an issue about increasing the discoverability of
embedded crates on crates.io using categories.

Second, I have written a draft for the areweembeddedyet.com website and I’d
like to get feedback about what else should go in there and on how the organization
of the content could be improved.

Please comment on the respective issues / threads.


#79

@therealprof

One thing that is sorely missing is lldb/gdbserver support. gdb causes me
nightmares…

Do you mean this in general or in the particular case of debugging embedded
programs?

I have used lldb before to debug a bare metal Rust program running on an
emulated ARM Cortex-M microcontroller (QEMU) and it worked fine: Stepping and
breakpoints worked fine.

I haven’t found an equivalent to gdb’s monitor command in lldb so I haven’t
been able to debug embedded programs on real microcontrollers. But I read
somewhere that embedded C programs have a similar problem so I’m not sure if
lldb can actually be used to debug embedded programs on real hardware. (If
someone knows how to do that I’d appreciate if you could tell how it’s done.)

If we are talking about Rust program debugging in general then gdb is in better
shape than lldb since the gdb project officially supports Rust and has built-in
support for pretty printing and expressions since v7.12.

@belegor

However I haven’t seen much discussion around using RTOSes

Have you seen the Real Time For the Masses (RTFM) framework? (Also see
this blog post) It’s a lightweight framework (not a full blown OS) for
building concurrent applications but it’s ultimate goal are real time
applications (though we can’t claim real time-ness without proper scheduling
analysis tools).

Its concurrency model is based on event triggered task (instead of on threads),
which can be prioritized (= preemptive multitasking), and resources, which are
global variables with memory safe, data race free and deadlock free access
guaranteed at compile time.

I’m not really sure how the Rust compiler would behave if you tried to share
data across tasks

The Rust language is not tied to any particular concurrency model; instead it
has two marker traits, Send and Sync, that can be used to build concurrency
models. Those two plus the borrow checker has been enough to build a memory safe
thread-based concurrency model in the standard library, and to build a memory
safe task-based concurrency model in the RTFM framework.

So provided that you correctly apply these markers to your rustic API on top of
the unsafe C bindings to a RTOS then you can have the compiler prevent data
races like it does in std code. Of course the compiler is no panacea; if the C
implementation has memory safety bugs then you Rust layer on top of the C code
will also have them.

Given that Rust supports threads and mutexes natively, it would be nice if one
could just write thread::spawn() and Rust would automatically call the
corresponding FreeRTOS, Micrium, etc. functions.

Modeling an API after the std API on top of bindings to C RTOSes is certainly
doable. OTOH adding support for one of these RTOSes to the standard library
sounds less likely. Not everything in std can be implemented on top of a RTOS
(std::net::lookup_host, std::env, etc.) and std has no API for some
features that RTOSes have (like task priorities).


#80

lldb support for embedded devices is in general pretty meh. The only really well supported cases a local debugging or debugging using lldbserver.

Well, if you have a remote process talking the gdb-server (i.e. you’re using platform select remote-gdb-server) protocol over TCP/IP, that’s to address easy, simply use:
process plugin packet monitor
to fire off your “monitor” commands.

The whole thing quickly ugly though if you don’t have a remote TCP/IP gdb-server but one that talks the protocol over a PTY (like a BlackMagicProbe) because the shortcuts are utterly broken and you have to use commands that will actually accept a “file” instead of a networking socket.

Which goes right over to the next problem: lldb makes certain assumptions about the way the GDB protocol is spoken and if the developers of the other side had a different idea, like the BPM folks, then it gets ugly quickly…

I have to admit I haven’t done too much native debugging of Rust code yet (neither with gdb nor lldb) since many of my debugging usecases do not even exist with Rust (unlike C/C++), for embedded developing this is a lot more critical since a debugger is often then only useful way to see what the device is doing at all. But quick tests with the Apple lldb shipped with El Capitan show quite decent support for Rust out-of-the-box, I can easily set breakpoints, step through Rust code, print expressions and data structures, get correct type information and even look at the inner workings of std traits. Maybe it’s not complete or has improved in later versions but for something shipped out-of-the-box I’ve seen far worse in my life…