Introducing multitasking to Arduino
blog.arduino.cc
blog.arduino.cc
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`.
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.
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 :-)
FreeRTOS really isn't that large of a leap forward, in fact when it's done right on your target platform it's just a few API calls as well. But it means wrapping your head around a lot of detailed concepts (stack size? semaphores?) when all you want to do is light up that string of RGB LEDs.
I come back and now AVR is old and slow and ARM is hot stuff. All it took was the tooling becoming nearly free. No more $500 ISP programmers, just USB DFU boot.
Do you have any recommended resources or additional search terms to explore to learn more about hobbyist-level embedded electronics outside of the Arduino ecosystem? FreeRTOS looks interesting but it seems to add a lot of overhead versus something simple like Arduino. Similarly, I've looked at STM32 programming before but my searches were very generic and the STM ecosystem is massive. Specifically, I was trying to figure out if I could reprogram some old drone flight controllers (equipped an STM32F103CBT6 with a bunch of useful embedded sensors, running old versions of "betaflight") for personal projects but the entrypoint to STM programming (STM32Cube?) and the setup code was considerable.
FreeRTOS on an ESP32 in the Arduino IDE is effectively free: you don't have to do anything to enable it. It's pulled in by default.
AFAIK, Arduino support for STM32 is limited to the F103 & F4xx series.
I confess that since my entry point is as a professional, I really haven't kept up with what other hobbyist-level entries are still on the market. That said, a good place to start would be with an STM32 Discovery board -- if you can find one these days! Looks like Digi-Key has exactly 1 in stock rn. It's a $19.95 board with an 'F407 and some sensors and output devices. No external tools needed: you can program it through a USB port. All the tools can be downloaded from ST Thomson or its partners for free. This is more entry-level professional than hobbyist, but there's no sharp dividing line there.
The Raspberry Pi pico is also taking off: https://www.raspberrypi.com/products/raspberry-pi-pico/ I haven't used one yet, but they seem to be amazing little devices and the community is rallying around them.
There are lots of libraries online that demonstrate how to set up registers for various peripherals. Avrgcc is an open source toolchain, and avrdude can program devices running the arduino bootloader.
Micropython and Arduino are not dead ends but hobby grade tools.
However, if you do know the hardware, if you are familiar with what's going on under the hood, then MicroPython allows you to write the majority of your system significantly faster.
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?
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.
The SDCC open source C compiler targets some of those chips now, too.
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.
edit: but it makes sense from the perspective of a education and hobbyist needs to take the simplest option that could work
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.
I have STM32 and Beaglebone too but have not used them yet.
Or google "esp-32" to find store selling a very popular one.
Or check out what's available on sparkfun or adafruit.
Within the past few weeks, they introduced a wireless version - the Pi Pico W. It's about $6 from authorized resellers, though stock is hard to find at the moment.
https://www.raspberrypi.com/documentation/microcontrollers/
https://www.raspberrypi.com/news/raspberry-pi-pico-w-your-6-...
But the most usual thing was knowing the precise timings that takes instructions and the peripherals to allow do multiple things at same time.
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.
This adds enough of a delay that I moved some code to the other core on the Pico to get precise timing.
I never ran into an issue of quasi-hidden behaviors like that, because I never pushed the thing that hard. It feels a little bit like the Arduino toolset (that is pre IDE 2.0) was designed to discourage people from pushing things that hard.
Since switching to VSCode and PlatformIO, I read the source more. (Arduino 1.x discouraged this due to not having easy ways to navigate between callers and callees.)
[1] https://www.arduino.cc/reference/en/libraries/scheduler/yiel... [2] https://github.com/PaulStoffregen/cores/blob/master/teensy4/...
Even? That's nearly the lowest bar regarding multitasking, compared to other OSes of the time.
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.
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?
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...
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.
source: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/...
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.
Seriously, there are SO MANY mature, well-understood RTOS implementations for MCUs out there already, they really, really should just use one.
I know you can have multiple boards but yeah.
It's a strange thing to say - async/await is now built in to most major programming languages - strange that Arduino would dismiss it so easily.
They are correct in that async/await does not solve multicore utilisation, but it's the most important and easiest way to implement parallelism on a single core.
> The traditional way to do this is to write non-blocking code so that the loop() function can run as fast as possible, updating state variables and calling the millis() function to ensure proper timing
Indeed, that style of programming is nothing at all like async/await in, say, Javascript.
[edit] - Actually... they have some good points, I just worry about someone thinking they can just "sprinkle threading" into their code, and failing to understand that in doing so they change the laws of physics of the code.
If I absolutely need to multitask on a Mega328 Arduino, I use the protothreads library. But really, for anything heavy, I'll turn to an ESP32 or an STM32. Use the right tool for the job!
There are already people running FreeRTOS on those.
Not sure why you would dismiss multitasking on those as "useless".
So this development is also relevant for those other boards, maybe even more so.
The truth is, that Arduino always has put in a lot of work to do things that could already be done only to make them more accessible, easier to use and thus more widely available.
If you think this is pointless you should consider this might say more about how far you have come in your own journey than about the usefulness of that new feature.
That is a minuscule part of the Arduino audience. For the rest of us, FreeRTOS works fine.