HaWe:
viel einfacher (weil man sich dann um delay etc keine Gedanken machen muss), wäre ntl preemptives (Zeitscheiben-) Multitasking.
Preemptives Multitasking verbrät allerdings bei den Taskwechseln einen erheblichen Anteil der Performance, die ein Controller oder Prozessor bietet. Machst Du zu häufige Taskwechsel, geht fast die gesamte Performance für Taskwechsel drauf und für die eigentlichen Tasks bleibt kaum Rechenzeit übrig. Und machst Du nur seltene Taskwechsel, können die einzelnen Tasks nur mit erheblicher Zeitverzögerung reagieren.
Nicht umsonst hat sich Preemptives Multitasking eigentlich nur auf Multiprozessor-Betriebssystemen wirklich durchgesetzt.
Auf Einprozessorsystemen ist preemptives Multitasking nur eine unnötige Bremse und kann von der Performance her mit einem gut gemachten kooperativen Multitasking nicht mithalten. Es gibt kein preemtives Multitasking ohne Rechenzeitverbrauch. Und je preemptiver das System sein soll, desto mehr Rechenzeit geht nur für die Aufrechterhaltung des Multitaskings drauf.
Das Problem gibt es beim kooperativen Multitasking nicht. Anbei mal ein kleines Codebeispiel für so etwas ähnliches wie es der Thread-Starter nachgefragt hat:
define TASTERPIN 5
#define LED 13
#define BLINKTIME 250
void setup() {
Serial.begin(9600);
Serial.println("Kooperatives Multitasking by 'jurs' for German Arduino Forum");
Serial.println("Verbinde Pin-5 mit GND zum Starten der Lauflicht-Demo...");
pinMode(TASTERPIN, INPUT_PULLUP);
pinMode(LED, OUTPUT);
}
void blinken(unsigned long time)
// Funktion: Ständiges Blinken im Rhythmus von BLINKTIME
{
static unsigned long lastSwitchTime=0;
unsigned long timeSinceSwitching=time-lastSwitchTime;
if (timeSinceSwitching>=BLINKTIME)
{
digitalWrite(LED,!digitalRead(LED)); // change LED state
lastSwitchTime+=BLINKTIME;
}
}
boolean laufTaskRunning=false; // Task läuft oder läuft nicht
unsigned long laufTaskStart=0; // Startzeit des Tasks
byte virtualLEDS=0; // 8 Bits 1/0 simulieren 8 LEDs ein/aus
void eingabe(unsigned long time)
// Funktion: Taster abfragen, bei gedrücktem Taster den LaufTask starten
{
static unsigned long lastRunTime=0;
static byte lastButtonState=0;
if (time-lastRunTime>5)
{
byte buttonState=!digitalRead(TASTERPIN);
if (buttonState && !lastButtonState)
{
laufTaskRunning=true;
laufTaskStart=time;
}
}
}
void verarbeitung(unsigned long time)
// laufender Task schaltet 8 virtuelle LEDs nacheinander an und wieder aus
{
unsigned long laufTaskRunningSince=time-laufTaskStart;
virtualLEDS=0;
for (int i=0;i<8;i++)
{
if (laufTaskRunningSince>i*1000 && laufTaskRunningSince<=(i+8)*1000)
bitSet(virtualLEDS,i);
}
}
void ausgabe(unsigned long time)
{
static byte lastLED=0;
if (virtualLEDS!=lastLED) // Änderungen der LEDs anzeigen
{
for (int i=0;i<8;i++)
Serial.print(bitRead(virtualLEDS,i));
Serial.println();
lastLED=virtualLEDS;
}
if (time-laufTaskStart>16000) // Task beenden, wenn erledigt
{
laufTaskRunning=false;
Serial.println("Task beendet...");
}
}
void loop()
{
unsigned long now=millis();
blinken(now);
eingabe(now);
if (laufTaskRunning) verarbeitung(now);
if (laufTaskRunning) ausgabe(now);
}
Das Programm läßt mit der loop als Round-Robin-Scheduler
- die Board-LED ständig im vorgegebenen Rhythmus blinken
- fragt einen Taster an Pin-5 ab, der einen zusätzlichen Task starten kann
- wenn der zusätzliche Task läuft, werden seine Daten verarbeitet und ausgegeben
Die Aufgabe des zusätzlichen Tasks ist eine LED-Simulation aus 8 LEDs, bei der im Sekundentakt eine weitere LED angeschaltet wird bis alle LEDs leuchten, danach werden die LEDs in derselben Reihenfolge im Sekundentakt abgeschaltet bis alle aus sind. Danach beendet der Task sich selbst und stellt sich auf "laufTaskRunning=false". Solange bis der eingabe-Task den zusätzlichen Task wieder startet.
Race-Konditions, dass Tasks etwa um dieselbe Hardware konkurrieren, lassen sich im kooperativen Multitasking auch sehr leicht auflösen. Was soll zum Beispiel das Programm machen, wenn der Taster ein zweites mal gedrückt wird, während der Zusatztask noch abgearbeitet wird? In diesem Demoprogramm wird dann einfach der Zusatztask neu gestartet und beginnt bei einem Tastendruck während der laufenden Sequenz neu mit der Wiedergabe der Sequenz. Man könnte aber genau so gut weitere Tastendrücke verwerfen und ignorieren, solange der Zusatztask läuft. Oder man könnte weitere Tastendrücken während einer laufenden Sequenz in einem FIFO-Puffer zwischenpuffern, so dass die Sequenz nach der vollständigen Abarbeitung gleich nochmal abgearbeit wird.
Auf Einprozessor-Systemen läßt sich die Hardware jedenfalls durch kooperatives Multitasking viel besser bis zum letzten Takt ausnutzen als durch preemptives Multitasking, bei dem viele wertvolle Rechentakte für das Sichern und Wiederherstellen der Ausführungsumgebung bei der Taskumschaltung draufgehen. Keine Taskumschaltung = maximale Rechenleistung auf Einprozessorsystemen.
Erschwerend kommt beim preemptiven Multitasking noch dazu: Das verbrät nicht nur Rechenzeit, sondern auch zusätzlichen RAM-Speicher. Für jeden Task muss ja bei der Taskumgebung der Prozessorzustand mit allen seinen Registern gesichert werden, um genau diesen Zustand später wieder rückspeichern zu können, wenn der Task wieder an der Reihe ist, ausgeführt zu werden. Und das ist auf 8-Bit Atmegas echt hart, von beispielsweise 2 KB RAM für jeden Thread z.B. 128 Bytes zu verlieren, die für die Sicherung des Controllerzustands beim Umschalten der Tasks benötigt werden.
Im übrigen gibt es ja stets noch das preemtive Multitasking des kleinen Mannes: Interrupts!
Die Atmega-Controller kennen Timer-Interrupts und Hardware-Interrupts, und was neben der normalen kooperativen Programmausführung keinerlei Aufschub duldet und "in Echtzeit" abgearbeitet werden soll, das läßt man dann eben in Timer-Interrupts oder Hardware-Interrupts laufen.