← Back to context

Comment by rcxdude

1 day ago

I think it's interesting because they seem to have built some vaguely pretty decent interfaces and drivers. Before that there were some attempts to make a rust embedded HAL but I think they were a bit too basic and didn't seem to get much traction. Also async interfaces are probably the most generic, because you can hook them up to superloops, single-threaded applications, and threaded code relatively easily (at least, more easily than the other way around), and IMO one of the big reasons Arduino stayed firmly hobbyist tier is because it was almost entirely stuck in a single-threaded blocking mindset and everything kind of fell apart as soon as you had to do two things at once.

> superloops

I’ve been doing async non-blocking code for decades, but this is the first time I e seen that word used? I’m assume you’re meaning something like one big ass select!() or is this something else?

> IMO one of the big reasons Arduino stayed firmly hobbyist tier is because it was almost entirely stuck in a single-threaded blocking mindset and everything kind of fell apart as soon as you had to do two things at once.

This. Having to do something like this recently, in C, was not fun and end up writing your own event management layer (and if you’re me, poorly).

  • Superloop is common terminology in the firmware space. They are cruder than a giant-state-machine-like case statements(but may use still them for control flow). They usually involve many non-nested if statements for handling events, and you usually check for every event one by one on every iteration of the loop. They are an abstraction and organizational nightmare once an application gets complex enough and is ideally only used in places where an RTOS won’t fit. I would not consider asynchronous frameworks like Embassy to be superloops.

    • This superloop pattern can also appear in more abstract scenarios as well.

      The wildly popular ESPHome is also driven by a superloop. On every iteration the main loop will call an update handler for each component which then is supposed to check if the timers have elapsed, if there is some data coming from a sensor, etc before doing actual work.

      This pattern brings with it loads of pitfalls. No component ought to do more than a "tick" worth of work or they can start interfering with other components who expect to be updated at some baseline frequency. Taking too long in any one component can result in serial buffers overrunning in another component, for example.

    • Superloop is arguably how every PLC that is programmed in standard way works.

  • I'm surprised nobody has put together a cooperative threading C framework using the -fstack-usage (https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#in...) option supported by GCC and clang. With per-function stack usage info, you can statically allocate a stack for a thread according to the entry function, just like async Rust effectively does for determining the size of the future. Context switching can be implemented just like any other scheduling framework (including async Rust executors), where you call the framework's I/O functions, which could just be the normal API if implemented as a drop-in alternative runtime.

    Googling I see people attempting to use -fstack-usage and -fcallgraph-info for FreeRTOS, but in an ad hoc manner. It seems there's nothing available that handles things end-to-end, such as generating C source type info to reflect back the computed size of a call graph based on the entry function.

    In principle Rust might have a much tighter bound for maximum stack usage, but in an embedded context, especially embedded C, you don't normally stack-allocate large buffers or objects, so the variance between minimum and maximum stack usage of functions should be small. And given Rust's preference for stack allocation, I wouldn't be surprised if a C-based threading framework has similar or even better stack usage.

Embassy provides some traits, but it's pretty much expected you'll be using traits from embedded-hal (both 0.2 and 1.0).

  IMO one of the big reasons Arduino stayed firmly hobbyist tier is because
  it was almost entirely stuck in a single-threaded blocking mindset and'
  everything kind of fell apart as soon as you had to do two things at once.

I think Arduino also suffered because they picked some super capable ARM chips and weren't really prepared to support people migrating away from AVR. Even the Uno R4 is obscenely complex.

Conversely Embassy suffers from being immature with some traits that haven't really been fleshed out sufficiently.