Readit News logoReadit News
primitivesuave · 4 years ago
A couple years ago, I managed a summer/afterschool program that introduced thousands of kids (age 8-18) to the Arduino. When we would set up a software abstraction to simplify multitasking (something that came up often with more advanced students), we set up something like this:

  int time = 0;
  int max_period = 10000;

  void loop() {
      if (time % 100 == 0) {
          everyTenthSecond();
      }
      if (time % 1000 == 0) {
          everySecond();
      }
      // ...
  
      time = (time + 1) % max_period; 
  }
This `loop` assumes functions are perfectly non-blocking, which obviously is not always true, but works well for 99% of actual use cases. If keeping accurate time is required, the student would compare `time` with the elapsed millisecond time since the last loop invocation, adjust accordingly, and check for any periodic functions that weren't executed in that period. It gets tedious quite fast.

If I had to imagine a solution, it would be to simply have an easy way to enter parameters to the JS-equivalent of `setTimeout` and `setInterval`.

_whiteCaps_ · 4 years ago
http://www.calgaryfieros.com/OSGdocs/ECM-hacking.html

The GM engineers used a similar approach:

> The ECM hardware generates a periodic interrupt at 160 Hz. (Where have we seen that number before?) The entire ECM program operates off of this one interrupt. Except for some initialization code, there is no non-interrupt level code. In fact, the interrupt service routine never returns, it simply cleans the return address off the stack and waits for the next interrupt. There are no other interrupts in the ECM. This means that fully autonomous hardware generates all the high speed signals like the fuel injector and ignition pulses.

> At each tick of the 160 Hz, various tasks are performed. Some are done every tick. Others only on just odd or just even ticks. There are also 16 tasks, one of which is performed each tick. Every tenth of a second, all 16 tasks will have been completed. In general, this means that the ECM cannot adjust engine performance faster than 10 times a second. Perhaps this explains the engine surging at low rpm that many list members have complained about.

primitivesuave · 4 years ago
This is awesome, thank you (and to the other HN commenters) for providing so much valuable perspective as always.
joezydeco · 4 years ago
That is spiritually what their Scheduler library is doing, as mentioned in the article. Which works for a lot of things just fine until one task begins to overrun its time slice or just hangs. And then you're looking into preemption.

But if you're overruning an AVR micro maybe it's time to take the training wheels off and move up to a larger system.

HeyLaughingBoy · 4 years ago
> move up to a larger system

I'll take this one step further and suggest that beginners not start with the AVR, but instead with a Blue/Black pill (STM32) or ESP32. Leave the AVR for the more advanced people who are trying to squeeze every penny out of a project (even there, in many cases STM32 will cost less!!!).

I visit the arduino.cc forums at least once every day and the things that beginners want to do these days is far more advanced than what they would have attempted even 10 years ago. It gets very difficult trying to explain how to do things on an AVR that would be much easier on a processor with far more resources. String vs char* is one of those.

Hell, I don't know why we're even telling beginners to code in C++ in 2022.

My downvoted comment that boils down to "just use an ESP32 and get FreeRTOS along for the ride" is in this vein. The reality is that beginners have a lot of trouble wrapping their heads around writing nonblocking code using timers and something like a FreeRTOS thread is a much simpler concept to explain.

Rant over :-)

jgerrish · 4 years ago
Don't be too quick to dismiss the "training wheels" of AVR.

While ARM and RISC-V may have beefier processors, and will probably be migrated to or adopted, AVR has a lot going for it.

First, a common AVR board like the Arduino Uno has built-in peripherals like a temperature sensor. You can build something out of the box without buying external items. If your target market is cheap educational, that's key.

Second, it offers basic microcontroller features like power management, external interrupts, serial and other stuff.

It provides a terrific cheap platform to understand managing interrupts and concurrency safely in your language of choice. Which can be applied to the bigger boards.

Finally, memory constraints require careful thinking through code. I'm not going to implement a full standard library on an Arduino AVR, so what do I bring with me?

jldugger · 4 years ago
This basically how all of arduino "works". It was quite shocking to see this after working on embedded systems in grad school that used interrupt driven microcontrollers for low power situations to see a while (true) in the core arduino main.c

edit: but it makes sense from the perspective of a education and hobbyist needs to take the simplest option that could work

inamberclad · 4 years ago
Hell, I did that for a prototype product once. Combined it with a state machine and wrote the entire thing to be nonblocking. Worked great.
primitivesuave · 4 years ago
Really appreciate you sharing this - I used to downplay the real-world application of these kinds of methods ("if you were making a real product you'd have a priority queue and a scheduler and so on") so if I ever teach kids again I'll let them know that people out there build real things like this.
gnramires · 4 years ago
I wonder if it's possible to just interleave statements from k different (loop) functions? Then it would seem mostly equivalent to an arduino at 1/k Clock speed.

I think it's not possible to interleave instructions per se (because of registers?), but a compiler should be able to figure out the correct instructions of statement interleaving.

This should eliminate problems with blocking.

NavinF · 4 years ago
You can do that, but it's not very useful in practice and really breaks down when you want to do something more compute intensive that needs all the registers/cache. Multicore MCUs like the ESP32 are pretty cheap ($2 1pc pricing) so if you really care about realtime performance it makes more sense it do realtime on one core and schedule slower work to be done on the other core.
Teknoman117 · 4 years ago
Anyone know if a barrel-processor microcontroller exists? Propeller is sort of that way at least regarding main memory access.
dandelany · 4 years ago
I’ve made some fun projects with Espruino - where you can literally use setTimeout/setInterval and it will even sleep the uC until it hits the the timeout :)
petra · 4 years ago
So it simply manages both multitasking and power management?
xkcd-sucks · 4 years ago
I'm crappy at embedded development but isn't it pretty normal to handle "multitasking" with interrupts instead of using an event loop?
sophacles · 4 years ago
Embedded can be a tiny little msp430 with RAM measured in bytes, or it can be the latest and greatest intel flagship - it's a description of how the chip is used more than the chip type.

As CPUs get more powerful in general, so too do the little development boards. I can spend $10 on a board with multiple cores and megabytes of ram to drive my blinky lights and do wifi - that's enough power to run a full unix.

baremetal · 4 years ago
where? where can i get one?
qbasic_forever · 4 years ago
They're looking at this from a higher level for defining the API and programming model people will use to write event-based code. An implementation might run using interrupts and a timer to drive an event loop on a single core, or it might scale itself out to run on multiple cores where available like they mention.
gmiller123456 · 4 years ago
Not really, the processor has to be designed to support it that way to make it practical. With interrupts, execution can be interrupted at any point, and you have to be able to restore the registers to the correct state before letting the process continue from that point. It's technically doable, but with a chip that has only 128 bytes of RAM, you'll be pretty limited in how many processes you can run. Even Windows, prior to Win95, used the event loop style for multitasking.
AnonymousPlanet · 4 years ago
> Even Windows, prior to Win95, used the event loop style for multitasking.

Even? That's nearly the lowest bar regarding multitasking, compared to other OSes of the time.

petsfed · 4 years ago
That's exactly what's described.

Arduino's framework divides main() into two functions: Setup() and loop(), but they're essentially the same as whatever comes before the while loop, and the while loop itself, in a standard embedded c main function.

Then you just have interrupts setting states, and the loop responds to the change in state.

skybrian · 4 years ago
It's not quite the same as a while loop, because when you return from the loop() function, how long it takes before it gets called again isn't defined. Using arduino-pico, for example, it does some USB I/O handling (when using TinyUSB). If you write an actual while loop and don't return from the loop function often enough, you might starve the USB port implementation.

This adds enough of a delay that I moved some code to the other core on the Pico to get precise timing.

roland35 · 4 years ago
Interrupts work up to a point, but the correct way to do it is with an RTOS. Looking at the GitHub discussion it seems they are looking at mbed, FreeRTOS, or some other varieties too.
Zardoz84 · 4 years ago
And/or using a RTOS. I remember using a RTOS on a PIC16/18 family chip like ten years ago.

But the most usual thing was knowing the precise timings that takes instructions and the peripherals to allow do multiple things at same time.

eldruin · 4 years ago
In Rust there is https://embassy.dev which is an async Rust framework for embedded. It has been shipping in products for years. Can be made real-time (for some definitions of it).

There is also RTIC https://rtic.rs which is a concurrency framework for real-time systems using interrupts. IIRC the car industry is interested in it.

mongrelion · 4 years ago
Very interesting projects.

Looking at the documentation and Github pages it's not very clear if ATmel, the microcontroller family for Arduino, is supported at all or not.

Do you have a link where this is validated?

eldruin · 4 years ago
There are several microcontroller architectures within the Arduino family officially as well as compatible ones that are also called Arduino-something.

The classic (and oldest) are the AVR microcontrollers (ATmega), which are not supported by embassy nor rtic at the moment AFAIK. They are supported by Rust, though. see: https://github.com/rahix/avr-hal.

However, there are several other Arduino microcontrollers that are supported. For example the Arduino Nano RP2040, Arduino Nano 33 BLE (nRF), the Arduino MKR ones as well as the STM32duino (STM32F103, the one in the hugely popular $2 blue-pill boards) are all based on the ARM cortex-m architecture, which is supported by both embassy as well as rtic.

For espressif chips (like the esp32), support is being worked on by the manufacturer itself: https://github.com/embassy-rs/embassy/issues/745

Here an overview of microcontrollers in the (official) Arduino family: https://www.makeuseof.com/exploring-different-types-of-ardui...

fmakunbound · 4 years ago
Forths such as FlashForth https://www.flashforth.com/index.html on the Arduino support multiple tasks .. in addition to including a compiler and interactive REPL
ramary · 4 years ago
We've done quite a bit of experimentation with adding preemptive multitasking support to non-hard real time software running on MCUs at my current company.

After a lot of head banging and dead ends, I've come to the conclusion personally that the embedded community could really use an implementation of the POSIX threading APIs or some meaningful subset thereof for various platforms. They're already standardized, well understood/used, and aren't that hard to implement on an MCU.

Of course, there would probably be some semantics that wouldn't make sense to or may not be possible to implement on MCUs, and there would be work required to support different cores, but these tradeoffs seem better to me than reinventing the wheel and probably needing to make the same tradeoffs at some point down the line with a ground up new API.

For Arduino, exposing POSIX APIs wouldn't be very user-friendly. But wrapping something more user-friendly around them seems like a maintainable and extensible path for the project and community.

Tajnymag · 4 years ago
Threads are already supported by esp-idf on esp32

source: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/...

Teknoman117 · 4 years ago
Do you know how the core scheduling works? Does it migrate tasks between cores? Is there any notable core to core migration penalty?
ramary · 4 years ago
That's great, thanks for sharing!
deepspace · 4 years ago
And esp-idf uses FreeRTOS, which is pretty much the standard for this kind of thing.
qbasic_forever · 4 years ago
ramary · 4 years ago
Thanks for sharing! I hope the Arduino teams heads in this direction as well.
inamberclad · 4 years ago
RTEMS has been providing a real time, multi threaded, POSIX API on embedded systems for decades. It's got preemption, interactive shells, a libbsd port with networking, etc. It's popular in spacecraft, especially in combination with NASA's Core Flight System (cFS). I've run it on an STM32F4 and a Raspberry Pi.

BSP support is a little lacking in some cases. The raspberry pi is currently crashing on a call to fflush(). No idea why.

I really wish more people knew about it. As a matter of fact, I'll post about it here.

ramary · 4 years ago
This is great to know about, thank you for sharing!
OlaCh · 4 years ago
Energia IDE was a good example of multitasking for MCU. Here is an example: https://energia.nu/guide/foundations/programming_technique/m...
fake-name · 4 years ago
ITT: Arduino people re-implement FreeRTOS badly.

Seriously, there are SO MANY mature, well-understood RTOS implementations for MCUs out there already, they really, really should just use one.

syncurrent · 4 years ago
Synchronous programming is also a good choice IMHO to handle multiple concurrent activities in a simple way on an embedded system.

https://github.com/frameworklabs/proto_activities