[Projekt] Multitasking

Sehe ich vermutlich richtig: taskBegin und taskEnd allein nutzt nix:

void blink() {
   taskBegin();
   while(1)   
   {
      digitalWrite(13,!digitalRead(13));
      delay(100);
   }    
   Serial.println("Hier hin oder gar aus blink wieder raus kommt er nie");  
   taskEnd(); 
}

Wenn man es richtig verwendet, funktioniert es wohl. - geht nur innerhalb einer ``` void ``` Funktion - taskBegin und taskEnd dürfen je Funktion nur einmal in richtiger Reihenfolge als Paar auftreten - dazwischen dürfen taskPause taskWaitFor taskSwitch stehen (auf jeweils eigenen Zeilen in der gleichen Funktion) - diese führen gegebenenfalls ein return aus. Es geht also nicht nach taskEnd(); weiter

Wir haben mal vor Urzeiten c-Makros geschrieben, die helfen sollten, etwas zu programmieren das wie FORTRAN aussah ( #define END } und sowas )

Ich mags nicht - weil es was anderes macht als wie es aussieht - weil Fehlermeldungen nur verständlich sind, wenn man die Makrodefinition kennt und versteht

Aber hübsch ist es schon, jedenfalls für Leute die auch gut ohne diese Makros auskämen ;)

Sehe ich vermutlich richtig: taskBegin und taskEnd allein nutzt nix:

Richtig! Sonst bräuchte jede Task ihren eigenen Stack. Preemptives Multitasking, wäre das dann.

Wenn man es richtig verwendet, funktioniert es wohl.

Du hast die Bedingungen korrekt erkannt. Ich werde diese als Anregung für die Erweiterung der Doku nehmen.

  • geht nur innerhalb einer void Funktion

Da habe ich lange dran geknackst... Aber keinen eleganten Weg gefunden.

Aber hübsch ist es schon, jedenfalls für Leute die auch gut ohne diese Makros auskämen

Danke, das geht runter, wie Honig!

Es geht also nicht nach taskEnd(); weiter

Doch schon... Unter Umständen... z.B. wenn sich in der Task keine Endlosschleife befindet.

Erst mal: ich habe das Ampel-Beispiel jetzt ausprobiert, und es funktioniert tatsächlich. Bis hierhin sieht die Bibliothek wirklich brauchbar aus :-)

Mich hätte jetzt aber brennend interessiert, was der Präprozessor draus macht - wie komme ich da dran? Vielleicht macht der ja noch mehr unentdeckte Schweinereien?

Es sieht tatsächlich so aus, als ob der Compiler ohne Schleife den case LINE wegoptimiert. Igitt igitt, aber gerade noch erklärbar.

Mein Vorschlag für taskPause funktioniert durchaus, da dürften beide Varianten den gleichen Code erzeugen.

Bei taskWaitFor hingegen tritt das Problem auf, unter welchen Umständen der nachfolgende Code ausgeführt wird. Folgt darauf kein weiterer taskSwitch, wird die Bedingung bei jedem Durchlauf erneut geprüft und der nachfolgende Code so lange ausgeführt, als die Bedingung wahr ist. Wird die Bedingung absichtlich oder versehentlich false, bleibt die Task hängen. Deshalb würde ich sicherheitshalber ein weiteres taskSwitch einfügen, wenn die Bedingung das erste mal true war, damit sie nicht weiterhin geprüft wird. Leider kriege ich das per Makro nicht hin, da case LINE ja schon vergeben ist :-(

Ähnlich würde ich auch bei taskEnd ein taskSwitch einfügen, damit das Ende des switch (ab dem letzten taskSwitch) nicht endlos wiederholt wird. Das dürfte dann auch das Problem mit taskWaitFor beheben. Oder wait=0 setzen, damit es bei taskBegin weitergeht - dann kann man sogar die while(1) Schleife zwischen taskBegin und taskEnd einsparen.

Damit reduzieren sich meine Änderungsvorschläge hierauf:

#define taskEnd() wait = 0; }

Was die void Funktion betrifft, die könnte man mit taskBegin kombinieren, ähnlich ISR:

TASK() //incl. taskBegin
...
ENDTASK() //incl. taskEnd

Geschweifte Klammern je nach Geschmack hinzufügen.

Mich hätte jetzt aber brennend interessiert, was der Präprozessor draus macht - wie komme ich da dran?

Kein Problem... ;-) Build Meldungen aktivieren. Dann zeigts dir das Build Verzeichnis. Im Build Verzeichnis findet sich ein Ordner namens preproc. Da drin findest du es aufgedröselt.

Man könnte ja auch noch in beginTask ein neues delay() Makro hinzufügen bzw. aktivieren, und in endTask wieder löschen. Aber bei dem dafür notwendigen Aufwand erscheint mir taskPause dann doch eleganter...

combie: Build Meldungen aktivieren. Im Build Verzeichnis findet sich ein Ordner preproc Da findest du es aufgedröselt.

Danke für dieses Puzzle, aber ich hab's noch geschafft ;-)

Seltsamerweise funktioniert es jetzt auch ohne die Schleife in beginTask. Sehr undurchsichtig :-(

Hallo combie,

schön für die Mühen, aber ist das nicht ein umgedrehtes Intervall? Sieht alles nach negativer Logik aus. Und sollten Multitasking Aufgaben nicht eher vom OS verwaltet und ausgeführt werden statt vom Userprogramm?

Kannst aber gern weitermachen. Bin gespannt was noch bei rauskommt. :)

Je länger ich mich mit den Makros befasse, desto mehr bin ich davon begeistert :-)

Vor allem Anfänger dürften davon profitieren, die von den komplizierten Lösungen wie BlinkWithoutDelay abgeschreckt sind. Aber auch Fortgeschrittene können umfangreicheren Code damit sehr übersichtlich hinschreiben, besser als mit verschachtelten oder scheinbar unmotiviert aufeinanderfolgenden umfangreichen Abfragen. Die Effizienz könnte sogar höher sein als mit der sonst üblichen Technik, zumindest habe ich in dieser Richtung keine ernsthaften Bedenken.

Mich persönlich stört die while-Schleife zwischen taskBegin und taskEnd, und die läßt sich einfach vermeiden, wenn man in taskEnd mark=0 setzt. Eine Frage ist dabei, ob diese Schleife ein besseres Gefühl dafür vermittelt, wie der Code tatsächlich abläuft. Läßt man die Schleife weg, weil die Task nur einmal ausgeführt werden soll, dann hört sie tatsächlich garnicht auf, sondern wiederholt den letzten Teil (ab dem letzten case label) endlos. Dieses Verhalten empfinde ich jedenfalls wenig intuitiv.

Weitere Verbesserungsmöglichkeiten sind mir schon eingefallen, allerdings noch keine Lösungen dazu. Ich sollte die wohl einzeln zur Debatte stellen.

Das Timing mit taskPause ist etwas unpräzise, wie man am Ampel Beispiel sehen kann. Ersetzt man dort in piepser() den letzten taskSwitch durch TaskPause(500), dann müßte der letzte Piep eigentlich nach dem Umschalten der Ampel (alle 2000ms) erfolgen - tut er aber meist nicht! Für zeitgenaues Loggen oder die Erzeugung von Impulsen ist das eher ungeeignet.

Vielleicht könnten dafür zwei Makros verwendet werden, mit taskPause oder taskDelay für unexaktes Warten als direkter Ersatz für delay(), und taskResume o.ä. das relativ zum letzten timeStamp wartet? Die Implementierung wäre relativ simpel

#define taskResume(interval) timestamp+=interval; while (millis()-timestamp < (interval)) taskSwitch();

Nur der Name gefällt mir noch nicht.

Nett das du es mal wieder versuchst mit Tasks.

Ich hab es damals mal versucht, es ist aber leider untergegangen:

http://forum.arduino.cc/index.php?topic=165552.0

Das Projekt habe ich seit dem nicht weiter verfolgt. Die Erkenntnisse sind dann mit in die LCDMenuLib geflossen.

Jomelo: Nett das du es mal wieder versuchst mit Tasks.

Ich hab es damals mal versucht, es ist aber leider untergegangen:

http://forum.arduino.cc/index.php?topic=165552.0

Das Projekt habe ich seit dem nicht weiter verfolgt. Die Erkenntnisse sind dann mit in die LCDMenuLib geflossen.

Hi..

Hmm.. das hatte ich bisher nicht gefunden.... Das war wohl vor meiner Zeit hier ;-)

Dein TaskDingen verfolgt ja einen ganz anderen Ansatz. Den werde ich mir auch mal genau anschauen...

Mir drehte es sich eher darum, einen 1:1 DropIn für delay() und nebenläufige Fäden trotz lang laufender Schleifen zu bekommen.

Lange habe ich darüber gegrübelt, sowas wie einen TaskKontrollBlock einzuführen, aber dann verworfen. Das hätte mehr Möglichkeiten geschaffen, wäre aber auch um Größenordnungen komplexer geworden. Auch für den Anwender, und genau das wollte ich nicht.

Das Timing mit taskPause ist etwas unpräzise,

Ja ... Dass das Ganze kein Echtzeitbetriebsystem wird, dank des kooperativen Charakters auch nie werden können wird, darf einen nicht verwundern. Will man es exakter, wird man die Hardwaretimer bzw. ein RTOS bemühen müssen.

Im Ampel-Test war noch ein Denkfehler drin. Bei 5 Pieps im Abstand von 500ms kommt der letzte ja bereits nach 2000ms, also etwa zeitgleich mit der Weiterschaltung der Ampel, und nicht erst nach 2500ms. Tatsächlich kommt der letzte Piep sauber nach dem letzten Ampel-Zustand, wenn das Intervall auf 501ms hochgesetzt wird. So schlimm ist es mit dem Zeitversatz also doch nicht :-)

Bei meinen angedachten Erweiterungen geht es nicht um hochkomplizierte Funktionen aus Echtzeit-Systemen, sondern was sich mit vergleichbar einfachen Mitteln an Standard-Aufgaben sonst noch erledigen lassen kann.

Anfänger dürften davon profitieren, die von den komplizierten Lösungen wie BlinkWithoutDelay abgeschreckt sind

Das Problem bei Makros ist, dass der Compiler was anderes sieht als was der Anfänger geschrieben hat, und eventuelle Fehlermeldungen dadurch noch viel unverständlicher werden als sie für Anfänger so schon sind.

Wem BlinkWithoutDelay zu komplex ist, dem ist nicht zu helfen der wird auch mit den Multitasking Makros Probleme haben :confused:

Wenn man weiss, dass diese Makros dazu dienen, heimlich aus der umgebenden Funktion zu verschwinden um beim nächsten Mal genau dort wieder weiter zu machen, und man damit gedanklich zurecht kommt, schön. Damit kann man sicher übersichtlich AblaufSteuerungen definieren.

Wenn man weiss, dass diese Makros dazu dienen, heimlich aus der umgebenden Funktion zu verschwinden um beim nächsten Mal genau dort wieder weiter zu machen,

Das hast du schön formuliert. .... "heimlich" .... "verschwinden" .... ;-)

Was gefällt euch an Jomelos simpleThread nicht?

Hallo,

ich hatte ja schon einmal versucht zu fragen, bekam aber keine Antwort. Im Grunde ist das ja die negative Logik von deinem Intervall Projekt. Womit ich nicht klar komme vom Verständnis her ist folgendes. Man möchte doch das die loop so schnell wie möglich durchlaufen wird. Da widerspricht es doch das man hier feste Zeiten für andere Dinge frei gibt. Statt so schnell wie möglich sein Ding fertig zu machen und dann kann das nächste dran kommen.

ich hatte ja schon einmal versucht zu fragen, bekam aber keine Antwort.

Sorry, ich hatte deine Frage nicht verstanden. Auch jetzt noch nicht wirklich.

Aber, ich versuchs mal....

Man möchte doch das die loop so schnell wie möglich durchlaufen wird.

Das stimmt! Findet bei den ganzen Beispielen ja auch mehr als 100.000 mal pro Sekunde statt.

Da widerspricht es doch das man hier feste Zeiten für andere Dinge frei gibt.

Nöö... Denn die "freie" Zeit geht ja an loop(), im Grunde so, wie es das Interval Macro auch macht.

Ich halte es also nicht für inverse Logik, sondern eher für die gleiche.

Hmmm. War es das, was du wissen wolltest?

Nachtrag: Ah.... Ich glaube, jetzt... kommts mir...

Zu Anfang sagte ich:

taskSwitch() Gibt die Rechenzeit an andere Tasks ab. Es wird im nächsten Durchlauf an dieser Stelle weiter gearbeitet.

Der erste Teil ist wohl missverständlich. Die Rechenzeit wird nicht direkt an andere Tasks abgegeben, sondern der Programmfluss wird per Return an die aufrufende Funktion übergeben, und loop() kann dann andere Tasks aufrufen, bzw. zusätzliche Aufgaben erledigen. Das gilt natürlich auch für die anderen Taskkontrol Dinger..

skorpi08: Was gefällt euch an Jomelos simpleThread nicht?

Das kannte ich vorher noch nicht.

Auch sind meine taskMacros vermutlich schon einiges älter. Hatte sie nur noch nicht aufs Arduino Umfeld portiert.

Hallo,

genau jetzt gehts in die richtige Richtung. Ich hatte noch keine Zeit mir das näher anzuschauen, habe nur deine Texte hier dazu mehrfach gelesen. Also macht der Task sein Ding und wenn er fertig ist gehts in der loop normal weiter. Und wenn der Task selbst aktuell nichts zu tun hat blockiert er auch nicht sondern gibt sich sofort wieder frei zurück an loop. Okay, dass verstehe ich. Wie dein Intervall nur eben anders verpackt. Sehr schön. :) Werde das sicherlich bald selbst testen.

Was mache ich falsch :confused:

kurze Einleitung:

ich möchte einen Servo ansteuern und nebenbei andere dinge erledigen doch ich bekomme diese Fehlermeldung und habe keinen schimmer was falsch ist

Bitte um richtigstellung !!

#include 
#include 
Servo servo1;

void setup() {
  
  Serial.begin(38400);     
  delay(500);
  servo1.attach(9);

}

void loop() {

 bewegung();

}

void bewegung() {

 taskBegin();

  int position;


  for(position = 20; position < 180; position += 2)   
  {
    servo1.write(position);  
    taskPause(20);               
  }

  
  for(position = 180; position >= 20; position -= 2)
  {                                
    servo1.write(position);  
    taskPause(20);     
   
  }
  
}

Fehlermeldung:

 In function 'void bewegung()':

sketch_aug04a:40: error: expected '}' at end of input

 };

  ^

exit status 1
expected '}' at end of input