extension request: preemptive multithreading lib for M0, M3, M4 (like std::thread, ftrias/TeensyThreads, ESP32)
hi, coincidentally I found the lib ftrias/TeensyThreads https://github.com/ftrias/TeensyThreads, unfortunately working just for Teensys. IIUC, it's not trivial to port that preemptive multithreading lib to standard Arduinos... :( So what would be realy awesome is if there was such a std::thread implementation available for M0, M3, M4 Arduino/Adafruit cores!
There is a project called FreeRTOS that already implements this across almost every uC and CPU that exists There are Arduino ports of FreeRTOS for AVR 8-bit boards, STM32 and Atmel SAMD21 boards
There is a project called FreeRTOS that already implements this across almost every uC and CPU that exists There are Arduino ports of FreeRTOS for AVR 8-bit boards, STM32 and Atmel SAMD21 boards
no, I meant an extension for the standard Arduino API/ IDE, just to have threads additionally using std::thread :
as simple to use as this:
// std::thread for ESP32, Arduino IDE
#include <Arduino.h>
#include <thread>
#include <chrono>
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
void counter_loop() {
thread_local uint32_t counter = 0;
while(true) {
// do something
counter++;
}
}
void blinker_loop() {
thread_local uint32_t counter = 0;
while(true) {
//do something
counter++;
}
}
std::thread *thread_1;
std::thread *thread_2;
void setup() {
Serial.begin(115200);
thread_1 = new std::thread(counter_loop);
thread_2 = new std::thread(blinker_loop);
}
uint32_t main_loop_counter = 0;
void loop() {
main_loop_counter++;
Serial.print("main loop: ");
Serial.println(main_loop_counter);
delay(10000);
}
Coincidentally I meanwhile also found thei std::thread implementation to ESP32: https://github.com/espressif/esp-idf/blob/master/examples/system/cpp_pthread/main/cpp_pthread.cpp
update: for ESP32 (Adafruit Feather ESP32) std::thread works fine now: https://github.com/espressif/arduino-esp32/issues/2814#issuecomment-495525603
but the code doen't compile for Arduinos, neither for Arduino Due nor Arduino M0/Zero nor for (Adafruit) SAMD51/M4 (e.g., many error messages are about max() and min() ):
// std::thread for ESP32, Arduino IDE
#include <Arduino.h>
#include <thread>
#include <chrono>
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
const auto one_sec = std::chrono::seconds
{
1
};
void counter_loop() {
thread_local uint32_t counter = 0;
while(true) {
Serial.print("counter_loop: ");
Serial.println(counter);
std::this_thread::sleep_for(one_sec);
counter++;
}
}
void blinker_loop() {
thread_local uint32_t counter = 0;
while(true) {
digitalWrite(LED_BUILTIN, HIGH);
Serial.print("blinker_loop (HIGH) counter: ");
Serial.println(counter);
std::this_thread::sleep_for(one_sec);
digitalWrite(LED_BUILTIN, LOW);
Serial.print("blinker_loop (LOW) counter: ");
Serial.println(counter);
std::this_thread::sleep_for(one_sec);
counter++;
}
}
std::thread *thread_1;
std::thread *thread_2;
void setup() {
Serial.begin(115200);
//debug
delay(2000);
thread_1 = new std::thread(counter_loop);
thread_2 = new std::thread(blinker_loop);
}
uint32_t main_loop_counter = 0;
void loop() {
main_loop_counter++;
Serial.print("main loop: ");
Serial.println(main_loop_counter);
delay(10000);
}
PS,
as tested, std::string by esp32 implementation appears to work as preemptive:
// std::thread for ESP32, Arduino IDE
#include <Arduino.h>
#include <thread>
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
const auto one_sec = std::chrono::seconds
{
1
};
int fibbonacci(int n) {
if(n == 0){
return 0;
} else if(n == 1) {
return 1;
} else {
return (fibbonacci(n-1) + fibbonacci(n-2));
}
}
void blinker_loop() {
thread_local uint32_t counter = 0;
while(true) {
digitalWrite(LED_BUILTIN, HIGH);
Serial.print("blinker_loop (HIGH) counter: ");
Serial.println(counter);
std::this_thread::sleep_for(one_sec);
digitalWrite(LED_BUILTIN, LOW);
Serial.print("blinker_loop (LOW) counter: ");
Serial.println(counter);
std::this_thread::sleep_for(one_sec);
counter++;
Serial.print("blinker_loop counter: "); Serial.println(counter);
}
}
void fibonacci_loop() {
thread_local uint32_t counter = 0, i=0;
while(true) {
for(i=30; i<41; i++) { // limits: test, debug
Serial.print( (String)"Fibbonacci of " + (String)i + "=");
Serial.println(fibbonacci(i));
}
counter++;
Serial.print("fibonacci_loop counter: "); Serial.println(counter);
}
}
std::thread *thread_1;
std::thread *thread_2;
void setup() {
Serial.begin(115200);
//debug
delay(2000);
thread_1 = new std::thread(blinker_loop);
thread_2 = new std::thread(fibonacci_loop);
}
uint32_t main_loop_counter = 0;
void loop() {
main_loop_counter++;
Serial.print("main loop: ");
Serial.println(main_loop_counter);
delay(10000);
}
output:
blinker_loop (HIGH) counter: 0 Fibbonacci of 30=832040 Fibbonacci of 31=1346269 Fibbonacci of 32=2178309 Fibbonacci of 33=blinker_loop (LOW) counter: 0 3524578 Fibbonacci of 34=blinker_loop counter: 1 blinker_loop (HIGH) counter: 1 5702887 Fibbonacci of 35=blinker_loop (LOW) counter: 1 blinker_loop counter: 2 blinker_loop (HIGH) counter: 2 9227465 Fibbonacci of 36=blinker_loop (LOW) counter: 2 blinker_loop counter: 3 blinker_loop (HIGH) counter: 3 14930352 Fibbonacci of 37=blinker_loop (LOW) counter: 3 blinker_loop counter: 4 blinker_loop (HIGH) counter: 4 blinker_loop (LOW) counter: 4 blinker_loop counter: 5 blinker_loop (HIGH) counter: 5 24157817 Fibbonacci of 38=blinker_loop (LOW) counter: 5 blinker_loop counter: 6 blinker_loop (HIGH) counter: 6 blinker_loop (LOW) counter: 6 blinker_loop counter: 7 blinker_loop (HIGH) counter: 7 blinker_loop (LOW) counter: 7 blinker_loop counter: 8 blinker_loop (HIGH) counter: 8 blinker_loop (LOW) counter: 8 39088169 Fibbonacci of 39=blinker_loop counter: 9 blinker_loop (HIGH) counter: 9 blinker_loop (LOW) counter: 9 blinker_loop counter: 10 blinker_loop (HIGH) counter: 10 blinker_loop (LOW) counter: 10 blinker_loop counter: 11 blinker_loop (HIGH) counter: 11 blinker_loop (LOW) counter: 11 blinker_loop counter: 12 blinker_loop (HIGH) counter: 12 blinker_loop (LOW) counter: 12 blinker_loop counter: 13 blinker_loop (HIGH) counter: 13 blinker_loop (LOW) counter: 13 blinker_loop counter: 14 blinker_loop (HIGH) counter: 14 blinker_loop (LOW) counter: 14 63245986 Fibbonacci of 40=blinker_loop counter: 15 blinker_loop (HIGH) counter: 15 blinker_loop (LOW) counter: 15 blinker_loop counter: 16 blinker_loop (HIGH) counter: 16 blinker_loop (LOW) counter: 16 blinker_loop counter: 17 blinker_loop (HIGH) counter: 17 blinker_loop (LOW) counter: 17 blinker_loop counter: 18 blinker_loop (HIGH) counter: 18 blinker_loop (LOW) counter: 18 blinker_loop counter: 19 blinker_loop (HIGH) counter: 19 blinker_loop (LOW) counter: 19 blinker_loop counter: 20 blinker_loop (HIGH) counter: 20 blinker_loop (LOW) counter: 20 blinker_loop counter: 21 blinker_loop (HIGH) counter: 21 blinker_loop (LOW) counter: 21 blinker_loop counter: 22 blinker_loop (HIGH) counter: 22 blinker_loop (LOW) counter: 22 blinker_loop counter: 23 blinker_loop (HIGH) counter: 23 blinker_loop (LOW) counter: 23 102334155 fibonacci_loop counter: 1 Fibbonacci of 30=832040 Fibbonacci of 31=1346269 Fibbonacci of 32=blinker_loop counter: 24
Finally and thankfully now also Arduino.cc has figured out which terrific advantages preemptive multithreading offers for more powerful ARM Cortex MCUs! https://store.arduino.cc/nano-33-ble-with-headers https://store.arduino.cc/nano-33-ble-sense
As many of you may know, Mbed is a fully preemptive RTOS (real-time operating system), meaning you can run multiple “programs” (more specifically, threads) at the same time, much like what happens in your notebook or smartphone
update: it turned out that the preemptive ESP32 std: thread implementation (based on freeRTOS) is stable and not hooking up the entire program any more when blocking or long-time calculating or continuously running loops (while forever) are started by thread prio=0 (same prio as RTOS watchdog). In that case the scheduler switches the time slices in any case, even without delays (as expected) Threads started by prio >0 (1...5) need to provide yield() or thread_sleepfor() not to block different lower-prio threads (also as expected). (tested, works like a charm)
That is how it would be wishful also for Arduino M0 (Zero), M3 (Due) and perhaps 3rd party M4 (e.g., by Adafruit).