20 ms·
Rust on AVR: Safer microcontrollers almost here
- pjmlp 10y agoNice work, although there are already a few options for safer microcontrollers when one bothers to look beyond C. More options is certainly even better.
- IgorPartola 10y agoHaving more programming languages for embedded platforms is always great. If all you know is Rust or Python or JS, etc. it is great to have it as an option. But I have to wonder if the specific claim of type safety really translates here. In my (albeit limited) experience, certain classes of errors just don't occur on embedded systems because there is not enough room for them. For example, there is no double free() because heap memory is not a thing. I suppose you can still have off by one errors, and problems with referencing/dereferencing, but once again in my experience you usually find this stuff pretty fast because your code just won't run. Am I wrong here?
- extrapickles 10y agoOn the smallest (I've used ones with 16 bytes RAM) you simply don't have room for doing much at all so you pretty much only use registers, none of this fancy pointer stuff and you can easily fit everything its doing into your head as its program space is only big enough to fit 64-256 assembly instructions (a paper pages worth). It can be hard to pin down when something switches from a fancy logic circuit to a microcontroller on the low end of the spectrum. In my opinion, it stops being a microcontroller when the RAM is external to the CPU or it needs to be provided multiple voltage rails.
- yokohummer7 10y agoI have zero knowledge about microcontrollers, so forgive my dumb question... But how do you program with 16-byte RAM!? I believe even ENIAC had more memory than that? (Though I'm not sure if ENIAC had such a concept like main memory.) What useful thing did you conduct with it?
- bleke 10y agoNot everything needs ram you can perfectly write program having only few variables. Mostly what saw is exchange part of circuit to microcontroller which sometimes led in saving big chunk of components, you can you mcu as simple state machine for specific task in circuit like power controller or sensor processing unit... For example I saw 3 mode led flashlights selling on ebay for 3$ have PIC12 (probably chinese clone).
- kosma 10y agoConsider it yourself: what kind of math can you do in 16 bytes of RAM? What kind of control? I suppose you can fit some running average, statistics, logic equations, maybe even PID would fit. A single for loop takes just one byte. These 16-byte microcontrollers are usually very small, and have very few pins (usually eight); they are the electronic equivalent of a very short shell script that glues some stuff together.
- LeifCarrotson 10y agoYou have lots (where "lots" means maybe 512 bytes, or possibly 8k bytes) of flash memory. Flash is cheap both in cost/silicon area and in the power budget. You store constants and your actual program code in Flash. The limitation of Flash memory is that you can only erase a whole page state time. On many of the smaller microcontrollers, one page may be larger than your SRAM! So you treat Flash as read-only, writing it with a separate hardware programmer. You store state in the limited SRAM. You would also read or modify external state by loading or writing to special function registers outside of the Flash and RAM address space, where the "memory" mapped to that bit or word would do something in hardware. It would tell you whether a digital pin was on or off, or turn it on or off, or tell you the analog voltage on an input pin, or configure the pulse width of a pulse-width modulation timer, or read/write to a communication port, or set up a hardware interrupt timer. Here's probably the most common example of this class: http://ww1.microchip.com/downloads/en/DeviceDoc/40001239F.pdf http://ww1.microchip.com/downloads/en/DeviceDoc/40001239F.pd... They're often used to 'glue' two separate circuits together. For example, you could use it to measure an input, and in one mode output a high voltage if that input was on, or in another mode output a low voltage. In more physical examples, you could read the position of an analog input knob and control the pulse width of a TRIAC to control the speed of a motor. You could monitor the capacitance of a touch sensor (with an ATTiny) and turn a device on or to standby, or do a hard reset if the button is held down for a long time. You could modulate an RGB LED through a rainbow of colors. You could run a flashlight in a high power, low power, or strobe mode depending on how a button was pressed. And so on.
- taneq 10y ago> For example, there is no double free() because heap memory is not a thing. This was my experience back when I first did embedded stuff. These days unless you're programming something that costs less than a dollar to make, you've probably got at least double digit KBs of RAM and a C compiler. Not saying it's good practice to be using malloc() in this environment but you'll get away with it.
- kosma 10y ago> Not saying it's good practice to be using malloc() in this environment but you'll get away with it. Actually, for the sake of keeping code modular and portable, it's fine to malloc() once on boot and never free(). It's the "dynamic" part of memory management that's dangerous on MCUs, especially on those without an MMU (as fragmentation gets in the way very often and you can't really reason about memory usage anymore).
- wyager 10y agoIf you're using malloc on boot only, you can almost always replace that with a static data declaration, which usually gets put in the same space the heap would otherwise use. It's better because the compiler can drop malloc, saving you some flash space, and static analysis tools can see how much RAM you're using (and if something is clearly going to blow up).
- daeken 10y agoThat used to be the case, but as more functionality makes its way in (USB support, networking, etc) the attack surface grows substantially. Right now not a lot of that goes on, but that's changing -- and fast.
- pjmlp 10y agoA good example would be the ESP-32, powerful enough to run MS-DOS (520 KB hurray!), dual core with extra modern peripherals. Which means every programming language we used to enjoy back in the day are possible to target such environment, assuming someone would bother to port them to it, that is.
- yjftsjthsd-h 10y agoI wonder whether it would be practical to write an emulator and actually run DOS et al. You'd lose some memory and speed, of course, but we might be powerful enough that it doesn't matter.
- shmolyneaux 10y agoA simple example for 8-bit microcontrollers would be concurrency issues in an interrupt handler. Heap memory is still a thing for larger devices running on a RTOS, in addition to threads, mutexes, etc.
- loeg 10y agoThere are people who think ARM SoCs with a few hundred MB of RAM are embedded systems. You can fit a lot of errors on that scale of embedded platform.
- imtringued 10y agoThose are considered embedded because the intention is to just flash them with software like android once during the manufacturing process and then never update it.
- pjmlp 10y agoLove the Android example regarding the update.
- adrianN 10y agoMicrocontrollers are a continuum. On the tiniest you might be right that there is no room for error (I doubt that though), but there is a large class of chips that are powerful enough to be connected to a network. Those are dangerous.
- pjmlp 10y agoYes, Therac-25 as an example. The code will run and corrupt data without crashing, due to off by one error. In the proprocess a few people got cooked. Although the code was in Assembly, the situation wouldn't have been different if it was coded in C, due to the lack of bounds checking.
- janzer 10y agoA PDP-11 is the classic example of a microcontroller. ;) Here's a picture of the model used for the Therac-25 the PDP-11/23 http://www.physics.purdue.edu/~jones105/pdp-11/images/IMG_2877.JPG http://www.physics.purdue.edu/~jones105/pdp-11/images/IMG_28...
- pjmlp 10y agoAs I mention on another thread, an ESP-32 would be good enough to run MS-DOS. Your example makes it even more obvious that current microcontroller are actually quite powerful, when we look at the hardware resources of the 60 - 70's.
- milesrout 10y agoYou're definitely right.
- iuguy 10y agoI'd be interested in seeing the challenges and outcomes of assumptions made porting between 64-bit von neumann and 8-bit harvard architecture systems. Most of the AVR community tends to code in a mix of C and assembly, I think Rust is possibly not the best language to port, and type safety is not really an issue that's going to sell it. To put it into perspective, Rust on a more powerful IC like an ESP32 or STMF32 makes sense to me, as you have a lot more to play with, but I've spent the best part of a year and a half working on a design constrained within 512 bytes of dynamic RAM, 512 bytes of SRAM and 512 bytes of EEPROM. I'm not sure what type safety buys me when I'm already trying to squeeze out every last bit. All of this is not to suggest we shouldn't try new things, of course we should. Hacking things previously thought impossible is a longstanding and welcome tradition. However, I'm struggling to see how this would increase adoption of Rust, or make things significantly easier for AVR users vs say Arduino scratch.
- deleted 10y ago[deleted]
- notalaser 10y agoBugs that occur due to dynamic memory allocation and freeing are definitely not a concern for 99.99% of well-written embedded software. We avoid dynamic memory tricks not because of memory size constraints, but because it makes verification difficult and introduces additional uncertainty in the program's behaviour and timing. Even in systems where there's plenty of memory to support it, dynamic memory allocation is done sparingly -- sparingly enough that it can be properly managed via code reviews and the like. Real-life, but somewhat extreme metric: in my last project, there were two instances of dynamic memory allocation, in about 100k lines of code, and both were in third-party code (we could have replaced them but you know, not broken, nothing to fix). At the other end of the spectrum, I think we had about one dynamic memory allocation at every 6-700 lines of code, but almost all of them were confined to a single scope, and they were largely due to code smell. (Edit: I think at one time we got really pissed when we saw how out of hand it had gotten and we went back and replaced most of these occurences, but I don't remember the metrics.) In any case, lifetime management at the lexical level should not be a problem in most embedded applications, because there should not be much lifetime to manage. By "lexical level", I mean "things that are exposed to the language and manipulated through its primary means" -- variables, functions and so on. (Edit: there are legitimate exceptions to "should not be much lifetime to manage", but they are very few; the non-legitimate ones are in code of such poor quality that Rust won't help: what those codebases need isn't a new language but different managers, different programmers, or both.) Unfortunately, this retains a whole class of errors that, to my knowledge, Rust (or any other language, frankly) can't manage. Off-by-one errors, for instance, can be handled if they occur at the end of the buffer (because you'd be spilling out of the container) but there's no way to handle off-by-one errors inside a buffer (you meant to say "read from the beginning of the buffer up to the last character that's been read from the serial line" but you actually ended up including one more character because you wrote off + 1 instead of off). It's still up to the programmer to manage these things, and sloppy programmers will still write sloppy code that does this sloppily. However, and this is a very big and very important however, we're talking about a level where any kind of additional safety helps. Returning to the previous example, off-by-one access errors inside a statically-allocated buffer are generally easy to spot and get caught very quickly during QA (since ooh, let's look at the buffer is the first thing you say when you get a bug about repeatedly garbled data when reading data from the serial interface). Off-by-one access errors outside the buffer (accidentally zeroing all the TX buffer, oh, and the first byte of the structure next to it that's used by an entirely different module) are the icky ones that sometimes fly under the QA radar for months. I don't think the safety gains are on the level that the Rust fandom expects, but no one in their right mind is in a position to not welcome them. No, Rust won't solve everything, and bad programmers will continue to productively shoot themselves in the foot in Rust, too, just as they can productively shoot themselves in the foot in pseudocode. But for people who realize that there's a pretty big chance their next bug is going to get somebody killed, anything that reduces the bug population is a welcome addition. tl;dr In most embedded scenarios, this will help less than the Rust community wants to think, but enough that sane people welcome it!
- ekidd 10y agoI used to work as an embedded systems developer (for systems where a single failure could cost a quarter million dollars), and I've experimented with kernel-space Rust just for fun. Rust is a surprisingly nice language on bare metal. If you use '#![no_std]' in Rust, it disables the standard library and substitutes the 'core' library. This has no 'malloc' (unless you supply one, which is easy) and no OS-level features. But you still get iterators, slices, traits, "scoped" closures and generic types. You get a lot of abstraction with basically zero overhead. And don't forget that embedded systems have lot of state machines, which are nice to implement using Rust's "tagged union" support and exhaustive 'match' statement. Here's an example of a Rust I/O port wrapper (https://github.com/emk/toyos-rs/tree/master/crates/cpuio https://github.com/emk/toyos-rs/tree/master/crates/cpuio) and an interrupt controller interface (https://github.com/emk/toyos-rs/tree/master/crates/pic8259_simple https://github.com/emk/toyos-rs/tree/master/crates/pic8259_s...). These APIs are reasonably pleasant for something so close to the metal. Rust would be a very agreeable programming language on an AVR system, IMO. And not necessarily because of pointer safety, either.
- StavrosK 10y agoAs a hobbyist and C junior, if I can get anything higher level than C, I'll be ecstatic. Of course, it's going to be the same as with micropython, which is fantastic: The entire ecosystem is in C. I tear my hair out every time I have to wrestle with C for the simplest things, though, whereas Rust is a joy to write.
- yjftsjthsd-h 10y agoOn "full size" systems we solve that with FFI, but I don't know whether that would work here is not.
- ComputerGuru 10y agoHaving recently worked on a few projects written in C++11 for embedded systems (again, no standard library), I must concur with your comment. C++11 brings a lot of the same features (minus the functional coding components) to embedded systems making program flow a breeze.
- nercury 10y agoWhile I also agree that Rust does no better in unsafe code, I think it allows for some sweet careless use of pointers (references) in _safe_ code.
- dopeboy 10y agoIt's been seven years since I've programmed on embedded systems. I remember C ruling the day. Has that changed at all?
- CamperBob2 10y agoNope. Not in the least.
- boznz 10y agoC++ has gained a bit of ground recently but C still rules the roost especially on smaller micros
- pjmlp 10y agoTo add to it, this was the issue of a few CppCon talks, namely how to bring C devs to write safer code.
- pjmlp 10y agoThere are Ada, Basic, Pascal, Oberon, C++ and Java options available, but C still takes the biggest market share.
- milesrout 10y agoNot at all.
- hvasilev 10y agoC is like a "portable assembly", its goal is to "abstract computers" and that is all. The languages that have attempted to replace it are trying to "abstract problems", which comes at a cost that people are not willing to pay. If you want to replace C you either need to come with a better "portable assembly" or have a significant change in the hardware architecture that requires the change. BTW this constant shoving of Rust down the embedded world's throat doesn't seem to be working.
- ubercow 10y agoI'm so happy for this, it's gonna be great to use Rust for Arduino stuff
- boznz 10y agoI dont know rust from jack so I am interested to know why it will compile a safer program than a well written C one and what makes it better than other safe embedded languages like ADA? Note it wont affect me as I mainly use PIC micros
- deleted 10y ago[deleted]
- nrclark 10y agoYou gain Rust's memory-safety, but also you gain all of its meta-programming facilities. Which means all those #define macros become a thing of the past.
- jononor 10y agoYes, the expressive type system with zero-cost abstractions that makes me really excited about this!
- pjmlp 10y agoThe best thing Rust has over Ada (no one writes ADA any more :) ) is momentum. Although Ada has eventually got an open source compiler, and there are still around 5 vendors available, due to the way it got sold, its key use has been in the area of High Integrity Computing. So unless that is the domain of the work, where no errors are allowed, which could possibly lead to loss of human lives, very few care of using Ada. For the young generations, Ada is kind of something they hear it existed but they never seen live, even though it has enjoyed a regular presence at European FOSS conferences like FOSDEM. So by being modern, Rust can appeal more to the younger generations, also some type system safety rules of Rust for parallel code can only be coded in Ada via SPARK.
- jonathonf 10y ago> The best thing Rust has over Ada (no one writes ADA any more :) ) is momentum. Having only recently looked at Ada I don't really understand this. It does look like Ada offers a lot of really nice features that other languages haven't yet caught up to. For example, SPARK allows formal definition of correct code behaviour within the function definition. This appears to me to mean tests are written into the function at the time you write the function. That's huge from a maintenance standpoint, and it likely provides extra information a compiler could take advantage of. > where no errors are allowed, which could possibly lead to loss of human lives, very few care of using Ada. Loss of life is one area, but surely anything financial would benefit from this - as well as anything dealing with personal data (e.g. identity management)?
- loeg 10y agoI'm kind of surprised and impressed that LLVM (and GCC) can target AVR well. It's a tiny machine with some quirks. Registers and IO ports are mapped into low memory. The big ones have a weird 24-bit segmented addressing mode. Some crazy ones have DES intrinsics.
- JoachimSchipper 10y agoIn fact, does anyone know whether LLVM/GCC can target AVR well? I recently had the displeasure of staring at what sdcc produces from pretty reasonable-looking C... (it doesn't help that some 8-bit microcontrollers are really not C processors.)
- Gibbon1 10y agoI wrote a bunch of stuff for the AVR in c. But it's been a few years since I've done much more than a few fixes. Some of the weird issues with targeting a Harvard architecture is I think fixed. I'd say: 'works okay' One thing that annoyed me was calling a variadic function like printf was expensive ~100-120 bytes each call. Eight to Ten printf's --> 1k of flash gone. Far as I could tell most of it was pushing and popping stuff on the stack one byte at a time. I ported a bunch of AVR code to an ARM Cortex[1]. Code size didn't increase much. [1] ARM Processors are approximately half the cost of an AVR. Seriously not important if you're building 1000's of something. Important if you're building 100,000s.
- jononor 10y agoGCC at least is just fine. Was involved in developing an embedded system which was controlled by an AVR. There were some cases of unexpectedly big program sizes. But most was due to using large integers unnecessary (higher than 8 bit) and one case of overly aggressive inlining. Don't remember offhand how we solved the last one, but just took a day or two.
- makomk 10y agoAVR was basically designed from the ground up to be a good platform for C. It's fairly RISC-like, registers are for the most part interchangeable, it has enough pointer registers to support a standard C-style stack and still have two left over for actual pointers, etc.
- sagebird 10y agoWhy do programming language maintainers for a front end to LLVM (rust) need to consider/patch/modify their front end to properly support a llvm backend, like AVR? (My mental model was that once you add a backend to Llvm, every programming language that uses llvm should magically work with it.) Does LLVM's design fail to capture the full problem space? Are there out of band concerns that do not lend themselves to be generally expressed?
- JoshTriplett 10y agoQuoting the article: > Rust's version of LLVM has a few patches on top of stock LLVM, and it also tended to have extra commits cherry-picked on top. Apart from that, Rust needs very little per-architecture glue code. And someone needs to run the Rust testsuite on the resulting version of Rust and make sure it passes.
- elihu 10y agoFrom the blog post, it sounds like the problem is that Rust doesn't use standard LLVM, they maintain a separate branch with a bunch of patches that are specific to Rust. So, adding AVR support to Rust means adding AVR support to Rust's branch of LLVM, which is a hassle. Rust is about to move to a newer version of LLVM, though, so when that happens they'll be pulling in the AVR support that's already in mainline LLVM.
- dbaupp 10y agoLLVM isn't completely platform independent (e.g. all integers are fixed-width, so the frontend needs to know the pointer size to emit pointer-sized integers) and there are some configuration options to be passed down to LLVM/the linker. Additionally, it is useful for the front-end to understand things like the layout of types, and the standard library usually also needs changes to paper over any platform differences (although I imagine AVR doesn't need many, mostly in the compiler-support library compiler-rt, given not much of the standard library will work).
- hedgehog 10y agohttps://internals.rust-lang.org/t/adding-16-bit-pointer-support/2484 https://internals.rust-lang.org/t/adding-16-bit-pointer-supp...
- jfries 10y agoI think that one thing that makes it difficult to write safe microcontroller code is the interrupts that can interrupt the normal flow at anytime. Does Rust on AVR help with this at all, or is it an orthogonal issue?
- jononor 10y agoYes, really interested in how interrupts can be modelled with Rust. Preferably a safe abstraction without going full RTOS?
- steveklabnik 10y agoMy toy OS project has a partial implementation of interrupts for x86_64: https://github.com/intermezzOS/kernel/blob/master/interrupts/src/lib.rs https://github.com/intermezzOS/kernel/blob/master/interrupts... Just enough to get keyboards working, still a lot of stuff to do, but the foundation is there. Used like this: https://github.com/intermezzOS/kernel/blob/master/src/main.rs#L48-L68 https://github.com/intermezzOS/kernel/blob/master/src/main.r...
- pinpelipon 10y agoDoes this mean that some day I might be able to buy a cell phone that has a radio chip that is not full of buffer overflows and other types of flaws?
- pmorici 10y agoPeople still use avr?
- ComputerGuru 10y agoYup. You'd be surprised how much functionality people can wring out of an ATTiny85 or an ATMega328p.
- pmorici 10y agoFigured people would be moving on to ARM M0+ chips for new development.
- dbcurtis 10y agoLatest commit in the git repo 8 months ago? is this project active?
- steveklabnik 10y ago> The backend started as a GitHub fork. It now lives inside LLVM’s svn repository.
- jononor 10y agoWhich repo? Last commit to LLVM AVR git is just marking it as being upstream in LLVM SVN
- Keyframe 10y agoSounds exciting to have options apart from C and asm. How about something for us PIC plebs though?
- pjmlp 10y agoIn regard to safety you could use Basic and Pascal compilers, while Rust doesn't arrive. https://www.mikroe.com https://www.mikroe.com