top | item 32495551

(no title)

quake | 3 years ago

I've done some small embedded Rust applications on various STM32 and RP2040 boards. I've been doing embedded dev in C professionally for 7 years and Rust full-time for 2.5, mix or system and bare metal.

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

discuss

order

jsmith45|3 years ago

Ouch. Making you treat hardware registers as owned (or use unsafe to access them) feels like it would be unpleasant if writing single threaded firmware, where the only preemption is the interrupt handler.

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

Unsafe is exactly what they are in C. They're essentially volatile memory that can change at any time, and woe befall anyone messing around with the same register without synchronization when using an RTOS.

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

Atomic ops only address a small part of the problem.* Sure, your internal queue pointers will be sane, and two-stage register settings won't be interrupted. But locks are very fine-grained, and aren't going to help you keep co-ordinated in-the-large. In fact, you could argue that each mutex is a warning about muddled coordination. "Who's going to be changing this queue?" "Everybody!" "Better put a lock around it."

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

Being owned and unsafe feels like the most natural fit. If multiple threads (or the interrupt handler) manipulate a register while you're already working on it could leave you in a very undefined state you have no idea about. In this case, rust makes you promise that you checked that this will be entirely safe.

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.