Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Raspberry Pi Bare Metal Programming with Rust (thiago.me)
340 points by thiagopnts on Jan 16, 2016 | hide | past | favorite | 92 comments


As a long time C programmer this is not very convincing. A C program to do the same requires far less voodoo. All you need to do is take the address of the GPIO register then toggle the bit. No name mangling. No error handlers to override. Don't get me wrong I know rust does have some compelling features. It does seem odd to me that so many of what would be compiler options in C are hard coded.


Point by point:

> All you need to do is take the address of the GPIO register then toggle the bit.

Yes, and that is unsafe. Rust makes you put that in an unsafe block, to encourage you to build safe interfaces. This is a good thing.

> No name mangling.

The alternative to name mangling is not having a module system, with all the fun name conflict issues that come with it. Rust made the right decision here.

> No error handlers to override.

C has no concept of a panic handler because what would be panics in C are undefined behavior. Undefined behavior is bad for safety and understandability of the code. Having to declare an error handler is a small price to pay.

> It does seem odd to me that so many of what would be compiler options in C are hard coded.

Because Rust is safe by default. That's one of its most important features. Being more like C in the ways you mention would mean compromising that principle.


After having played around with Rust a bit and seeing it used in some non-trivial applications I've grown to like it a lot, despite being an initial skeptic. However it's aggravating that in every post about Rust which raises valid criticisms about the language (in this case having to do with the ease with which C allows you to do low level programming), those associated with the Rust project leap to its defense and suggest that Rust's decisions are right for every situation.

Just because Rust can't easily do things that C can doesn't make it a bad language, but it does mean that, shockingly, there are somethings that Rust isn't as well positioned for as other languages are.

Similarly, there are things that C is bad at. Both at the high level and the low level. At the low level it hides too much of the CPU's features so for some very low level programming you need to drop to assembly. At the high level it has obvious deficiencies, many of which Rust addresses, but for interfacing with the actual hardware it is tough to beat C in terms of convenience.


Of those three issues, I see not sectioning off unsafe code as the only real defensible choice when comparing C to Rust. Whether you should have to type "unsafe" is a legitimate tradeoff; if all your code is unsafe, the unsafe keyword adds noise.

Undefined behavior and the lack of namespacing don't fall into this category, though. C would be a better language all around if it required that null pointer dereference trap to an error handler (at least on hardware with an MMU or MPU). It would also be a better language if it had a module system.

Not every engineering decision is a tradeoff. Sometimes certain decisions are just better all around. I think that modules and error handlers fall into this category.


> Not every engineering decision is a tradeoff.

Scratch the "not". For example, if I have a small-enough code-base, a module-system, is not worth it, unless it has zero cost. Or if the module system doesn't fit my needs, it will often be easier to create what I need if I don't have to work around what is there. Same with error handlers.

Every engineering decision is a tradeoff, though you may be in a space where the tradeoffs obviously point in one direction.


A module system is always worth it because every program needs to use library functions. Even if you think you don't, LLVM will generate calls to library functions for ordinary code; for example, if you move structures around on the stack, LLVM might just decide to call memcpy(), even if you didn't ever import string.h. (Checking your code after the fact to verify there are no library calls doesn't get around this because a new version of the compiler might add a library call where there wasn't one before.) Now you want some mechanism to isolate your code's names from the library's names. That's a module system.


Does bare metal Rust also use memcpy() or other library functions? If so, how are those linked into the final binary?


Yes, LLVM will assume three or four functions exist regardless. The rlibc crate provides a Rust implementation of them, so you just add it and you're done. https://github.com/alexcrichton/rlibc


They are usually provided by libc, but if you do low level stuff they are provided by other means.


That's because there's no language syntax for device address space. Modula had DEVICE as a keyword, and you could define structures as being in device space.

That's a useful language feature for low-level programming. It tells the compiler that the address space is special - it can change without help from the program. Other features of device space can include that the write width can matter (some registers have to be written as a single byte, world, or double word) and some registers are read-only or write-only. The compiler needs to know this stuff. Especially because Rust makes some strong assumptions about memory.

Rust probably needs to know about both device registers and memory shared with peripherals to operate at this level. For the blinky light program it doesn't matter; for the network stack it matters a lot.


Of course that same ability lets you clobber anything else in your address space and leads to those lovely debugging sessions playing "track down what corrupted my datastructure".


Whilst having to debug this is still undesirable, I recommend trying `rr` (http://rr-project.org/) to track this if you're ever in such a situation. It's as simple as setting a watchpoint and reverse-continue-ing!

I don't do much C/++ these days, but when I do (and it's a large codebase which is hard to debug), `rr` is invaluable. It works with Rust too.


Seems quite restrict in terms of platform support.


My experience doing this sort of stuff in C is that this class of complexities is still present, but hidden outside the source in the crt0 and build scripts. Putting it in the actual program text seems like an improvement.


Exactly. I saw the gp comment and feared there was lots of boilerplate, arcane incantations -- and the only slightly smelly thing in the (IMNHO) beautiful and short example is the use of an empty asm block to "fight" the compiler on optimizing out the busy loop.

I can't imagine the full C example for this is any prettier or easier to follow?

[ed: As for "no name mangling" - having such an easy way to turn it off when needed, and yet avoid collisions when you do need it seems pretty good to me. Perhaps just having something that's "extern" be umangled by default would be better -- but it's not like one needs to bend over backwards to get a "mostly safe" bare metal program here.]


I think you're giving it a bit more credit than necessary. Don't get me wrong, it's cool that they've got it running on a RPi, but it's almost word for word what you'd write in C (right down to needing to inline assembly to get a busy loop to work). The C implementation wouldn't need to bring in a bleeding edge nightly build, worry about name mangling etc..


The purpose of the article was to show how to get up and running with rust on a Raspberry Pi, not to show how that rust is easier to use than C for programming a Raspberry Pi.

Plenty of things I do are are word for word the same in C and python. (import math vs. #include <math.h> , math.sin(x) vs. sin(x), etc.)

It is when you want to build something more complex than a blinking light that the differences between languages come out.


We generally try to put options in source code or configuration files, rather than in command-line flags, because that's more repeatable. It makes the CLI much simpler.


As the author said at the end, the general "good practice" for Rust is to encapsulate the weird stuff and unsafe blocks into a safe lib that other people can use.


As a C programmer who fiddles with low level stuff I wonder why people get exciting about such trivial thing.

Don't get me wrong. Rust is an interesting language. The thing described in this post is well within its capability, i.e. IMO there isn't really anything that worths bragging about. Such trivial thing neither demonstrates the real potential of Rust, nor answers important questions from real world engineering perspective.

I'm all for having better tool to write low level stuff. I have dabbled with Rust and the experience was eye-opening. I think Rust still have a lot to catch up though.


Has hello world ever been exciting, it is only really a building point. You can't do anything intersting until you know the basics, of building and flashing the board.

The question is what does Rust have to offer as your embedded program grows.


> Has hello world ever been exciting

The HN upvote says otherwise. :-)

But I guess had there been an HN-equivalent in assembly age people would get excited about C, too.

> The question is what does Rust have to offer as your embedded program grows.

Exactly.

We are aware of the good, bad and ugly bits of C. The industry has built extensive tooling around it. Rust has a long way to go. I certainly wish to see more pioneering projects from Rust.


I don't think the post is bragging about this. It's well known amongst the Rust community that this stuff is pretty easy to do)

I read it as a primer for writing simple Rust programs that can run baremetal on the Pi (I've cross compiled for a Pi-with-OS before but never tried this) and access GPIO.

If you're talking about the HN upvotes, HN just tends to upvote posts mentioning Rust a lot :)

Have you looked at https://zinc.rs/?


zinc.rc is certainly interesting. Thanks for sharing!


I have the perfect project for this! Generate a signal using the GPIO ports to drive a mirror and camera in a synchronized fashion.

Does anyone know if the RPi GPIOs can be driven at around 80KHz? I've seen reports that this is possible, but that the USB or video driver tends to lock the CPU for long times, messing with timings - but hopefully running on bare metal would take care of that.


And then if you want to get seriously weird, you could try the PRU on a Beaglebone Black. Two internal processors (almost) entirely separate from the main processor. They have a 200 MHz clock speed and can generate extremely accurate and deterministic time critical signals


Yeah 80khz should be no problem. Even in Linux if you access the Pi's GPIO registers directly you can toggle pins in a tight loop at around 1-2mhz. As others mentioned though you might look at a small microcontroller though since the Pi is kind of overkill for generating a simple signal.


> Even in Linux if you access the Pi's GPIO registers directly you can toggle pins in a tight loop at around 1-2mhz.

Careful, this is strongly dependent upon the hardware, not Linux.

I seem to recall that people doing SWD programming can barely get the GPIO's to move faster than a couple of KHz.

I can tell you that, at least on the BeagleBone Black, you have to do some Linux driver voodoo to get better than a couple of KHz.

Edit: I stand corrected. Apparently the Broadcom chip in the RPi does much better than the TI one in the BeagleBone Black when driving baseline GPIO's.


On the other hand, the BBB has two dedicated on-chip MCU's for driving the GPIO's.


True, but those are really annoying to program and are mostly meant for hard real-time rather than maximum speed.

The speed on the BBB for GPIO's is okay (I can get to about 12.5MHz with some care). It's more annoying that you actually have to be in supervisor mode to change the I/O direction of a GPIO pin.


Here is an example of a project driving a realtime camera with Beaglebone black.

http://www.erlang-factory.com/static/upload/media/1394631509...

There is a video for the presentation as well.

https://www.youtube.com/watch?v=OBGqVmzuDQg&list=UUKrD_GYN3i...

The trick is Bealgebone black has 2 co-processors PRUs and they can be used for realtime work:

http://beagleboard.org/pru

http://elinux.org/Ti_AM33XX_PRUSSv2

It is a programmable 200-MHz, 32-bit processor with single-cycle I/O so it can be used to toggling GPIO pins.



Excellent, thanks!

So GPIO performance won't be a problem, but they note that OS multitasking can cause issues:

"What is not evident from the snapshots, however, is that due to multitasking nature of Linux, the GPIO manipulation is constantly interrupted for short periods when the CPU is doing something else, such as receiving or sending data over network, writing log files, etc"

Running on bare metal should take care of that, so the only remaining problem is how to synchronize the signal generation on the RPi with the recording of data on the host computer. (Sigh, if only RPi had a USB3 port...)


>due to multitasking nature of Linux, the GPIO manipulation is constantly interrupted for short periods when the CPU is doing something else, such as receiving or sending data over network, writing log files, etc.

With a few simple tweaks (isolcpus + irqbalance + setting cpu affinity), you can get rid of the overwhelming majority of interruptions a specific userland program running on a Linux machine could encounter.

If this quote is talking about kernel context switches, choice of I/O scheduler and tick rate in the kernel could be tweaked for better performance.


You could probably use an Arduino for this, they connect to a computer using serial-over-USB.

You can do GPIO bit-twiddling pretty fast: http://skpang.co.uk/blog/archives/323


The new raspberry pi zero can be run as a device, so you could use that.


O.o this $5 puppy could essentially replace the 1.5K National Instruments DAQ I'm using right now. Even better, I won't have to deal with their horrendous API documentation anymore.

Of course that would require a Linux kernel, so not bare metal anymore, but there might be a way to make this work!

Now I just need to get my hands on one, it's out of stock everywhere within a radius of 300km...


They haven't committed to making more, or if they do, to the $5 price point.


They are making more, but they sell them faster than they can make them.


Really?? Then the zero was a really shitty marketing move...


You could do this with much less than an RPi. I would recommend using an AVR or better yet a 555 timer if all you need is an 80KHz signal. Let me know if you want help with this I'm looking for projects like this or something much more complicated.


That's true, the reason I mentioned the RPi was because I happen to have one lying around. (Actually, I seem to remember there are a couple of AVR development boards lying around in the lab, so these might be worth a look.)

My project involves generating two analogue signals to drive a mirror (a sin wave on the x-axis and a staircase pattern on the y-axis), and a synchronized digital trigger signal for the camera. There are a number of configuration options that make this slightly more interesting than it looks at first (frequency, number of sin periods per staircase step, duty cycle and number of triggers per sin).

An implementation of this on a NI DAQ took the better part of a day and an implementation on a FPGA took roughly two weeks (mostly spent on familiarizing with the tools and communicating settings from the host.) I actually think an implementation on the RPi would be simpler than either, including wiring up simple DAQ.


I think you will run into real-time performance problems on the RPi.


I wouldn't recommend an AVR. That is outdated technology. Even Atmel mostly makes ARM microcontrollers now (and they are much nicer than their AVR ones).


AVRs are simple and reliable. You can read the whole datasheet and understand everything with much less effort than modern ARM MCUs. They've been around long enough that I have a lot of confidence in them. They're electrically more robust than most modern chips so they're hard to accidentally damage. There's a lot of community support available because of the popularity of Arduinos. Many tasks don't need anything more powerful. I think it would be foolish to discard AVRs as obsolete just because they're old.


AVRs are far from outdated. The AtXmega series is very powerful. It includes modern features such as DMA transfers. AVR chips are much easier to work with than ARM for certain applications. They are also lower power and cheaper. The OPs application would be significantly easier to implement on an AVR. On the RPi you would have to write a Linux kernel driver and deal with real-time issues. In the AVR you would create a few interrupt handlers and be done.


horses for courses. If you want a small microcontroller that has a very simple IO model then avr is great, and if you use something like an attiny they're cheap as ... chips.


At small scale the price difference is pretty meaningless or even non-existent. For example right now the cheapest AVR at quantity of 10 costs $0.837, while cheapest ARM is $0.714.

(prices checked from digikey)


Or a Parallax Propeller


Is Rust the future? As a C++ (hobby), Java (full time) developer, should I invest my time in Rust?


If you're just concerned about employment then you'll probably be fine with java (maybe go in the future). If you're looking for self-improvement then its always a good idea to learn a new language, especially if it does something different like Rust's borrow checker. Outside of that, I also used to do all my hobby code in C++ and after learning Rust I'm never going back to C++.


>maybe go in the future

Go is not going to replace Java in the future. For a language which cannot even lock dependencies to a version, people sure like to pretend it is the holy grail because Google made it.


Out of all the new languages of the last 5 years or so (D, Rust, Crystal, Go?, etc.) I think Rust shows the most promise. It is attracting both c/c++ people and people coming from dynamic languages (Python, ruby, js). So I'd recommend trying Rust out for sure, not much to lose, maybe spending a day...


The first release of D was in 2001. That's 15, not 5, years ago.


Though, what I find odd is that for me (and yes that is an extremely small sample) I barely heard of D until people started wondering why rust was being picked over D.


D has always been well known in the C++ community given its authors.


As a fan of Rust I don't think it is really going to be the future. While it might be successful in its own way I now believe that it will remain relatively small-scale (compared to e.g. C++ and Java). The entrenched players are extremely entrenched and Rust lacks the mass appeal that popular languages such as Ruby and Go have, being more complex to learn and write.


I started playing around with Rust a few weeks ago and fell in love; it has some quirks for sure (the whole borrowing system is a bit tough to grok at first) but it didn't take long for me to start seeing things the "Rust way." My only hang-up with it right now is that so many of the most useful packages in Cargo depend on the nightly build of Rust.


Could you list which packages you tried to use which were blocked on a nightly? In many cases a package can be moved off nightly with trivial changes, I can try to do it.


I tried digging around but couldn't find the package names -- I could've sworn ansi-term (https://crates.io/crates/ansi_term/) and hyper (https://crates.io/crates/hyper/) were two of them, but it looks like they both use stable. Thanks though!


Depends on when you last tried I guess.

So what happened was that many of these packages existed pre-1.0 when there was no stability system. Nobody knew which features would be stabilized (if you go further back, the fact that things would be stabilized in such a way wasn't clear either).

So we all just used whatever feature we liked. Many of these unstable APIs or features had stable counterparts, but the choice to use an API/feature was made pre-stability, so nobody had any way of knowing. Which meant that a lot of unnecessary unstable API usage persisted after 1.0. Some crates made a transition. some were slower (I think hyper was pretty quick to stabilize). Servo, for example, didn't have any issue with using nightlies for various reasons so we kept using unstable APIs (I later audited and removed low-hanging extraneous unstable API usage. Most of our out-of-tree crates are stable too).

There have been efforts both by maintainers and by individual community members to bring crates to stable, and I think at the moment most useful non-plugin crates work fine on stable. Let me know (@Manishearth on twitter) if you find a crate that you need that doesn't work on stable!


Ah, that makes perfect sense. Thanks for the breakdown, and I'll definitely let you know if I run into any issues with packages on nightly!


I think you should lean Rust just for some new insights and patterns (error handling, optionals, pattern matching come to mind). It might make you a better programmer over all.


If you invest your time in reading books then with same success (at least) you can invest your time in Rust. And there are chances it will be even more useful.


Rust, Go, Java, Scala, Clojure, Javascript... (leaving out quite a few other choices for brevity). They say choice is a good thing but too much choice is actually really annoying and fragments the community across a very large number of ecosystems.


I see the point you're trying to make, yet:

- Nobody in their right mind would choose Rust or Javascript for a project where the other is more appropriate.

Though (AFAIK) Scala and Clojure both interop quite well with Java, and Scala/Clojure interop isn't terrible either, plus there's definitely a culture of reusing in Clojure/Scala all the stuff that the Java ecosystem already had. So this cluster isn't causing much fragmentation at all.

You have something of an argument that Go and the JVM languages clash for mindshare, but at that point the argument around really annoying fragmentation has kind of lost steam.


I can't stand JS and Go syntax, I tried a few times... This is the worst thing that happened to humanity after in XXI century. I feel more comfortable with Prolog than with JS.


In a way Go is a step backwards from C towards Pascal. Since a number of people that had extensive C experience and were in fact present at the birth of the C language made that decision I figure they are doing it for a good reason.

I can read Go easily enough, I haven't done any major writing in it (yet), other than some minor work on Hugo trying to help to nail down/replicate a bug. It was easy enough that I think I'd pick it up quite fast, the syntax feels 'weird' to me but that's most likely just unfamiliarity.


Outside of C, are there any other languages or platforms that can do this? I'd like something modern, but I haven't liked what I've seen with Rust personally.


Still alive: Ada, SPARK, ATS, FreePascal, D

Controversial: Go (if someone ports the runtime to bare metal), embedded JVMs, embedded, Swift (depending how Apple drives it), .NET Native (if C# gets missing features from System C#)

Faded away: Algol, PL/I, CPL, Mesa, Modula-2, Modula-2+, Modula-3, Oberon, Oberon-2, Active Oberon, Component Pascal, Turbo Pascal, Forth, Sing#, System C#


Don't forget to add Nim to that list! One of Nim's defining features (compilation to C) makes it work rather well for this sort of thing.


Forgot about that one sorry.

Compilation to C is a toolchain feature, not a language one.


I think he was asking which languages could actually be used to do this today, without writing a new cross-compiler first.


That's exactly right. I didn't know that D would be an appropriate language for things like this - I'll have to check it out.


Check some of the videos here

http://wiki.dlang.org/Videos

Namely "Tiny, Ubiquitous Machines Powered by D" and "x86 Bare Metal and Custom Runtime Programming".


You always need a cross-compiler, or a VM. There is no way around it.


I don't have a problem with needing a cross-compiler, but I don't want to write one. I don't have to write my own if I use Rust. Or D, or C. But I can't find such a thing for ATS for example. So just being a language which has semantics to support this is not sufficient to be declared a language "which can do this". I would also need to be able to download a toolchain which can do this.


Ok, but that is a matter of availability of implementations, not a language feature.


And C++, surely? (Although, I would probably choose C (or even assembler) over C++ for something this simple (loop, blink a light). And between all those, I think Rust is really interesting.

It could probably be argued that Rust doesn't add much on top of D (as a "better" C++) -- but between the mind-share and the focus on "safe" (and boxing in unsafe) memory access, I think Rust is really interesting. Not sure how run-time free D is coming along - believe there were some issues with the standard lib?

Ada was troubled by a confusing split between FSF LGPL Gnat trailing Ada Core GPL/commercial compiler with a release -- leaving people a little confused if there was a good Free Ada compiler that could be used for commercial (or just non-GPL) development. (Yes, it was a real issue, just as one needs an LGPL libc for c development etc).

But as I understand it the GNAT compiler has matured to the point that one can now use modern Ada without having to worry about that. Unfortunately Ada probably lost a lot of potential developers due to the confusion/issue.


I left C++, Objective-C and Objective-C++ out, because of the C compatibility.

The price of commercial compilers wasn't the only issue with Ads. It never had any friends in the UNIX culture.

UNIX culture always ignored safer system programming languages from the 60 and 70's.

If the hacker culture bashes Java for being verbose, what would they say about Algol languages like Ada?

Another problem was Ads was mainly a DoD thing, there were no commercial OSes using it.

So you either used the system programming language of the OS, if you were lucky to access to the official SDK, or bought one you could afford. There was always an option to type it all in as well.

So with these constraints it was hard to get Ada widely adopted.

It is now mainly used in avionics, train control systems and high integrity systems. All areas that the common Starbucks coder usually doesn't care about.

As for D there are some projects using it on micro-controllers, presented at D Conf 2014. In comparison to Rust, it might appeal to those that favour a C++ like syntax, interoperability with C++, GC support, great metaprogramming capabilities.

I like both, and think there might be space for both of them.


I've only toyed with Ada, but it doesn't strike me as particularly verbose?

[ed1: eg: http://rosettacode.org/wiki/N-queens_problem#Ada compares pretty favourably/neutrally in my mind to the C++ version in terms of verbosity (or lack thereof). Unless one gets upset about "end loop" vs "}" ]

[ed3: Hm, I guess looking at: http://rosettacode.org/wiki/Balanced_brackets#Ada I could see Ada as being derided as verbose... maybe :-) ]

[ed2: And looking a bit more at rosettacode, I now found:

http://forge.ada-ru.org/matreshka (Quite unrelated to the rest of the discussion here, but anyone else interested in poking at Ada might find it interesting) ]


I also only toyed with Ada.

My real work experience with Algol like languages are Turbo Pascal, Delphi and Oberon. Although thanks to my interest into compiler development during my CS degree, I also do have some knowledge of almost all major Wirth influenced languages.

Personally, I do prefer some verbosity to lines full of symbols.

Ada 2012 is quite good.

It is also remarkable that in the 80's Ada 83 as seen as almost impossible to implement and nowadays C++ is way complexer to implement than Ada 2012.

Another anecdote, Rational started out in mid-80's as a startup doing Ada workstations[1] (think Ada machines), with IDE capabilities that probably only Lucid C++ had in the 90's [2].

[1] https://en.wikipedia.org/wiki/Rational_R1000

[1] http://datamuseum.dk/wiki/Rational/R1000s400

[2] https://www.youtube.com/watch?v=pQQTScuApWk


I remember reading a couple months ago that bare metal using Rust was in a bad state. If I recall correctly the outputs were prohibitively large, among other issues.

Has that changed?


The real reason that Rust isn't necessarily wonderful for bare metal is that a lot of the things you need to do aren't stable yet.

Binary size isn't a problem, though. A lot of the threads talking about sizes aren't doing all the stuff you need to get truly small binaries. That's because, as this stuff is on nightly, it has much worse (or no) docs, so it's easy to miss things.

I'm working on a little kernel, the one linked in the first line of the post, and it's great.


If you want to play around with the code in this post that requires nightly, multirust makes it much more pleasant to use nightly and / or stable rust.

https://github.com/brson/multirust


Very cool! Rust has been impressing me alot these past few months.


[deleted]


Don't hold your breath. The kernel is huge and Linus is happy with C.


Rust needs a Unikernel, there is no need for full blown OS.


I think the MirageOS guys are thinking into eventually replacing the C parts with Rust.


Rumprun already has Rust support.


Very nice. A few more months and that should be stable.


Thanks for sharing!




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

Search: