Hi.
Das Thema (kooperatives) Multitasking und endliche Automaten wird immer wieder gerne genommen. Das ist auch nicht wirklich ein großes Wunder, denn viele, ja fast alle, Arduino Programme lassen sich auf diese "Software Design Pattern" runter brechen.
SPS Programmierer sind da besser dran, die kennen fast nichts anderes.
Die Stichworte mal als Sammlung:
Endlicher Automat, State Machine, Multitasking,
Coroutinen, Ablaufsteuerung, Schrittkette,
BlinkWithoutDelay, Ersetzen von delay(),
Unterbrechen von Schleifen
Als Wunderwaffe gegen alle diese Sorgen möchte ich euch meine TaskMacros
vorstellen. Siehe, die Arduino Library im Anhang.
OK, ok, auch Wunderwaffen können nicht alles, bzw. haben ihre Einschränkungen...
Der Werkzeugkasten:
Als erstes mal die Umgrenzung einer Task:
taskBegin() Damit wird ein Task Block eingeleitet
taskEnd() Das Ende eines Taskblockes
Taskkontrolle:
taskSwitch() Gibt die Rechenzeit an andere Tasks ab. Es wird im nächsten Durchlauf an dieser Stelle weiter gearbeitet.
taskPause(interval) Diese Task pausiert, bis interval abgelaufen.
taskWaitFor(condition) Diese Task pausiert, bis condition true wird.
Genug der Theorie!
Hier mal ein Beispiel für BlinkMitDelay:
void blink()
{
static byte count = 0;
while(1) // Endlosschleife, totale Blockade
{
count++;
digitalWrite(13,count & 1);
delay(500); // blockiert für 500 ms
}
}
void setup()
{
pinMode(13, OUTPUT);
}
void loop
{
blink();
}
Das funktioniert!
Die Nachteile dieses Vorgehens sind natürlich offensichtlich.
Sowohl die while Schleife, als auch das Delay, blockieren.
Möchte man noch zusätzlichen Kram in loop() unter bringen, wird man daran versagen.
Der Umbau auf BlinkOhneDelay:
#include <TaskMacro.h>
void blink()
{
static byte count = 0;
taskBegin();
while(1) // blockiert dank der TaskPause nicht
{
count++;
digitalWrite(13,count & 1);
taskPause(500); // gibt Rechenzeit ab
}
taskEnd();
}
void setup()
{
pinMode(13, OUTPUT);
}
void loop
{
blink();
}
Noch ein Beispiel....
Blink so modifiziert, dass man eine kurze Leuchtdauer (100ms) und eine lange Dunkelphase (700ms) bekommt
void blink()
{
taskBegin();
while(1) // blockiert dank der TaskPause nicht
{
// Schritt 1
digitalWrite(13,1); // LED ein
taskPause(100); // gibt Rechenzeit ab
// Schritt 2
digitalWrite(13,0); // LED aus
taskPause(700); // gibt Rechenzeit ab
// gehe zu Schritt 1
}
taskEnd();
}
Eine einfache Form der Schrittkette.
In den Beispielen finden sich noch ein paar Varianten. Z.B. wie man For Schleifen unterbrechbar macht. Und das Warten auf Flags.
Noch was erwähnenswertes? .....
Klar!
1:
Man sollte bedenken, dass lokale Variablen immer nur einen Durchlauf überleben. Man wird in solchen Tasks also eher statische, oder globale, Variablen verwenden.
2:
Dinge, welche bei jedem Aufruf erledigt werden wollen, haben sich vor taskBegin(); einzufinden.
3:
Switch/Case sind nicht in den Tasks verwendbar, da ich diese Kontrollstruktur darin für die Steuerung verwende.
Ansonsten:
Viel Spaß mit diesen Macros....!
Verbesserungen?
Vorschläge?
PS:
An dieser Stelle möchte ich mich insbesondere bei "Adam Dunkel" und "Donald E. Knuth" bedanken.
Edit:
Neue Version hochgeladen
(Die alte Version wurde 15 mal runter geladen)
Schritte können jetzt benannt und angesprungen werden. Das hilft deutlich beim Schrittkettenbau. Zu finden im Schrittketten Beispiel.
Dieses Beispiel besteht aus 3 Tasks:
- Taste entprellen
- Blinken in 2 Geschwindigkeiten, per Taste umschaltbar
- Zeigen der Loopdurchläufe pro Sekunde (ca 62000 auf einem Uno)
Sicherlich könnte man dieses Beispiel noch etwas mehr straffen/optimieren....
TaskMacro.zip (3.6 KB)