Unbeabsichtigter Aufruf einer virtuellen Methode.

Doc_Arduino:
Irre. :slight_smile: Danke für den Tipp mit dem neuen avr-gcc.
Denn diese Methode von hier http://blog.zakkemble.net/avr-gcc-builds/ funktioniert nicht. Egal ob 8.2 oder 7.3
Das Paket funktioniert nur unter Atmel Studio.

Mittlerweile habe ich die Version 9.1 von genau der Seite bei mir am laufen.
Falls du (oder auch wer anders) interessiert bist, könnte ich mal eine Installationsanleitung basteln.
Denn die Anleitung auf der Seite funktioniert (bei mir) wirklich nicht.

Hallo,

die Anleitung auf der Seite für die Arduino IDE hatte ich auch schon probiert. Im Thread steht dann was das es nur mit der 7er funktionieren soll. Aber auch das wollte bei mir nicht laufen. Irgendwann hast du dann den Tipp mit dem Update in Arduino IDE gegeben. Das läuft ohne Probleme.

Mein Atmel Studio 7 läuft schon eine Weile mit gcc 9.1 von der Seite. Ich muss nur immer eine alte Dateiversion der avr-size.exe im ersten bin Ordner mitschleppen. Sonst zeigt er nach Kompilierung immer falsche Codegrößen an bzw. gar keine.

Falls es dir keine Umstände macht würde ich deine Anleitung ausprobieren, dann wären bei mir beide gleichgezogen. Wenn getestet kannste das bei ihm ins Forum schreiben. :slight_smile:

Ja, dann mal eine Kurzanleitung.

Auf der Seite gibt es drei Pakete.
x86, x64 und für Linux

x86, x64 laufen auf meinem Win 10
x64 ist deutlich schneller, sonst keine erkennbaren Unterschiede.

Ich gehe mal davon aus, dass du immer noch das Arduino AVR Paket 1.6.209 installiert hast.

Bei mir fahre ich die Arduino portable Version.
Du auch?
Egal.

Das 9.1 Paket kannst du in irgendeinen beliebigen Ordner stopfen.
Ich habe E:\Programme\arduino\portable\avr-gcc\avr-gcc-9.1.0-x64-mingw
gewählt/erzeugt.
Einziger Rat: Keine Leerzeichen im Path

In dem 1.6.209 Installationsordner findet sich eine platforms.txt Datei
Bei mir: E:\Programme\arduino\portable\packages\arduino\hardware\avr\1.6.209\platforms.txt
Daneben, eine platforms.local.txt anlegen

Mit folgendem Inhalt:

compiler.path={runtime.ide.path}/portable/avr-gcc/avr-gcc-9.1.0-x64-mingw/bin/

compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++2a  -fno-exceptions -fpermissive -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto
compiler.cpp.extra_flags=-fno-sized-deallocation  -fconcepts

Wenn du einen ganz anderen Ordner wählst, kannst du auch den absoluten Pfad angeben.
Hier eine ältere Variante von mir

#compiler.path=E:\Programme\arduino\portable\packages\arduino\tools\avr-gcc\avr-gcc-9.1.0-x64-mingw/bin/

So fluppt es bei mir.


Am Rande:
https://www.gnu.org/software/gcc/projects/cxx-status.html

Hallo,

läuft :grin:

Meine IDE ist installiert. Habs entpackt unter (32Bit)

C:\Users\xyz\Documents\Arduino\avr-gcc-9.1.0-x86-mingw

Mein Pfad lautet

C:\Users\xyz\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.209

Der Eintrag entsprechend

compiler.path=C:\Users\xyz\Documents\Arduino\avr-gcc-9.1.0-x86-mingw/bin/

compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++2a  -fno-exceptions -fpermissive -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto
compiler.cpp.extra_flags=-fno-sized-deallocation  -fconcepts

Besten Dank kann ich da nur sagen. Damit kann man jede aktuelle Version verwenden. :slight_smile:
Klappt das auch ohne das "1.6.209 Paket" ? Wobei das nicht wirklich stört.

Klappt das auch ohne das "1.6.209 Paket" ?

Nicht getestet...

Vielleicht stiften wir ja einen stillen Mitleser an?
?

Laut dem Kommentar müsste das funktionieren:

# Default "compiler.path" is correct, change only if you want to override the initial value

compiler.path={runtime.tools.avr-gcc.path}/bin/

Das kann man schon fast als Empfehlung auffassen.

Hallo,

wenn ich mal mehr Zeit haben sollte ... könnte ich das ausprobieren, glaube aber selbst nicht dran.

Habe den SchedulerLoopCount mit der 32 und 64Bit Version vom avr-gcc-9.1 laufen lassen. Bekomme wie erwartet gleiche Werte.
Die 100.000er Marke wurde jedoch unterschritten. Vielleicht sind irgendwo noch verschiedene Einstellungen vorhanden.
Oder das IDE Paket 1.6.209 mit seinem gcc-7.3er war/ist optimiert. Naja was solls. :wink:

// 32Bit
Scheduler loops: 87368 per 1000ms
Scheduler loops: 87278 per 1000ms
Scheduler loops: 87369 per 1000ms

// 64Bit
Scheduler loops: 87278 per 1000ms
Scheduler loops: 87369 per 1000ms
Scheduler loops: 87368 per 1000ms

Welche Unterschiede hast du in den xx Bit Versionen?

Hallo combie,

ich habe mal Dein:
QuadroDynamicDefekt mal ausprobiert.
Dabei habe ich nicht - wie vorgesehen - die Library benutzt, sondern die Dateien in die lokale Dir kopiert.
Dabei ist mir aufgefallen, dass ich in Task.h #include "Listnode.h" #include "ListNode.h" umbenennen musste.
Das Ergebnis ist ok:

Start
BlinkTask begin, pin: 10
BlinkTask begin, pin: 11
BlinkTask begin, pin: 12
Init 13
BlinkTask begin, pin: 13

Oder habe ich etwas falsch verstanden?

Dabei ist mir aufgefallen, dass ich in Task.h #include "Listnode.h" #include "ListNode.h" umbenennen musste.

Oh...
Windows meldet mir solche Fehler leider nicht.

Wird in der nächsten Version geändert!

Danke.

So, die “QuadroBlinkDynamicDefekt” funktioniert. Es lassen sich jederzeit - auch später im Programm - dynamisch neue Tasks starten.
Anbei als Zip. (keine Library - alles in Sketch-Folder).

Ich benutze kooperatives Multitasking schon seit vielen Jahren - lange vor dem Erscheinen von Arduino.
Hier meine “Werke”:

http:/HelmutWeber.de
Daher darf ich bei aller Bescheidenheit wohl sagen, über einige Erfahrung zu verfügen - ohne zu beanspruchen, die Weisheit mit Löffeln gefuttert zu haben.

Es juckt mich, einige Bemerkungen loszuwerden, aber die könnten als Kritik verstanden werden.
Das möchte ich vermeiden!

Deshalb positiv:
Combie hat die beste C++ Version erstellt, die ich bisher gesehen habe - deutlich besser, als meine eigenen Versuche dazu.

Glückwunsch - weiter so! :wink:

QuadroBlinkDynamicDefekt.zip (4.92 KB)

Sorry, firstD, firstE müsssen Global statt local :wink:

Combie hat die beste C++ Version erstellt, die ich bisher gesehen habe - deutlich besser, als meine eigenen Versuche dazu.

Glückwunsch - weiter so! :wink:

Danke für die Blumen und den Zuspruch!

Allerdings, ist es doch auch ein hervorragender Dünger für meinen Hochmut und Arroganz. :o

Kritik, Bemerkungen?
Men los!

Ich fange mal an:
Beim überfliegen deiner Seiten, ist mir ein "sind" aufgefallen, welches, so vermute ich, eigentlich gerne ein "Sinn" werden möchte.


Eigentlich höre ich mir gerne alles an....
Etwas Probleme habe ich da mit allzu absoluten Aussagen, und (ausuferndem) Featurerismus.

Offensichtlich hast du dich schon "etwas" mit dem Thema Multitasking beschäftigt.
Das Denken in Nebenläufigkeiten scheint mir, bei dir, gut verankert zu sein.
Und der Einbau deiner "TaskHandles", in meinen Code, hat ja auch gut geklappt.

Wie auch immer...
Ich betrachte diese Multitasking Variante eher als Fingerübung in Sachen C++, als ein bezugsfertiges System.
Da ist also noch deutlich Entwicklungspotential.

Also:
Bitte raus damit.
Sage, was es zu sagen gibt.

Hallo,

ich habe es heute probiert ohne das Board Paket 1.6.209.
Alle Zusatzpfade in den Preference gelöscht und IDE deinstalliert.
Auch den Ordner C:\xyz\Worker\AppData\Local\Arduino15 habe ich anschließend gelöscht.
Nackte aktuelle IDE installiert.
Wenn man die platform.local.txt dann in den Pfad
C:\Program Files (x86)\Arduino\hardware\arduino\avr\ ablegt verwendet die IDE den dort angegebenen gcc 9.1.
Ein Problem weniger. :slight_smile:

Ok, Du hast es gewollt :wink:

  1. Es gibt keinen TaskStatus. (Task.h: enum state {READY, DELAYED, …} class Task { state State; … }

Das führt dazu, dass auch pausierende Tasks aufgerufen werden, die - da pausierend - sofort mit taskSwitch:

#define taskSwitch()                               \
    do {                                                     \
        lable = &&LC_CONCAT(taskLable, __LINE__);    \
        return;                                                                    \
       LC_CONCAT(taskLable, __LINE__):;                    \
    } while(0)


#define taskPause(Task_interval)                                  \
    timeStamp = millis();                                                 \
    while((millis() - timeStamp) < (Task_interval)) taskSwitch()

zurückzukehren. Welch eine Verschwendung. Bei länger wartenden Tasks kann sich das millionenfach wiederholen, so dass 20 wartende Task einen Task, der jetzt dran ist, erheblich verzögern können.
Ergebnis: 200000 TaskSwitches /s aber dabei nur echte 2 TaskSwitches für READY Tasks.
Ein Task, der pausiert, darf nicht aufgerufen werden! Punkt.

Bei RTOS ( die, die mit Listen arbeiten) wird ein pausierender Task auf eine andere Liste verschoben.

  1. Die Verwaltung mit der Liste ist fragwürdig. Es wird nur zugefügt. Das geht besser (und schneller) mit einem
    Array[MAXTASKS];
    Damit ist auch eindeutig, ob ein neuer Task Platz hat - da statisch reserviert. Mit “new” muss in jedem Fall (vorab) kontrolliert werden, ob der Speicherplatz dafür noch reicht.

  2. Und wann und wie lösche ich einen Task? Jederzeit? Oder nur bei einem bestimmten Zustand seiner selbst?

  3. Es fehlen Zustandüberwachungen wie: (serial output):
    Starte Task nur, wenn Resource frei ist.
    Oder: Bleibe blockiert, bis ein anderer Task (oder Interrupt) dich wieder aktiviert.

  4. Es fehlen Taskprioritäten, um dafür zu sorgen, dass der wichtigere (READY-) Task zuerst aufgerufen wird.

  5. Die Tasks (A,B,C) werden “rückwärts” initialisiert und auch (bei gleichen Delays) abgearbeitet.
    Das kann bei Tasks, deren Wirkung - gerade beim Start - aufeinander aufbaut, zu Problemen führen:

Sch-Begin: BlinkTask begin, pin: 5 Name: C <<<
Sch-Begin: BlinkTask begin, pin: 12 Name: B <<<
Sch-Begin: BlinkTask begin, pin: 2 Name: A <<<
construct task: Add
Constr 14
BlinkTask begin, pin: 14 Name: F
construct task: Add
Constr 14
BlinkTask begin, pin: 14 Name: Hallo1
Loop Task: Hallo1 Pin:14 <<< Reihenfolge rückwärts
Loop Task: F Pin:14 <<< …
Loop Task: C Pin:5
Loop Task: B Pin:12
Loop Task: A Pin:2

Folge von: An Liste (add(Node)) wird vorne angefügt - nicht hinten!

Mit anderen Worten: als “Fingerübung” sehr schön - in der Praxis so (noch!) nicht zu gebrauchen.
Also: Tasks haben Zustände (state), die vom Scheduler berücksichtigt werden:

For i < TaskAnzahl

    if (DELAYED) 
      Test, ob von DELAYED auf READY

    if (READY) 
      call Task

End For

Eine solche Schleife übergeht z.Bsp. auch BLOCKED Tasks ohne Zeitverlust.

Warum taskPause in millis() statt micros(), wenn 200000 TaskSwitches ausgeführt werden (alle 5 us) ?

Jeder Task muss von class Task ableitet werden. Warum ? Es reicht, wenn class Task die Funktionspointer für
begin, loop
enthält. Wobei loop eigentlich reicht.
Dann können mit TaskInit(“name”, BeginPt, LoopPt); Funktionen vom Scheduler aufgerufen werden - wesentlich übersichtlicher. Alle Tasks sind von class Task - egal, was sie letztendlich tun!

Tasks können als einfache Funktionen (mit taskBegin, taskEnd) deklariert sein - wesentlich übersichtlicher und einfacher, als für jeden neuen Task eine neue Klasse zu definieren/abzuleiten.
Sinn von OOP ist es schließlich, Dinge zu abstrahieren und die Benutzung des Gesamtproduktes zu vereinfachen, statt sie unnötig zu komplizieren.

Da aber TaskLoop sowieso vom Scheduler aufgerufen wird, ist Task.h eigentlich auch überflüssig:
Es reicht struct (von mir aus class) Task:

class task {
    char     *name;
    char     ID;
    state    State;
    void     *Param;
    void     (*FunctionLoop)();  // Von InitTask besetzt
...
}

Damit kann - wenn gewünscht - auch eine Parameter-Struktur an einen aufgerufenen Task weitergereicht werden. Die auch von mehreren Tasks benutzt werden kann.

Bitte nicht böse sein. Ich finde Deinen Ansatz nach wie vor Klasse! Aber etwas lösen von den “Fingerübungen” und mehr die Praxis im Hinterkopf haben könnte Sinn machen.

Hier eine simple Aufgabe:
BlinkTask1 : Loop: schalte LED 1000 us HIGH, dann 1000 µs LOW
BlinkTask2 : Wenn Blink2 auf LOW geschaltet hat - und nur dann:
Loop: schalte LED 100 us High, dann 100 us LOW.

Lasse 5 weitere Tasks laufen und kontrolliere das Ergebnis mit einem LA:
Wie genau werden die Zeiten eingehalten? Wie lange braucht Task2, um auf die LOW Flanke von
Task1 zu reagieren?

Eine gute Intertask-Kommunikation ist sehr wesentlich für ein solches System. Reine Delays sind die Ausnahme!

Ich hoffe sehr, ich habe eher Deinen Ehrgeiz angestachelt, als Dich entmutigt.
Bleibe am Ball - Du bist auf einem guten Weg - der noch weit ist. Durchhalten :wink:

Mit besten Wünschen und Grüßen

Wenn Du eine entsprechende Version machst:

Ich teste sie mit Vergnügen!

heweb:
Wenn Du eine entsprechende Version machst:

Das kannst du knicken!

Mit diesen Anforderungen, habe ich irgendwie gerechnet.

Irgend eine Art von abgesetzten Taskkontrollblöcken, werde ich nicht einführen.
Prioritäten, no
Tasks in einem Array fester Größe verwalten, no.

Ob eine Task abgelaufen ist, wen interessiert es...
Und wenn doch, dann kann sie ja ihren Status bereit stellen.
Oder sich jederzeit aus dem Speicher entfernen.

Was interessieren mich µs? ,
ms ist in meiner kleinen Arduino Welt meistens völlig ausreichend.
Zudem: Der Umbau, oder das hinzufügen, wäre trivial.

Du möchtest bei jedem Taskwechsel:

  1. Prioritäten abhandeln
  2. Taskzustand abhandeln
    Das kostet RAM, Flash und Zeit.
    Diese verplemperten Ressourcen könnte man effektiver für eine weitere Task verwenden.

Wer Spaß an der Featureritis hat , darf den Kram gerne einbauen.
Alternativ könnte der Wünscher dein System verwenden, oder FreeRTOS
Eine ganze Reihe von Alternativen, es gibt.


Was anderes, ist die Sache mit den Mutex/Semaphore.
Ein solches Konzept ist manchmal nötig.
Allerdings auch bei jedem anderen System, welches Nebenläufigkeiten nutzt/implementiert.
Egal, ob da nun Multitasking auf dem Etikett steht, oder nicht.

Das kann/darf gerne jeder selber entwickeln.
Nutzbare Implementierungen gibt es auch schon wie Sand am Meer.
Aus dem Pool bediene ich mich sowieso schon.

Die Mutex/Semaphore Idee muss also nicht Teil "meines" Systems sein.
Und ja, es wäre ein sinnvolles/nützliches Feature.


  1. Die Verwaltung mit der Liste ist fragwürdig. Es wird nur zugefügt.

Es wird auch entfernt.

Damit ist auch eindeutig, ob ein neuer Task Platz hat - da statisch reserviert. Mit "new" muss in jedem Fall (vorab) kontrolliert werden, ob der Speicherplatz dafür noch reicht.

Instanzen von Task können unterschiedlich groß sein.

Damit kann - wenn gewünscht - auch eine Parameter-Struktur an einen aufgerufenen Task weitergereicht werden. Die auch von mehreren Tasks benutzt werden kann.

Das geht bei mir per Konstruktor, Template oder Setter Injektion.
Einen vierten Weg halte ich für flüssiger als Wasser. Für Überflüssig.

Tasks können als einfache Funktionen (mit taskBegin, taskEnd) deklariert sein - wesentlich übersichtlicher.

War es nicht der OOP Ansatz, welche die Begeisterung bei dir ausgelöst hat?

Ein Task, der pausiert, darf nicht aufgerufen werden! Punkt.

Wie sagte ich zu absoluten Aussagen?

Etwas Probleme habe ich da mit allzu absoluten Aussagen ....

Übrigens, wenn ich das richtig gesehen habe verwendest du switch/case für dein Konzept.
Das wird mit zunehmender Anzahl Fälle schnell sehr ineffektiv.
Es zerfällt in speicherintensive lookup tables oder zeitintensive Vergleichs/Entscheidungs Kaskaden.
Auch verbietet es die Verwendung von switch/case in Tasks.

Welch eine Verschwendung.

Nur wenn man das switch/case dafür verwendet.
Mit dieser simplen Anwendung von computed goto ist man die Verschwendung los.


Ich hoffe, dass du mir jetzt nicht allzu böse bist.

Schade, dass meine Implementierung nicht deinen Ansprüchen genüge tut.
Aber sie wäre nicht so herzerfrischend naiv, wenn sie nicht so wäre, wie sie ist.


Wo es Potential gibt:
Die Wire und SPI Implementierungen blockieren, solange eine Transaktion läuft.
Da bleibt richtig Zeit liegen....

Serial ist da etwas komfortabler, es sagt immerhin, wie viele Zeichen/Byte im Buffer frei sind. So kann man Wartezeit abgeben.

Den Punkt 6 habe ich eben erst gesehen…

  1. Die Tasks (A,B,C) werden “rückwärts” initialisiert und auch (bei gleichen Delays) abgearbeitet.
    Das kann bei Tasks, deren Wirkung - gerade beim Start - aufeinander aufbaut, zu Problemen führen:

Sch-Begin: BlinkTask begin, pin: 5 Name: C <<<
Sch-Begin: BlinkTask begin, pin: 12 Name: B <<<
Sch-Begin: BlinkTask begin, pin: 2 Name: A <<<
construct task: Add
Constr 14
BlinkTask begin, pin: 14 Name: F
construct task: Add
Constr 14
BlinkTask begin, pin: 14 Name: Hallo1
Loop Task: Hallo1 Pin:14 <<< Reihenfolge rückwärts
Loop Task: F Pin:14 <<< …
Loop Task: C Pin:5
Loop Task: B Pin:12
Loop Task: A Pin:2

Folge von: An Liste (add(Node)) wird vorne angefügt - nicht hinten!

Deine Argumentation zieht nur, wenn man heimliche, von der Reihenfolge abhängige, Abhängigkeiten einbaut.
Aber wer das tut, bringt sich selber in des Teufels Küche.
Sowieso.

Aus meiner Sicht, eine irrationale Geschichte.

Ob an den Anfang, oder ans Ende der Liste eingefügt wird, ist prinzipiell völlig egal.
Die Funktionsfähigkeit eines Multitasking Systems darf nicht von der Abarbeitungsreihenfolge der Tasks abhängig sein.
Es müssen alle Tasks an die Reihe kommen, diese Garantie reicht.

Das Einfügen an den Anfang ist einfacher und deutlich schneller, bei einer einfach verketteten Liste.
Hinten einhängen und von Vorn durchlaufen, erfordert eine doppelt verkettete Liste.
Und damit quasi den doppelten Verarbeitungsaufwand bei Veränderungen und den doppelten Speicher Aufwand.

Das nur, um eine potentielle Schlampigkeit/Irrtum des Anwendungsprogrammierers auszubügeln?

"Allerdings, ist es doch auch ein hervorragender Dünger für meinen Hochmut und Arroganz. :o "

Es war zu befürchten, dass Du so reagierst. :wink:

“Damit ist auch eindeutig, ob ein neuer Task Platz hat - da statisch reserviert. Mit “new” muss in jedem Fall (vorab) kontrolliert werden, ob der Speicherplatz dafür noch reicht.”

Re. “Instanzen von Task können unterschiedlich groß sein.”

Die von TCBs nicht !

"Du möchtest bei jedem Taskwechsel:

  1. Prioritäten abhandeln
  2. Taskzustand abhandeln
    Das kostet RAM, Flash und Zeit.
    Diese verplemperten Ressourcen könnte man effektiver für eine weitere Task verwenden. "

Oder für sinnlose TaskSwitches ?

Erweitere mal dein Macro taskPause

#define taskPause(Task_interval)                                      \
    timeStamp = millis();                                                      \
    while((millis() - timeStamp) < (Task_interval)) {             \
        Serial.println("Ich verschwende Zeit");                      \
        taskSwitch();                                                             \
    }

und lass es laufen und wundere Dich.

Um überhaupt noch etwas zu sehen nimm
Serial.print("."); \

Loop Task: A Pin:2
…7086
…Loop Task: E Pin:14
.Loop Task: D Pin:13

(Hier hat jemand die unerträglich vielen Punkte gelöscht, die in taskPause geschriebben wurden,
Also selber ausprobieren!)

Ich bleibe dabei: diese taskPause() - ein zentraler Punkt - ist wirklich reine Zeitverschwendung.

“Was interessieren mich µs? ,
ms ist in meiner kleinen Arduino Welt meistens völlig ausreichend.”

Die Zahl der erreichten TaskSwitches/s scheinen Dich doch sehr zu interessieren, denn von denen berichtest
Du gerne :wink:
Ein Standard Sensor: HC_SR04 Ultraschallsensor … mit ms ?

Alle preemptiven Versionen sind langsamer und brauchen viel mehr Speicher. Genau da liegen ja die Vorteile des
kooperativen Multitasking.
Und selbst in der noch viel kleineren AtTiny Welt sind us oft interessant.

Auch wenn Du es anscheinend nicht merkst: Du benutzt selbst Duff’s Device. (TaskBlock, taskSwitch, …) mit
entsprechenden Auswirkungen:
Z. Bsp.: keine localen Variablen im Task möglich - nur static.

Im loop von BlinkTask:

virtual void loop() 
 {
 static int mycnt;
   TaskBlock
   {
     mycnt++;
     Serial.print("Loop Task: "); Serial.print(name); Serial.print(" Pin:"); Serial.print(pin); Serial.print(" "); 
     Serial.println(mycnt);

Mit static geht, ohne nicht!

oder probiere:
virtual void loop() 
 {
   TaskBlock
   { 
     int i=0;
     do {
       i++;
       taskSwitch();
     } while (i<5);

     // wird NIE ausgeführt !!!        
     >>>>> Serial.print("Loop Task: "); Serial.print(name); Serial.print(" Pin:"); Serial.println(pin);

Ich habe mal taskSwitch/taskPause erweitert:

// Task Kontroll Worte, diese werden Taskwechsel einleiten
#define taskSwitch()                                                          \
  do {                                                                                  \
        SwitchCount++; \
        lable = &&LC_CONCAT(taskLable, __LINE__);        \
        return;                                      \
        LC_CONCAT(taskLable, __LINE__):;                        \
  } while(0)
  
#define taskPause(Task_interval)                                      \
  timeStamp = millis();                                                        \
  while((millis() - timeStamp) < (Task_interval)) {               \
      PauseCount++;                                                           \
      digitalWrite(5, HIGH);                                                  \
      taskSwitch();                                                               \
      digitalWrite(5, LOW);                                                  \
  }

Ein spezieller BlinkTask2 gibt alle 2 Sekunden aus:

TaskBlock
    { 
      Serial.print("Loop Task: "); Serial.print(name); Serial.print(" Pin:"); Serial.println(pin);
      Serial.print("Micros/Pause/Switch:  "); 
      Serial.print(micros()); Serial.print(" / "); Serial.print(PauseCount); 
      Serial.print(" / "); Serial.println(SwitchCount);
      SwitchCount=0; PauseCount=0;

Output:
Loop Task: Hallo1 Pin:14
Micros/Pause/Switch: 535208045 / 440083 / 440083
Loop Task: F Pin:14
Loop Task: C Pin:5
Loop Task: B Pin:12
Loop Task: A Pin:2
Loop Task: E Pin:14
Loop Task: D Pin:13
31450
Loop Task: E Pin:14
Loop Task: D Pin:13
31203
Loop Task: Hallo1 Pin:14 (TaskSwitch)
Micros/Pause/Switch: 537208039 / 438550 / 438550
(TaskPause)
Loop Task: Hallo1 kommt sehr sauber alle 2 Sekunden.

Aber:

taskPause() und in Folge taskSwitch() werden je 220000 mal pro Sekunde aufgerufen !

Das ist das, was ich mit Verschwendung meinte!

Auch mit dem LA kann man sehen:
Das System ist mehr als 50% der Zeit mit
** taskPause() / taskSwitch()**
beschäftigt !
"Die Zeit kann man besser für weitere Tasks nutzen" :wink:

Meine Implementierung erfüllt diese Forderungen und ist mindestens so schnell, wie Deine :wink:
Bei vielen Tasks mit delay mit Sicherheit schneller. Ich stelle mich gerne einem Vergleich.
Es geht also! Wo bleibt Dein Ehrgeiz?

Wie gehst Du im Job mit einem Vorgesetzten um, der Forderungen wie ich stellt?

Schade - Dein grundsätzlicher Ansatz hätte viel Potential gehabt. Aber so bleibt es wohl doch nur Hilfsmittel für Hochmut und Arroganz - und sicher wärst Du stinksauer, wenn ich sagen würde, was ich davon halte.
So jedenfalls ist Dein Produkt nicht brauchbar.

Auf den Ansatz kannst Du stolz sein - auf das sture Verfolgen der Irrwege und Verkaufen von Anfängerfehlern als Innovation eher nicht.

Schade, denn ich hätte Dir wirklich das Potential zugetraut, ein beachtliches Produkt zu schaffen! Aber wenn Du so schnell aufgibst mit “will ich nicht, weil ich es nicht kann”, dann wird die Säule des Hochmuts wohl ins Wanken geraten.

Schluck einfach mal trocken runter und schaue - ohne beleidigt zu sein - meine Ratschläge noch einmal in Ruhe an. Meine Beispielaufgabe ist doch wirklich nicht schwer. (Die beiden HIGH/LOW Tasks)
Programmieren ist kein Selbstzweck - es sollte auch als Übung etwas brauchbares leisten.

Und solltest Du je einmal professioneller Programmierer werden wollen - gewöhne Dich daran, mit Anforderungen umzugehen :wink:
Ich habe Dir ein paar Jahrzehnte Praxis voraus. Das heißt nicht zwangsläufig, dass ich ein besserer Programmierer bin. Aber ich sehe sehr schnell, wo es bei einer Lösung klemmt. Im Zweifel mit einem LA - ohne das Programm auch nur zu sehen.
Was hindert Dich, in class Task ein flag zu setzen: InDelay und timestamp dort zu deklarieren und in
class Scheduler::loop:

while(temp)
{
     temp->loop();  // InDelay neu setzen
     temp = (Task *)list.getNext(temp);

     // Dieser Test sorgt dafür, dass ein Task, der pausiert, gar nicht erst
     // Aufgerufen wird. Eine enorme Zeitersparnis!  

     if (temp->InDelay) {
         if ((millis() - timeStamp) < (Task_interval)) {    // Dieser Task pausiert noch
             temp = (Task *)list.getNext(temp);              //  Der nächste bitte ...
         }
     }
     else temp->InDelay = false;
}

// Nicht vollständig, aber ich denke die Richtung ist erkennbar.

Damit würden nur Tasks aufgerufen, die nicht pausieren - READY Tasks also viel schneller drankommen !

(Nachtrag)
Schaue dir mal das Disassembly von switch/case und computed goto an. Keiner zu finden?
Doch: Switch/case arbeitet genauso mit den vom Compiler LINE generierten Labels.
Ein Fehler darin führt zu Fehlverhalten (der case wird nicht gefunden) - aber nicht zum Absturz.
Und dieses Fehlverhalten kann entdeckt und abgefangen werden. (Mache ich auch nicht, aber es geht)
Computed goto verreckt sofort gnadenlos ! Still ruht der See.

Sturheit ist manchmal eine Tugend - und manchmal einfach nur dumm.
Die Kunst ist, die beiden Fälle zu erkennen :wink:

Schaffst Du es, mir trotz meiner klaren Worte nicht böse zu sein?

Die Reihenfolge der Tasks kann am Anfang (ja, nur da!) wichtig sein. Z. Bsp. zur Initialisierung des Systems mit einem Task "SysInit", der weitere Tasks, die geblockt starten, freigibt.
Oder auch:
So etwas benötigt man z. Bsp., wenn ein WDT kontrolliert einen Systemfehler erkennt und SysInit später als Task Aufrufen möchte - der wiederum das System hochfährt.
Es ist auch wichtig, wenn sich 2 Tasks Signale senden und so gegenseitig synchronisieren, aber nur, wenn sie in passender Synchronisation starten:
Task_1 sendet Ultraschallpuls und startet Task2.
Task2 misst die Zeit des Echos.
Wer keine Taskkommunikation benutzt und die Tasks "anonym" frei und losgelöst von dieser Welt und ohne eine eigentliche Funktion "vor sich hinblinken läßt", dem kann die Start-Reihenfolge natürlich egal sein :slight_smile:
Mit der Meinung: "Die Startreihenfolge ist unwichtig" wirst du wohl nicht viele überzeugen.
Da tropft es nämlich aus der Kaffeemaschine, bevor der Startknopf gedrückt wurde.

Ich denke, wenn Du ausser LED-Geblinke auch mal richtige Beispiele programmierst, wird dir so mancher Kronleuchter aufgehen.
Übrigens: Mit absoluten Aussagen habe ich nicht so Probleme, wie Du. Aber mit absolutem Unsinn schon.
Denn Programieren bedeuted Logik - und das Programm MACHT absolute Aussagen.
Also: mal ran an die Kaffemaschine:
Ist Einschaltknopf gedrückt?
Ja, ist genug Wasser da? Nein, blinke uns stoppe.
loop: Ja, heize Wasser auf
Wasser heiß genug? Nein, gehe zu loop
...
Jeder Task ist von dem anderen abhängig.
Wasserbehälter wird unvorhergesehen entnommen. Also alles stoppen und blinken.

Aber ich habe den Eindruck, Du bist schon so arrogant, dass Du lernresistent geworden bist !
Schade.

Denn Hochmut kommt vor dem Fall - immer :fearful:

Wer nicht in der Lage ist- auch nicht mit einem Serial.print - festzustellen, welche Taskvariation nun gerade blinkt
Wer im Delay die millis() bis zum Abwinken pollt und damit den Prozessor massiv belastet
Wer Tasks baut, deren Status (READY, BLOCKED, DELAYED) nicht erkennbar ist
Wer Sicherheitsabfragen (new: hat es geklappt?) für überflüssig hält
Wer wirklich glaubt, die Reihenfolge beim Start von Tasks sei egal
Wer sich an das Publikum mit einem Problem wendet, weil die REIHENFOLGE bei der Initialisierung nicht stimmte

der sollte sich sagen lassen: Das Üben mit Tools macht Sinn. Die Demonstration seiner Fertigkeit im Umgang damit auch. Aber es nützt nichts, wenn es nicht irgendwann auch einmal verwertbar eingesetzt wird.
Auch wenn Du bei jedem Jahrmarkt einen Nagel immer mit drei Schlägen versenkst - ein guter Zimmermann bist Du deshalb noch nicht.

Ich habe Dein defektes Programm (QuadroBlinkDynamicDefekt) repariert.
Dabei hätte ich es belassen sollen.
Der Rest war wohl Perlen ...