Dies ist eine alte Version des Dokuments!
Hier werden zwei ähnliche Konzepte bei Arduino und Processing behandelt und jeweils eine Aufgabe gestellt. Ihr könnt sie aber auch getrennt voneinander lesen / lösen.
Manchmal muss ein Teil des Programms ausgeführt werden, sobald ein extermes (nicht vom Programm selbst gesteuertes) Erreignis passiert, z.B. wenn ein Knopf gedruckt wird.
In Arduino gibt es für solche Fälle die Funktion ''attachInterrupt()''. Diese Funktion horscht auf ein Signal auf einem bestimmten Pin und, fallls das Signal detektiert wurde, führt den für diesen Fall vorgesehenen Code aus. Die Interrupts sind also eine der drei Möglichkeiten in Arduino, die Ausführung des Code in der Zeit zu steuern, neben ''delay()'' und ''millis()''. Der Vorteil von Interrupts ist, dass sie im Gegensatz zu ständigen Abfragen in loop()
Resourcen sparen und vermeiden, dass das Signal verloren geht, wenn das Hauptprogramm in loop()
gerade mit anderen Aufgaben beschäftigt ist.
Hier werden zwei ähnliche Konzepte bei Arduino und Processing behandelt und jeweils eine Aufgabe gestellt. Ihr könnt sie aber auch getrennt voneinander lesen / lösen.
Manchmal muss ein Teil des Programms ausgeführt werden, sobald ein extermes (nicht vom Programm selbst gesteuertes) Erreignis passiert, z.B. wenn ein Knopf gedruckt wird.
Für diese Aufgabe baut die Schaltung mit einem Knopf (Abb. 1) auf. Wenn der Knopf gedruckt wird, wird auf dem Pin 2 Spannung gemessen. Ansonsten fließt der Strom zum GND ab. Vergisst nicht den Widerstand, um einen Kurzschluß zu vermeiden.
attachInterrupt()
hat drei Parameter. Liesst in der Referenz, was sie bedeuten, und schaut euch dieses Beispiel an:
void setup() { Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(2), doInterrupt, RISING); } void loop() { } void doInterrupt() { // Der Name dieser Funktion muss mit dem Parameter in attachInterrupt() uebereinstimmen Serial.println("Knopf gedruckt"); // hier kommt alles, was auf Knopfdruck passieren muss }
Wenn ihr in loop()
Delays einbaut, werdet ihr sehen, dass der Code in doInterrupt()
unabhängig von den Delays sofort ausgeführt wird (s. das folgende Code-Beispiel). Dasselbe passiert, wenn der Code in loop()
länger braucht: seine Ausführung wird unterbrochen, um auf das Signal rechtzeitig zu reagieren. Ohne Interrupts würde das Signal in solchen Fällen verloren gehen. Übrigens: Delays in der Funktion doInterrupt()
selbst haben keine Wirkung.
void setup() { Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(2), doInterrupt, RISING); } void loop() { Serial.println("loop() Start"); delay(5000); Serial.println("loop() Ende"); Serial.println(); } void doInterrupt() { Serial.println("Knopf gedruckt"); }
Schreibt ein Programm, welches auf Knopfdruck die eingebaute LED (LED_BUILTIN
) anschaltet und nach 5 Sekunden wieder ausschaltet. Verwendet dabei Interrupts und millis()
.
Eures Ergebnis könnt ihr mit dieser Musterlösung vergleichen.
Eures Ergebnis könnt ihr mit dieser Musterlösung vergleichen.
unsigned long timeButtonClick; // counts time since last button click void setup() { Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(2), buttonClicked, RISING); } void loop() { if (millis() - timeButtonClick > 5000) { // if the led has been on for >5 seconds, turn it off digitalWrite(LED_BUILTIN, LOW); } } // Actions to do on button click void buttonClicked() { timeButtonClick = millis(); // update the time of last button click digitalWrite(LED_BUILTIN, HIGH); }
Listeners und ähnliche Ereignisbehandlungsroutinen sind Bestandteil vieler Programmiersprachen und erfüllen die gleiche Funktion wie Interrupts in Arduino: Sie werden benachrichtigt, wenn bestimmte Ereignisse passieren, welche sie “abonniert” haben. Z.B. kann man einen Listener schreiben, der benachrichtigt, wenn ein bestimmter Knopf auf der Benutzeroberfläche gedruckt wurde.
Processing stellt Listeners in vereinfacher Form zur Verfügung. Es gibt die Methoden keyPressed()
, keyTyped()
, mouseMoved()
, mouseClicked()
und andere. Sie haben die entsprechenden Ereignisse bereits “abonniert” und müssen nur aufgerufen werden, wenn man sich für diese Ereignisse interessiert. Z.B. kann man so auf ein Mausklick die Hintergrundfarbe zwischen Weiß und Schwarz wechseln:
int col = 0; // aktuelle Hintergrundfarbe void setup() { } void draw() { } void mouseClicked() { if (col==0) { col=255; // Schwarz -> Weiss } else { col = 0; // Weiss -> Schwarz } background(col); }
Im Gegensatz zu Interrupts bei Arduino wird der Code in den Listener-Methoden nicht sofort ausgeführt. Dies könnt ihr beobachten, wenn ihr einen Delay in die draw()
-Methode einbaut:
void draw() { delay(5000); }
Dann wird die Hintergrundfarbe nicht sofort geändert, sondern erst nach dem Ablauf der jeweiligen draw()
. Das liegt daran, dass in Processing Ereignisse in einem separaten Thread (d.h. durch ein anderes Prozess) verwaltet werden. Es gibt also einen Thread (er nennt sich „animation thread“), welcher setup()
und draw()
ausführt. Ein anderer Thread („event dispatching thread“) sammelt alle gestartete Ereignisse auf. Dieser Thread kommt am Ende von jedem draw()
dran.
Implementiert ein Katze-Maus-Spiel. In diesem Spiel sind die beiden Tiere durch Kreise auf der Zeichenfläche dargestellt. Die Bewegung der Maus wird durch das Programm (s. unten) gesteuert. Die Katze wird vom Spieler mit den Tastaturpfeilen (links, rechts, oben und unten) bewegt. Nutzt die Methode ''keyPressed()'' und die Variable ''keyCode'', um die Katzenbewegung zu realisieren.
Programm für die Mausbewegung zum Herunterladen und Erweitern
Programm für die Mausbewegung zum Herunterladen und Erweitern
int ratX = 300; // x-Koordinate der Maus int ratY = 100; // y-Koordinate der Maus int catX = 20; // x-Koordinate der Katze int catY = 20; // y-Koordinate der Katze int ratSpeedX = 2; // Geschwindigkeit der Maus in x-Richtung int ratSpeedY = 2; // Geschwindigkeit der Maus in y-Richtung int RAT_SIZE = 20; // Durchmesser des Maus-Kreises int CAT_SIZE = 40; // Durchmesser des Katze-Kreises drawEllipse(); void setup() { size(600, 200); } void draw() { background(130); // Hintergrund grau // Fortbewegung der Maus: ratX = ratX + ratSpeedX; ratY = ratY + ratSpeedY; if (ratX < RAT_SIZE || ratX > width-RAT_SIZE) { ratSpeedX = -ratSpeedX; // am Rand umdrehen } if (ratY < RAT_SIZE || ratY > height-RAT_SIZE) { ratSpeedY = -ratSpeedY; // am Rand umdrehen } drawEllipse(); // zeichne die Kreise } // Zeichnet die Kreise void drawEllipse() { ellipse(ratX, ratY, RAT_SIZE, RAT_SIZE); // zeichne die Maus ellipse(catX, catY, CAT_SIZE, CAT_SIZE); // zeichne die Katze }
Musterlösung zum Vergleichen
Musterlösung zum Vergleichen
int ratX = 300; // x-Koordinate der Maus int ratY = 100; // y-Koordinate der Maus int catX = 20; // x-Koordinate der Katze int catY = 20; // y-Koordinate der Maus int ratSpeedX = 2; // Geschwindigkeit der Maus in x-Richtung int ratSpeedY = 2; // Geschwindigkeit der Maus in y-Richtung int CAT_SPEED = 5; // Geschwindigkeit der Katze int RAT_SIZE = 20; // Durchmesser des Maus-Kreises int CAT_SIZE = 40; // Durchmesser des Katze-Kreises void setup() { size(600, 200); } void draw() { background(130); // Fortbewegung der Maus: ratX = ratX + ratSpeedX; ratY = ratY + ratSpeedY; if (ratX < RAT_SIZE || ratX > width-RAT_SIZE) { ratSpeedX = -ratSpeedX; } if (ratY < RAT_SIZE || ratY > height-RAT_SIZE) { ratSpeedY = -ratSpeedY; } drawEllipse(); // zeichne die Kreise } // Reagiert auf Tastaturpfeile void keyPressed() { if (keyCode==RIGHT) { catX = catX+CAT_SPEED; } else if (keyCode==LEFT) { catX = catX-CAT_SPEED; } else if (keyCode==UP) { catY = catY-CAT_SPEED; } else if (keyCode==DOWN) { catY = catY+CAT_SPEED; } } // Zeichnet die Kreise void drawEllipse() { ellipse(ratX, ratY, RAT_SIZE, RAT_SIZE); // zeichne die Maus ellipse(catX, catY, CAT_SIZE, CAT_SIZE); // zeichne die Katze }
Nun wollen wir dem Spieler klar machen, wenn das Spielziel erreicht ist. Wenn die “Katze” die “Maus” eingeholt hat, soll das Spiel anhalten und der Bildschirm rot werden. Dafür müssen wir draw()
“ausschalten”. Das kann mit der Methode noLoop() gemacht werden.
Anschließend werden wir noch die Möglichkeit einbauen, das Spiel mit einem Mausklick neuzustarten. Dafür implementieren wir die Listener-Methode ''mouseClicked()'' und rufen dort ''loop()'' auf.
Vollständiger Spielcode
Vollständiger Spielcode
int ratX = 300; // x-Koordinate der Maus int ratY = 100; // y-Koordinate der Maus int catX = 20; // x-Koordinate der Katze int catY = 20; // y-Koordinate der Maus int ratSpeedX = 2; // Geschwindigkeit der Maus in x-Richtung int ratSpeedY = 2; // Geschwindigkeit der Maus in y-Richtung int CAT_SPEED = 5; // Geschwindigkeit der Katze int RAT_SIZE = 20; // Durchmesser des Maus-Kreises int CAT_SIZE = 40; // Durchmesser des Katze-Kreises void setup() { size(600, 200); } void draw() { background(130); // Fortbewegung der Maus: ratX = ratX + ratSpeedX; ratY = ratY + ratSpeedY; if (ratX < RAT_SIZE || ratX > width-RAT_SIZE) { ratSpeedX = -ratSpeedX; } if (ratY < RAT_SIZE || ratY > height-RAT_SIZE) { ratSpeedY = -ratSpeedY; } drawEllipse(); // zeichne die Kreise } // Reagiert auf Tastaturpfeile void keyPressed() { if (keyCode==RIGHT) { catX = catX+CAT_SPEED; } else if (keyCode==LEFT) { catX = catX-CAT_SPEED; } else if (keyCode==UP) { catY = catY-CAT_SPEED; } else if (keyCode==DOWN) { catY = catY+CAT_SPEED; } if (abs(catX-ratX) < CAT_SIZE/2.0 + RAT_SIZE/2.0 && abs(catY-ratY) < CAT_SIZE/2.0 + RAT_SIZE/2.0) { // wenn die Katze die Maus gefangen hat background(255, 0, 0); // Mach den Hintergrund rot drawEllipse(); noLoop(); // Stopp das Spiel } } // Zeichnet die Kreise void drawEllipse() { ellipse(ratX, ratY, RAT_SIZE, RAT_SIZE); // zeichne die Maus ellipse(catX, catY, CAT_SIZE, CAT_SIZE); // zeichne die Katze } void mouseClicked() { loop(); // Restarte das Spiel auf Mausklick }