(no title)
quake | 3 years ago
It's very hard to unlatch your brain from some of the common C/C++ embedded principles of static context variables and thinking of the hardware registers as "owned" memory, which you have to do in Rust. The auto-generated HAL crates aren't that great unless you're using the most common ones like stm32f4 or RP2040. Even then, it's hard to create a portable device driver without delving into generic-hell.
That all said, the ecosystem is moving fast, and a lot of my gripes above are just a product of the embedded rust ecosystem being very new in comparison to C. I do love rtic as a framework, and while I've given embassy a try I think it's trying to do too much aside from being a good async runtime, it should just focus on the runtime and not with stuff like creating its own entire HAL. Hubris and humility are fascinating but I just haven't gotten around to tinkering with them yet.
Lots of good tooling too, and the fact that most of your original C/C++ debugging tools are compatible with rust binaries is just the icing on the cake.
I know that there's the whole Ferrocene project, but until that produces results, stick with C if you're doing safety-critical applications, especially if they need to be certified
jsmith45|3 years ago
I’d much rather have them exposed as some form of atomic that is restricted to operations that are atomic on this specific hardware.
quake|3 years ago
Good HAL crates don't have the entire register set as one struct, thank goodness. And for something like printing a message to serial in a panic handler, there are still unsafe options to yank control of registers, same as C.
What I really like about some of the HAL crates is the usage of builder patterns while configuring a peripheral. Setting up timer parameters before starting it by design is chef’s kiss wonderful.
But if you want to put the whole peripheral set in a static global, you can do that with a RefCell<Mutex<T>> or something similar. Or just yolo it and use unsafe blocks to ditch the mutex.
mgsouth|3 years ago
Yes, it's all trade-offs. You aren't going to avoid all shared responsibility, and will always need micro-coordination. But safe Rust forces you to assign ownership to every structure, at every scale. Some similar effects to opaque data structures, or actors. But unlike those techniques, it allows you to dynamically transfer responsibility.
The most obvious issue is use-after-"free", use-before-"allocate". "Free" and "allocate" don't just refer to malloc--it's any situation where you pinky-swear you aren't going to be touching something.
But more problematic is this kind of code is easy, even natural, to write in C:
- Task A is waiting for a state changes on resources Q1 and Q2. When it sees a particular set, it will modify resource Q3's state.
- Task B is firing off events to Q1 and handling error responses. It might need to stop Q1.
- Since Q1 is no longer changing state, what happens to Q3? Who's responsible for keeping stuff straight? What's "stuff"? Do we need to worry about Q2?
Rust is going to very strongly push you to explicitly designate what owns and is responsible for Q1, Q2, Q3, at all times. "B's got Q1, so A can't even look at it. I'm @#!* going to have to make A tell B what it wants and let B handle it." That was painful, but a good thing.
* Note to self: Write a blog post titled "Atomics won't save you now!" Start a band named "Useless Atomics".
zaarn|3 years ago
You could also write wrappers, for example one that automatically turns off interrupt when you do a write operation or gives you a guard that lets you write and read the register, which turns off interrupts until the guard is dropped. Or one that has a lock or uses atomics. Plenty of options you can use here.