This is amazing. Register and peripheral config is so easy to fuck up and waste a whole day chasing down because of limited debugging capabilities on these platforms. I've hit just about every one of those scenarios these compile-time guards now catch on one project or another.
I know this has me really exited too. I think there is so much potential for rust in the embedded space. Personally, I'd be content if the core team just concentrated on making rust the best platform for doing embedded programming. IMHO that's where rust can really blow the competition away.
Honestly it depends upon what people expect from "the port". A lot of people seem to be content with Arduino-like software compatibility: high-level constructs that offer basic functionality expected across a larger number of mcu's. Even something as "simple" as a timer peripheral will vary greatly across manufacturers and families of mcu's -- something you'd care about for real-time control, but not care about in other cases.
ARM controllers are (relatively) uniform and the svd files do a lot of the work by providing the mapping for the memory mapped peripherals.
Across other devices it becomes even more complicated. AVR devices, AFAIK, are not providing memory mapped flash. There are also differences in byte v page erasable flash and eeprom. How would something like this map to Rust? I don't know and I'm honestly skeptical of the practical use but there is a quite serious effort put into making the AVR backend for LLVM ready for use.
---
It really makes me wonder a lot about "embedded" programming and where it is heading. If I were to bet I'd say that there is a significant movement to employ more and more powerful "microcontrollers" in order to allow remove all the resource constraints typically associated with "embedded" programming.
Stuff like Esprino, MicroPython, huge layers of abstraction etc. are today kind of nice tools for learning and starting out -- but give it a few years and somebody will ship a successful product build around this. It doesn't matter that the BOM cost is higher, the current draw is larger and the complexity and security understanding decreased -- it enables a faster time to market and in the end that is what makes the business.
The same is happening in FPGA development.
The old-fashioned hardware folks say that "not how it's done" -- myself included. But you know what they say:
> When the wind of change blows, some build walls, while others build windmills.
As vvanders says, the hard data shows plenty of the market is still maximizing profit by using 8-bitters (33%) and 16-bitters (26%). That's a combined 59% of microcontrollers a 32-/64-bit-only language would be leaving on the table in terms of deployment. Even ATS language, much more difficult than Rust, has already been deployed on 8-bitters.
Hopefully, Rust stays adding whatever features are necessary to support this stuff. If not, the Haskell crowd can always grab it up with DSL's like Ivory:
Knowing very little about the low-end micro-controller manufacturing economics, isn't there some kind of lower bound limit where the marginal cost of the chip itself is utterly dwarfed by the cost of the pins and packaging?
Sure, a 32-bit chip is bound to be bigger than an equivalent 8 or 16-bit one, but e.g. the pulpino project has designed a 32-bit one using only 11.6 kGE. Not very much nowadays. For comparison, according to someone from Atmel, the AVR core is 12k gates, and megaAVR is 20k gates (https://www.embeddedrelated.com/showthread/comp.arch.embedde... ).
I have no idea. They've probably optimized the hell out of it in ways we can't understand. I mean, I thought about getting academics to cover cost of putting Leon3 or RISC-V on a cutting-edge node where 32-bit would be so cheap FOSS could undercut them. They'd need to do peripherals, too, but EE students always need projects to learn with. :) The MOSIS runs I saw at the time had packaging alone at $10-20 a chip. I know it goes down for higher volume but how low can I get it on a new product doing maybe 10,000/units a year?
Well, let me show you how low they get it so you know what your reusable-for-better-products micro would compete with:
The smallest one is designed, masked, printed on silicon, packaged and sold in 5k quantities at 24 cents a chip at a profit! That's nuts! I can only imagine what the 4-bitters cost. The cheapest 32-bitter I found were 32-bit NXP's at 10,000 units for about 50 cents. So, they're getting there. It's an understatement, though, to say the manufacturing costs are highly competitive in this sector. :)
Glad to hear it! Good languages as you agree have the potential to keep dev and material cost down, so there's no trade-off. But people seem to hate learning...anything, and was worried the market had already gone to too powerful devices out of laziness. Glad to hear the battle is not yet lost!
> It doesn't matter that the BOM cost is higher, the current draw is larger and the complexity and security understanding decreased -- it enables a faster time to market and in the end that is what makes the business.
I think consumers are still going to be cost-sensitive in quite a few markets so BOM will still be critical. I'd argue you're already seeing this with more than a few companies using Android + mid-tier ARMs when market time matters more than final cost.
There's probably just going to be less people that know how to do it, much in the same way native programming is today.
CMSIS provides an abstraction layer for peripherals, so at least the basics are the same between manufacturers. At least it's something, usually when switch MCUs you have to rewrite everything related to peripherals.
True, but the main way an ARM licensee has to differentiate their product is adding unique peripherals. So the generic functionality is in CMSIS, but the reason you chose the processor may not be.
If you look at a die photo and see what area goes to what, pretty quickly you conclude all microcontroller makers are just selling value-added flash memory.
Absolutely, device drivers must still be written. My observation is just that these device drivers are hidden deeper and deeper levels of abstraction, just like on a regular PC.
I have an xmos board(xcore architecture), although llvm has a backend for it, it is missing debugging support for timing analysis(XTA) and also outdated so potentially missing on various size optimizations.
Even ignoring that, because llvm instruction set mismatch between versions, i can't feed the rust generated IR to their firmware generating tool.
If you see their language xC(https://en.wikipedia.org/wiki/XC_(programming_language), it extends C with various pointer types(restricted, movable, alias) for memory safety. Rust definitely is a good fit for this kind of development with borrow checker, traits etc.
They could use Julia's "resurrected LLVM "C Backend"" and compile down to C, and then let developers use existing tools to get running on their MCU. Would probably mess with the debugging experience though.
> The hard challenge in embedded is porting to every mcu. Can this be done without convincing mcu companies to do it themselves ?
Agreed, but I think long-term its certainly going to be easier doing that on the rust platform with a tool like cargo than the current state of affairs.
Maybe if rust just concentrated on the hobbyist/prototypers at first i.e. arduino / rasberry pi, to make that experience as great and pain free as possible and really showcase what can be done with rust and cargo. That would at least make it a viable tool for professors teaching the next gen of engineers and all the startups/prototypers/hobbiests.
FWIW, I share your feelings regarding prioritizing this. In general, it's something we want, but we're still discussing what the overall goals for this year will be. We'll see!
That's assuming that you have a functional ICE[1] and aren't dealing with a timing issue in another subsystem :).
ICE pins are usually configured with the exact registers this article talks about. It's common for UART/SPI serial port configuration to be a part of enabling ICE. Some chips also let you disable the ICE pins so that you can use them to pick the cheapest chip possible.
Because ICEs are overrated. Many systems can't be debugged by single stepping (think servos or anything with physical hardware being controlled). ICEs rarely work well—every single one I've ever used was completely unreliable and required lots of fiddling to make it work. And then the next day you had to start the whole fiddling process over again. In the end they aren't very productive except for very specific types of bugs (they are invaluable in the very beginning of a project when you are bringing up a board). Once everything is generally up and running I find them to be pretty useless.
Yeah if you can get that to work. It's usually a serious hassle.
Another issue is that with microcontrollers you are usually debugging really low level stuff like interrupts where you can't even do printf debugging. Or you are setting registers on some black box subsystem and it just won't work and the only way to fix it is just keep randomly changing registers until you find the one you got wrong, or if you're lucky find working example code and bisect from that.
Or you've got some timing sensitive code that you can't stop and the only debugging channel that is fast enough is toggling group connected to an oscilloscope.
Though obviously the Rust approach provides a lot more guarantees, especially at runtime.
The one thing that interests me is how flexible this approach would be at dealing with cases where you have to hack around a hardware bug. For example, I had a board on it with an I2C expander whose default I2C address was not one the STM32 was able to address, but could be changed at runtime with an I2C command. So this was dealt with in firmware by initializing those pins initially as GPIO and bit-banging in the message to change the address, then changing them over to the regular I2C peripheral and taking it from there.
How possible would something like that be with this much more tightly constrained Rust IO model?
Looks to me like this is something that can be added to the take() function. After all, you're generating the crate yourself with svd2rust. I wonder if these "auto-crates" would be viable for submission to crates.io. This way you could solve that HW bug once and for all for everyone.
These problems that embedded rust is trying to solve are not a big deal. In C you try to minimize what happens in any interrupt, usually just set a flag, save a result, and return. You generally use atomic instructions on gpio. To set or clear a bit mask is atomic so there is not much reason to read-modify-write gpio in an interrupt or anywhere. I think these solutions would create more work than they save.
Keep reading, they also cover dealing with exclusive access to subsystems. I could totally see this being applied to DMA and other long-running peripherals to great success.
I'll also disagree that this is a small problem. We had one of these that was so hard to track down that it involved 100+ devices running in a stress loop over 24 hours with cameras looking for the regression. Ended up being a timing sequence that could have been caught by a system like this.
The repro was incredibly infrequent but when you've got millions of units even 0.01% chance of something happening is too often.
I think the project is neat, but I've also written stuff to do all of that for embedded C projects. Where you'd allocate pins on the board for the peripherals you wanted to enable and it would complain if you double allocated.
That it's auto-generated is the neat part, but then again, you could auto-generate C code that was just as robust (though admittedly, a lot of that robustness would be pushed to run-time checks with C code).
First, you can always use unsafe and access low level registers ignoring all the synchronization. You are still getting typed access to the bits (well, contingent on SVD file quality), which is an improvement over hand-writing bit manipulation code (or using wrong constant for shift/mask in STM32 HAL, for example).
Second, look at the port splitting example. If you split port into pins, when you can independently move these pins into different execution contexts and you won't need any synchronization as well. Write to a pin would be a single write to BSRR register -- no read-modify-write. So you are getting safety and about the same generated code as for hand-written code (well, except that if you want to do multiple pins at once).
What if you want to reconfigure port? This is, actually, what was particularly annoying for me with the old version of svd2rust I/O. Even though I know that my two subsystems operate on completely different pins, I still had to pass CRL/CRH around, to avoid potential race when reconfiguring pins.
This new version has the same property, though, -- according to the article.
However, the solution is pretty straightforward. By using ARM bit-banging feature, you can have the same "split" atomic-share-nothing-style API for the port reconfiguration, too. Similarly, you would split, for example, GPIOA into 8 pins and each pin would allow you to both input/output data and change port direction.
So, bottom line, I think you can get best of both worlds most of the time: safety and performance of the hand-written code. And in corner cases, you can still ignore all the safety and access registers without any overhead.
I don't really understand what is this approach lacking for career firmware engineer.
(disclaimer: I haven't ported my firmware yet, so I could be wrong in my assumptions how this new I/O works).
Yeah the current way of developing these things is slow AF so I'm not buying it. I've done Haskell->Verilog, its an amazing development speed up, and people give the same "but that's not the hard part" complaints.
The fact is, no one's development process is that parallel, so critical-path-style arguments don't work. Turning even just a few easy parts free does reduce dev costs, and in these case we're clean-sweeping like all the low hanging fruit.
As all problems, these bugs are avoidable, but having a high speed language that helps you avoid them seems like a good thing. Having had to fix this kind of stuff before, it sure felt like a "big deal" at the time. :-)
I can't say that I agree. I would rather thread through some boilplate, rather than have a hard to debug issue on a platform that I have low visibility into.
It sounds like this would work for beginners or certain people who know rust and not C. A new firmware language could never be widely adopted if it is not made for the career firmware engineer to use.
For the xpcc.io library, that I was involved with when I was in college, we ended up defining our own XML format and write importers for the custom formats that ST uses. For the LPC controllers from NXP we had to transcribe the information manually from the data sheet (our attempts to automatically parse the pdfs and extract the info never worked out).
"The CMSIS System View Description format(CMSIS-SVD) formalizes the description of the system contained in ARM Cortex-M processor-based microcontrollers, in particular, the memory mapped registers of peripherals."
Is it that it would be impossible to write an SVD file for another architecture, or just that vendors don't do it?
If it's possible, and this tooling makes SVDs a really powerful tool for porting, then perhaps it would be easier for the community to write SVDs for non-ARM chips than code.
Probably, but companies wouldn’t do it since they have their own tooling. And I don’t think the community would like to write it neither. Look at a SVD file for a single MCU.
It’s a more than 10k loc XML file. It is too much effort for the community to do that and verify it. And as an embedded system engineer, I wouldn’t trust the community-driven register definition because even vendors have some bug on it and it is almost impossible for the community to write a better register definitions than the vendors themselves. This is a vendors’ job, not the community's.
DeviceTree is the standard / tooling you'll see outside ARM micro-controllers. Started on Power, then found a place in Linux's ARM tree, and is now spreading further.
This is, amusingly, a lot like Modula I, circa 1979. That had language support for device registers, cooperative multiprogramming, and interrupts. A very nice way to program a PDP-11 at the bare metal level.
OT but I’ve just got into microcontrollers for the first time in the last week via an Arduino Uno. I’m absolutely in love with it and I now need to go a bit deeper (I’m working on a synth). I get a lot of the concepts like the registers to control the timers / interrupts but I’ve found a good guide to be lacking. I haven’t seen a list anywhere of all the available registers and what the bits do. I get that it’s different for different chips but I thought the info for more common ones (like the ATMega328) would be easier to come by.
If you want results rather than a learning experience, consider upgrading to a Teensy 3.2 and Teensy Audio Adapter. The Teensy (ARM Cortex M4) has the power and I/O to produce quality audio. It still uses the Arduino IDE and APIs, and the audio library gives you a high level toolkit.
I have implemented several soft synths on the Teensy, both with and without the audio library. It works.
Thanks for the tip. I’d seen Teensy mentioned in my travels but didn’t look any further. At the moment I care about the learning over the quality but I’ll check this out once I understand the space better.
The manufacturer website is where you usually want to go to find the documentation. The product datasheet will specify the peripherals and register set for the chip you're looking at, including a description of each register and the fields within them. Some companies have a different name for this document. E.g. ST usually define their registers in a Reference Manual.
To add, ST has a 'datasheet' and a 'reference manual' for each chip and chip family. The datasheet is usually under 200 pages and is focused more on electrical information and is focused on a few chips. The ST reference manuals are generally over 1000 pages and cover all of the software/register information for a family of parts.
You will want to code on the avr directly. The problem with audio processing on arduino is that there is a 1ms system tick that is higher priority than user code. It causes audio signals to sound scratchy, even a solid tone because the arduino pwm is all interrupt driven.
Interesting. For my first experiments I don’t really mind - will just be happy to get sane output driven by midi!
From what I gather, the technique is to change the PWM in an interrupt continuously to match the amplitude of your output wave. During that 1ms, what happens? Does the PWM still output a regular wave but I can’t adjust it? Or does the PWM wave itself stop?
The first problem with doing this in arduino is that the pwm is interrupt based. So that limits the frequency. There is a tone library that can generate a pwm for a specific tone, but these are interrupt pwms and it cannot work correctly with the 1ms tick causing jitter. I see arduino projects every now and then as a consultant and one of them was to generate tones. It sounds scratchy out of the box if all you do is generate a tone. Had to modify arduino core for it to use hardware pwm. Why didn't they use hardware pwm in the first place? Its because they needed pwm to work on any pin not just the 8 or so pwm pins, and they sacrificed a lot of performance to do it. I love arduino for inventors to get something running, but for coders and engineers, you guys are smart just program on AVR in C, or better yet program on a modern arm cortex in C. If you want a blazing fast mcu check out the FRDM-K64F, this is 32 bits 150MHz vs the AVR's 8 bits and 20MHz, plus it has a nice DAC so you won't have to do pwm modulation, not to mention 256kb ram vs the arduino's 8kb.
Kudos, can't wait to see where to goes from here.