Hacker News new | comments | show | ask | jobs | submit login
Programming ARM Cortex-M Microcontrollers with Rust (github.com)
179 points by randtl on Apr 9, 2017 | hide | past | web | favorite | 57 comments



The author of this guide (Jorge Aparicio) has done an amazing amout of work getting the ecosystem started for Rust on microcontrollers. A few of his other projects worth mentioning:

- svd2rust (https://docs.rs/svd2rust/) generates register-level APIs from SVD files, an XML format provided by most ARM microcontroller vendors. This gets you the equivalent of the low-level C header files for manipulating the hardware registers.

- cortex_m (https://docs.rs/cortex-m/), which provides APIs for the core ARM peripherals in every Cortex-M MCU.

- f3 (https://docs.rs/f3/) - High level APIs for the peripherals and external sensors on the STM32 F3 Discovery board.


I recently finished my Master's Thesis on using anything other than C/C++ on microcontrollers.

It's a mess. A couple of languages like Rust, D, etc. can be hacked on micros due to their compiler suite, but it is always messy. Above guide uses inline assembly to blink a GPIO. Hardware access is on register level. None of this makes your live easier.

In order to be viable for microcontrollers you need two things: * a compiler or interpreter for your target * a Hardware API

There are some projects, like MicroPython and Espruino who realized the importance of the Hardware API. They basically transcend micro-interpreters and RTOS (especially given the languages asyncio features).

Microcontroller vendors ship C/C++ libraries for their hardware. You either have to wrap that, like the micro-interpreters do, or your language has to be able to use them natively.

What Rust on microcontrollers need is a Kickstarter campaign with a nice dev-board as perk to finance a project that implements a nice API for that specific micro.


> Above guide uses inline assembly to blink a GPIO.

No, the only inline ASM in this guide is to cause a debugger breakpoint. You also need inline ASM to enable/disable interrupts, and that's about it.

The code in this guide looks ugly because it's written to show how to use the hardware without any abstraction. Higher level APIs like f3 (https://docs.rs/f3/0.3.1/f3/) exist, though not nearly for every chip or peripheral. Even in C, for anything outside the popular Arduino/mbed/etc boards, your choice is usually between buggy vendor bloatware and using the registers directly after carefully reading the datasheet.


From what I've seen, using registers is often simpler and easier than using the vendor GPIO libraries.


True, I was a bit hyperbolic.


The vendor SDKs are the weak link, not the compiler or language. Development could go 4x smoother if the vendor code were solid. Every single project I bump into sdk bugs that make it clear they have shipped code that was not tested. Its like they expect us to debug their code that they had developed on the cheap. Lately my philosophy is to just take the vendor header file and write my own low level drivers because it takes a more predictable amount of time. Our industry could use 3rd party SDKs.


There's a big conflict in mcu development:On the one hand, you have developers who want reliable and comfortable libraries so they can develop well and fast. Higher-level is a word to describe that(it's only partially accurate).

On the other hand, mcu companies make a significant amount of their profit based on lock-in(since mcu's are mostly commodities). And the higher level the API's and the rapid development is, the less lock-in companies get.

That together with the fact it costs a lot to develop quality libraries are the main reason to the state we're in today.

As for solutions:

1. ARM did face that problem once, with cores, and played it hand well , they came to mcu vendors and told them - the carrot: using our core will save you money on development(core/compilers/etc). The stick: a startup called luminary-micro , who could do cheap mcu's since all that was free.

- So can this move be played again, today, with peripherals ? maybe build a nice peripheral set, with libraries, well tested, etc - and offer it cheaply to the risc-v guys , and other mcu vendors, including Chinese mcu vendors ?

- Maybe the place to look for quality libraries is at mcu startups ? the last one was the acquired energy-micro , are their libraries better ?

2. The IOT is a big shift. Shifts create different leverage points sometime. Is there something that open-source community can do together to push the vendors towards great libraries in that niche , even if vendors don't like it much?

For example, what if the open-source community focused on one of the hardest building block of the IOT - security ? what if we created a highly proven security IP, proven by the community to a great extent, but used it licensing to push mcu vendors ?

Are there any other ideas how to solve this conflict ?


I don't think they need software lock-in. They already have hardware lock-in, which is easily 10 times stronger.

Rewiring and redesigning all electronics around a new MCU is just an insane undertaking. Irrelevant of API change in the software.


In the EBM embedded survey 2013, regarding microcontorllers[1], they asked the question "why did you use the same processor", pg 56:

https://www.slideshare.net/MTKDMI/2013-embedded-market-study...

Many of the reasons we're regarding software, indicating that software lock-in is a significant factor.

[1]they called it microprocessors , but pages 54, 60 hint we're talking about mcu's.


Assuming there was a bit of standardisation around the basic API. There is still significant lock-in by other factors.

It could be that the software is the thing they think first, if you ask the people writing it.


Maybe. But you have to consider that such surveys(and similar tools) could make semi company managers believe that software lock-in is important, and that's all it takes.


freertos or projects like that could help. They need different drivers anyway so good rtos support would mean you can ignore the official sdk.


Agree. Hardware SDK and tools are utterly terrible.

That's the major reason I stay away from embedded development.


> Microcontroller vendors ship C/C++ libraries for their hardware

They also ship Basic, Pascal, Ada, Oberon and Java, here's a small selection for those curious about the offerings.

https://www.mikroe.com/

http://www.astrobe.com/default.htm

http://www.microej.com/

http://www.adacore.com/press/8-bit-avr-microcontroller/

https://www.parallax.com/microcontrollers/propeller

https://www.parallax.com/microcontrollers/basic-stamp


Is creating a Hardware API that complex that it would require a kickstarter ? because this link talk about using rustbindgen to automatically generate embedded bindings to a vendor library:

https://spin.atomicobject.com/2015/05/21/generate-embedded-r...


Creating a _nice_ and _reliable_ API with _good_ documentation that is _native_ to the language - I do think so, yes. Also keep in mind that there would be a specific devboard to build a community around.


Total agreement.

And even before the coding starts, defining good abstractions for complex peripherals is very difficult. Counter/timer modules are the poster child for that. Heck, if you look at the Micropyothon issues on github you will find pages of vigorous debate about GPIO pullup configuration, much less anything complex.


C/C++ library?!?

In my experience most bundle their own compiler suite. Nothing like learning what parts of C++03 are/aren't implemented.

Especially in the RTOS space where most the time your program is just an object file being injected into the OS.


True, the libraries are C. Compilers are often gcc. Like AVR has a gcc compiler and as you said some parts of C++ are implemented. For ARM there is also a gcc.

I only have experience with AVR and ARM (STM) to be honest.


I came across this [0] yesterday while looking for STM32 stuff for those cheap ass $2 STM32F103 boards. To your point, however, it's still just writing values directly out to register pointers. That entire repo can be boiled down to one little C program like this [1].

0. https://github.com/jamesmunns/bluepill

1. https://gist.github.com/uxp/35c9d5d51b2229f5c4c2e5b11c3341e5


The MBed suite is rather nice to use.


Perhaps a viable way to break the monopoly that C/C++ has in embedded software is to create a language that compiles to C. That way people can use their existing compilers (which may be the only choice for a particular target), drivers, middleware, etc. but benefit from a modern, safe(r) language.



Another interesting language for embedded targets is Ada: http://blog.adacore.com/tag/STM32


Did you look at Céu? A really cute language, I think.

http://ceu-lang.org/


Can you go into your experience evaluating MicroPython a little deeper? What were the pros and cons you discovered?


Look out for ISBN 978-3-8007-4395-7 "Evaluation of MicroPython as Application Layer Programming Language on CubeSats" in the "ARCS 2017 Conference Proceedings" - paper should come out soon enough.

I have a presentation under http://plamauer.space/presentation.html - hard to follow without moderation, but pros and cons are at the end.


Is your Masters dissertation online? I'd be interested in reading it.


I do plan to make it a website, like a gitbook or something, but right now, I'm hammocking.

Also don't expect much. I'm a Mechanical Engineer and it was a bit out of my league, so it didn't go very deep.

The paper will hopefully soon be published: ISBN 978-3-8007-4395-7 "Evaluation of MicroPython as Application Layer Programming Language on CubeSats" in the "ARCS 2017 Conference Proceedings"


I've been looking for such thing for a long time as I don't really like C for embedded programming. Was following https://zinc.rs/ for a while but then I've discovered ivory/tower framework made by Galois (http://ivorylang.org/). It's basically haskell DSL and C code generator for embedded world, currently targeting stm32 family.

Compared to these early rust attempts they've written a port of ArduPilot in ivory/tower called smaccmpilot (http://smaccmpilot.org/).

In one of the papers they even compare ivory with rust in terms of memory safety.

I'm now trying to port ODriveFirmware written in C (+CubeMX/CMSIS) to ivory and so far I'm pretty surprised how well it goes. I'm going to start documenting this process on a blog soon, if interested I've created a wiki page collecting resources about embedded programming in Haskell at https://wiki.base48.cz/EmbeddedHaskell


In a similar vein there is ATS - which already compiles to C. And you can in theory write controller code in ATS if you want those functional features and more

ex:

(this guy has done examples on a ton of boards!) https://github.com/fpiot

www.metasepi.org/doc/metasepi-icfp2015-arduino-ats.pdf

https://github.com/fpiot/msp430-ats


I go through this cycle every 6 months or so where I will get excited about Rust and try it out for embedded development again. Typically in the first day, I get frustrated with some major limitation and put it back on the shelf. This last time, I wanted to make a simple timer that displayed the time since a button was last pressed on a 16x2 character LCD screen.

In addition to the HAL annoyances that turbinerneiter talked about, I wound up giving up this time because I could find no way to manipulate strings in a static buffer. In C I could just allocate two 17 byte strings statically and then `sprintf` to them, but there seems to be no equivalent in Rust.

I'd be curious to know what limitations others have run into when trying this and if I was just doing it wrong.


The write! macro would be the equivalent here. Something like this:

    use std::io::Write;
    
    static mut S1: [u8; 17] = [0; 17];
    
    fn main() {
        unsafe {
            write!(&mut S1[..], "Hi: {}", 5).unwrap();
            
            let s = std::str::from_utf8_unchecked(&S1[..5]);
            
            println!("string: {}", s);
        }
    }


Having all your main inside unsafe doesn't seem like the best advert for Rust :)


You're right, it's not; I was just showing the smallest code. :)

This is unsafe specifically because of the mutable static; you can deal with that in a few different ways, but that wasn't the point of the example.


This is exactly what I was looking for; I had only checked the String and &str documentation and didn't think to look for a macro. Thanks :)


No problem! It is slightly obtuse, given that you store a u8 buffer and then make &strs from it, rather than storing a &str.


Hello, HN folks. Author here.

Sadly, the repository posted by the OP is not up to date. That's not to say it's wrong but just that doesn't contain recent developments. Also, this repository documents every single part of putting together a microcontroller project from scratch. Although interesting, this is not the fastest way to "Hello World". That's why I have put together a nicely packaged Cargo template [1] [2] to quickly start a new microcontroller project. This template works for any Cortex-M microcontroller and paired with the svd2rust code generator [3] you can easily get full device support (register level API) for any microcontroller for which the vendor has released a CMSIS-SVD file [4]. There's a database of such files in this repo. [5]

For some, directly working with registers may be too low level. In that case you can look at my Discovery book [6] which showcases a higher level API and several examples for the STM32F3DISCOVERY board. The API is documented here. [7]

What's missing from all this is a good concurrency history. (All the examples in the Discovery book are single task.) So, I have been working (not alone) on a multitasking framework that's suitable for real time systems and the focus has been minimizing overhead while guaranteeing memory safety. At this point, we have pretty much reached the goal and we are currently working on the ergonomics. I hope that we'll have something show Soon (TM).

I'm happy to answer questions about Rust on microcontrollers. I may take some time answer, though, as it's weekend :-).

P.S. I hope I got the formatting right. First time posting on HN.

P.P.S. Congratulations for reading the whole thing! As a reward, here's a robot [8] built with the framework I mentioned.

[1] https://github.com/japaric/cortex-m-template

[2] It seems that the Cargo template feature got removed recently (due to not having gone through a RFC process) but you can easily rollback your Rust installation to get an older Cargo that supports templates: `rustup default nightly-2017-04-01`

[3] https://docs.rs/svd2rust/0.5.1/svd2rust/

[4] http://www.keil.com/pack/doc/CMSIS/SVD/html/index.html

[5] https://github.com/posborne/cmsis-svd/tree/master/data

[6] https://japaric.github.io/discovery/

[7] https://docs.rs/f3/0.3.1/f3/

[8] https://mobile.twitter.com/japaricious/status/84569793557265...


Thanks for doing this. I'm really excited about the potential of Rust for embedded, but it still requires too much trail-blazing to be compatible with my current priorities. FWIW I've been doing realtime embedded C for years, but feel it is high time we made some progress in the embedded world with respect to memory safety and concurrency correctness. I'm also a huge fan of Micropython, but of course Micropython isn't applicable everywhere.


The github readme page of that project should point to https://github.com/japaric/copper/blob/master/src/intro.md


This is cool. I wonder how much benefit Rust is likely to yield in this space. I generally tend to try to avoid any dynamic memory allocation when writing code for small μcs, so that takes away one of the big pain points of writing in C. Rust does have pattern matching, which is always useful. My experience is with Cortex M0s with ~32K flash, though, so I can see how Rust might have real benefits with bigger μcs.


Which Cortex M CPU/board would you recommend for a seamless development experience?


I really like F3 and F4 discovery boards. You can re-flash dedicated stm32f1 with blackmagic probe (instead of ST-Link) and bridge UART to it to get really awesome development board.

After this is done you'll get /dev/ttyACM0 and ACM1 for GDB interface and UART bridge. From GDB you can connect to it directly with 'target extended-remote /dev/ttyACM0' (no need for OpenOCD middleware).

https://wiki.base48.cz/STM32#Running_BMP_on_Discovery_boards


What is the advantage of blackmagic probe over plain ST-Link?

Genuine question, I've never used the ST-Link for more than flashing the image and some debugging, so what am I missing?


It implements GDB stub directly so you don't need to run OpenOCD to be able to attach to it from GDB.

Also UART bridge is nice but this might be supported newer in ST-Link too. I haven't used STLink for a while - flashing all the stuff with blackmagic right away.


Quite sure they do have the UART bridge now, but the black magic probe looks interesting. Thanks for the link.


I'm curious about the uses of the UART bridge. I've only used small chips with few pins to spare, so I've always used RTT or semihosting for logging. Is there an advantage to doing that using the UART instead? Or is the UART bridge for something else entirely?


AFAIK all of the major manufacturers have pretty decent dev boards. I've only tried the EFM32 and STM32 boards:

http://www.st.com/en/evaluation-tools/stm32f0discovery.html

http://www.silabs.com/products/development-tools/mcu/32-bit/...

The STM32 board includes an STLink programmer which you can use via OpenOCD. The EFM32 board includes a J-Link programmer and the IDE comes with the JLink command line tools. You may be able to use it via OpenOCD too, but I haven't tried that.

I prefer the EFM32 peripheral library (efmlib) to what's available for the STM32, which is either the old clunky Standard Peripheral Library or some new thing that's closely tied to their awful IDE.

If you use the EFM32 and the JLink software you can log using RTT (https://www.segger.com/jlink-rtt-viewer.html), which is much faster than using semihosting, which is the most straightforward option with the STM32.

The STM32 board has the advantage that you can set it up as a programmer by removing a jumper, whereas to set the EFM32 board as a programmer you have to load the horrible IDE and click buttons.

It's generally fairly easy to compile code for these chips using the arm embedded gcc toolchain:

https://launchpad.net/gcc-arm-embedded

The only tricky part is finding (or writing) a suitable '.ld' file. You can usually find one by poking around the files included with the IDE or with the peripheral libraries. In principle you can just write it yourself, if you know enough about ARM architecture, but that's above my pay grade.

Anyway, take all the advice above with a pinch of salt -- I'm no expert on this stuff. But sometimes the perspective of a relative newbie can be useful too.


I learned using the Silab's Zero Gecko starter kit that you linked to. As someone whose only other microcontroller experience was a few simple led projects with an arduino, I found the gecko family's tools and docs incredibly helpful and intuitive. It really helped me learned all the ins and outs of the Cortex M, such that I was able to build my own hardware programmer/debugger after a few weeks of tinkering.


The newer ST's HAL library is not that bad, and is definitely not tied to their IDE. The STM32CubeMX has pretty decent pin and peripheral configuration tool (definitely better than spreadsheet) which generates project skeleton. After that, you can use SW4STM32 (eclipse packaged with arm-none-eabi-gcc) or commercial IDEs. You can also use plain old make if that's what you prefer.

There are also other fully open options for STM32- libopencm3 (just HAL) or ChibiOS (an RTOS with HAL).


>The newer ST's HAL library is not that bad, and is definitely not tied to their IDE.

Fair enough. I'm sure the library itself isn't literally tied to the IDE, but it's not easy to find as a separate download, and there is little example code available online for the HAL library.

> libopencm3

It's a nice project, but I prefer to use vendor-supplied libs unless there's a good reason not to. Efmlib has a permissive license and seems pretty nice from my relatively limited experience of it.

>You can also use plain old make if that's what you prefer.

This is the thing. I really want to be able to easily set up a project just using GNU make and the regular arm gcc toolchain. I've found it easier to do that on the whole with the EFM32.


Interesting, adding EFM32 to the ever-growing list of things to tinker with.


Kind of off topic, but I can't recommend Quantum Leaps (www.state-machine.com) enough for microcontroller development. Also, the book by Miro Samek (quantum leaps founder) is very good. I've had a really good experience using his software on the cortex-m MCUs.


Ugly stuff. In my opinion, plain C is much better (the pain was to program microcontrollers in pure assembly). If you want higher/"safer" level, I would recommend you using Lua, Python, or even Node.js.


python and node js would have orders of magnitude worse performance / overhead.



Sure. If you require performance, C is the optimal fit, in my opinion.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: