Raspberry Pi Bare Metal Programming with Rust(blog.thiago.me) |
Raspberry Pi Bare Metal Programming with Rust(blog.thiago.me) |
> 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.
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.
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.
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.
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.]
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.
The question is what does Rust have to offer as your embedded program grows.
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 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/?
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.
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#
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.
Has that changed?
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.
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.
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://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.
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...)
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.
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.
- 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.
Compilation to C is a toolchain feature, not a language one.
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.
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.
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.
(prices checked from digikey)
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.
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.
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!
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 can do GPIO bit-twiddling pretty fast: http://skpang.co.uk/blog/archives/323
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...
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.
[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) ]
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
Namely "Tiny, Ubiquitous Machines Powered by D" and "x86 Bare Metal and Custom Runtime Programming".