Home splash

Einfaches Multitasking mit Arduino

Im Rahmen des Arduino Day 2014 im GarageLab, Düsseldorf habe ich in einer kleinen Präsentation die Möglichkeiten zur parallelen Verarbeitung mit Arduino vorgestellt. Für alle Teilnehmer und Interessentieren möchte ich eine Lösung für dieses typische Problem vorstellen.

Wie kann man mehrere Dinge gleichzeitig mit dem Arduino steuern?

Die Aufgabe: Eine LED-Lampe soll blinken / faden und gleichzeitig soll ein Servo langsam von links nach rechts bewegen. Wird ein Knopf gedrückt, soll das Servo schneller laufen. Das Programm der LED soll aber unverändert bleiben.

Zuerst einmal brauchen wir die passende Beispiel-Schaltung:
Arduino Tutoral Parallel Processing Sketch

Konventioneller Weg

Wie würde man die Komponenten einzeln ansteuern? Die Arduino-Samples liefern eine ideale Vorlage. Zuerst der Code, um die LED langsam heller und wieder dunkler werden zu lassen:


const int LED_PIN = 13;
void setup() {
pinMode(LED_PIN, OUTPUT);
}

void loop() {
// lets fade the led a little
for(int i=0;i<255;i++) {
analogWrite(LED_PIN, i);
delay(10);
}
for(int i=255;i>0;i—) {
analogWrite(LED_PIN, i);
delay(30);
}
}


Der Code für das Servo ist ähnlich einfach:

#include <Servo.h>
const int SERVO_PIN = 6;
Servo myservo;
// the setup routine runs once when you press reset:
void setup() {
// attaches the servo on pin 9 to the servo object
myservo.attach(SERVO_PIN);
}

void loop() {
// goes from 0 to 180 degrees with 1 degree
for(int pos = 0; pos < 180; pos++)
{
// tell to go to position in variable ‘pos’
myservo.write(pos);
// waits 15ms for it to reach the position
delay(15);
}
// and back
for(int pos = 180; pos>0; pos—)
{
myservo.write(pos);
delay(15);
}
}


Wir erweitern nun noch schnell den Servo-Code um die Erkennung eines Schalters. Wird dieser gedrückt, soll das Servo langsamer laufen:

#include <Servo.h>
const int SERVO_PIN = 6;
const int BUTTON_PIN = 8;
Servo myservo;

void setup() {
pinMode(BUTTON_PIN, INPUT);
myservo.attach(SERVO_PIN);
}

void loop() {
for(int pos = 0; pos < 180; pos+=getIncrement())
{
myservo.write(pos);
delay(100);
}
for(int pos = 180; pos>0; pos-=getIncrement())
{
myservo.write(pos);
delay(100);
}
}

int getIncrement() {
if(digitalRead(BUTTON_PIN)) {
// move slower
return 1;
} else {
// move faster
return 20;
}
}


Soweit kein Hexenwerk.

Kooperation und Multitasking

Nur wie bekommen wir beide Code-Teile zusammen?
Das Problem ist der Befehl delay(…). Solange die LED darauf wartet, die nächste Helligkeitsstufe zu setzen, können wir nichts tun! Eine Alternative wäre nun, dieses Delay der LED zu nutzen, um das Servo zu steuern. Komplizierte if-then Zeilen wären die Folge. Das macht es nicht nur schwierig, sondern auch fürchterlich unübersichtlich. Die Loop-Methode wird lang und die Logik undurchschaubar. Gar nicht auszudenken, wenn wir 10 Komponenten gleichzeitig steuern wollen.

Ein neuer Ansatz muss her!
Früher haben wir gesagt: Die Loop-Schleife steuert den gesamten Ablauf und die einzelnen Schritte stehen dort. Zeitliche Themen werden über delay() gelöst.
Jetzt drehen wir den Spieß um und sagen: Die Loop-Schleife ist dumm und fragt nur noch Komponenten, den nächsten Schritt zu machen

Ähh.. wie?! Nochmal:

  1. Wir teilen unser Problem in einzelnen Komponenten. Für unser Beispiel also LED und SRV
  2. Jede Komponente bekommt die Aufgabe, bei Aufruf den nächsten Schritt durchzuführen. Was ist das ist, entscheidet die Komponente
  3. Zu welchem Zeitpunkt der Schritt ausgeführt werden soll, entscheidet die Komponente ebenfalls selber
  4. Wir bauen die Loop-Schleife um und sagen einfach jeder Komponente: macheDeinenNächstenSchritt() bzw. doStep(…)
  5. Eine einzige Bedingung müssen alle Komponenten einhalten: Der Funktions-Aufruf muss schnellstmöglich zurückkehren


#include <Servo.h>
const int SERVO_PIN = 6;
const int BUTTON_PIN = 8;
const int LED_PIN = 13;
Servo SRV_servo;
int SRV_pos;
boolean SRV_moveFoward;
long SRV_nextWakeUp;

int LED_value;
boolean LED_moveFoward;
long LED_nextWakeUp;

void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
// init the compontent’s states
SRV_servo.attach(SERVO_PIN);
SRV_pos = 0;
SRV_moveFoward = true;
SRV_nextWakeUp = millis();

LED_value = 0;
LED_moveFoward = true;
LED_nextWakeUp = millis();
}

void loop() {
long currentTime = millis();
long nextWakeUpSRV = SRV_doStep(currentTime);
long nextWakeUpLED = LED_doStep(currentTime);
long nextMinWakeUp = (nextWakeUpSRV < nextWakeUpLED) ? nextWakeUpSRV : nextWakeUpLED;
delay(nextMinWakeUp – currentTime);
}

/* ** SERVO ** */
long SRV_doStep(long currentMillis) {
if(currentMillis>SRV_nextWakeUp) {
if(SRV_moveFoward) {
SRV_pos += SRV_getIncrement();
if(SRV_pos>180) {
SRV_moveFoward = false;
SRV_pos = 180;
}
} else {
SRV_pos -= SRV_getIncrement();
if(SRV_pos<0) {
SRV_moveFoward = true;
SRV_pos = 0;
}
}
SRV_servo.write(SRV_pos);
SRV_nextWakeUp = currentMillis + 100;
}
return SRV_nextWakeUp;
}

int SRV_getIncrement() {
if(digitalRead(BUTTON_PIN)) {
return 1;
} else {
return 5;
}
}

/* ** LED ** */
long LED_doStep(long currentMillis) {
if(currentMillis > LED_nextWakeUp) {
if(LED_moveFoward) {
LED_value+=3;
if(LED_value>=200) {
LED_moveFoward = false;
}
} else {
LED_value-=3;
if(LED_value<=0) {
LED_moveFoward = true;
}
}
analogWrite(LED_PIN, LED_value);
LED_nextWakeUp = currentMillis + 1;
}
return LED_nextWakeUp;
}

Wichtig dabei ist, dass jede Komponente im Aufruf zuerst fragt, ob überhaupt der nächste Schritt ausgeführt werden soll. Wenn nein, kehrt die Funktion sofort zur loop-Schleife zurück. Somit kann die Loop-Schleife sehr häufig durchlaufen werden.

Ein weiterer Vorteil besteht in der Modularisierung der Lösung. Komponenten können in einzelne Dateien ausgelagert werden und die Übersichtlichkeit steigt. Gleichzeitig vereinfachen wir die setup() und loop() Funktion. Leider gibt es in Processing unter Arduino keine interfaces. Hiermit könnten wir ein vollständig generisches Modell bauen und jede Komponente müsste nur dieses Interfaces implementieren. So müssen wir uns mit globalen Präfixen und manuellen Eingriffen behelfen. Da aber in Arduino Projekten in der Regel nur ein Entwickler beteiligt ist, sollte dies aber kein Problem sein.

Folgende Vor- und Nachteile bietet die Lösung:

  • Jede Komponente muss ihren eigenen Zustand speichern. Also muss beispielsweise ein Servo sich merken, wo es gerade steht
  • Jede Komponente entscheidet selber, wann die Ausführung des nächsten Schritts erfolgen soll
  • Die Prüfung, ob überhaupt ein neuer Schritt gesetzt werden soll, sollte am Anfang stehen und möglichst schnell erfolgen. Je schneller, desto eher erhält die Loop-Funktion die Möglichkeit andere Komponenten aufzurufen. Deswegen sollte eine Komponente nach der Berechnung und Ausführung eines Schritts die Uhrzeit für die nächste Ausführung berechnen und speichern
  • Systeme, die wochenlang laufen sollen, muss aber stets berücksichtigt werden, dass die interne Arduino-Uhr umfällt. Dies kann in der Komponenten-Definition durch eine eigene Funktion wie z.B. onClockFlip() gelöst werden. In der Loop-Schleife reicht dann eine Erkennung des Ereignisses und Aufruf der Funktion für jede Komponente
  • Für hochgenaue und besonders kurze Intervalle (z.B. beim Auslesen eines Signals) ist der Ansatz allerdings ungeeignet. Hier sind Interrupts Mittel der Wahl

Scheduling mit Libraries

Es gibt auf dem Arduino Playground bereits genügend fertige Lösungen zum Thema Scheduling, doch die meisten der dort gelisteten Libraries sind ihrer Anwendung sehr komplex und benötigen zusätzlich auch Grundkenntnisse über Funktions-Pointer oder C++.

Für zeitlich einfache Aufgaben lohnt sich aber ein Blick in die Arduino Scheduler Library for the Duo

Ausblick

Die Ideen und damit Projekte wachsen. Und damit nicht nur die Anforderungen an die richtige Elektronik, sondern auch an die zugehörige Software. Der Spendensauger im GarageLab ist ein schönes Beispiel dafür, wie man mit dem obigen Beispiel sehr viele Komponenten parallel und übersichtlich steuern kann.

Holger Prang

Bonner Str. 216
41468 Neuss
+49-172-2458951

Xing Profil Facebook page

Neuigkeiten

Einfaches Multitasking mit Arduino

Wie kann mit einem Arduino mehrere Dinge gleichzeitig steuern? Gar nicht so einfach. Ein Design-Pattern hilft.

QRCodes sind prima ... wenn man es richtig macht

QR Codes sind eine tolle Erfindung. Wenn sie nur richtig angewendet werden würden

Autos statt Spam und Chatten statt Fliegen

Mit der Energie aller Spam Mails könnten 1,6 Millionen Autos fahren

Archiv