===== Interrupts und Listeners ===== 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. {{:skript:arduino-logo.png?100}} =====1. Interrupts in Arduino===== 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 [[https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/|''attachInterrupt()'']]. Diese Funktion horcht auf ein Signal auf einem bestimmten Pin und führt, falls das Signal detektiert wurde, 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 [[https://www.arduino.cc/reference/en/language/functions/time/delay/|''delay()'']] und [[https://www.arduino.cc/reference/en/language/functions/time/millis/|''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. === 1.1. Erkennen des Knopfdrucks === Für diese Aufgabe baut die Schaltung mit einem Knopf (Abb. {{ref>circuit}}) 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.
{{:skript:button-sketch2.png?800 | Schaltung mit einem Knopf}} Schaltung mit einem Knopf
''attachInterrupt()'' hat drei Parameter. Liesst in der [[https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/|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 } === 1.2. Delays in ''loop()'' === 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"); } === 1.3. Aufgabe: LED auf Knopfdruck === Schreibt ein Programm, welches auf Knopfdruck die eingebaute LED (''LED_BUILTIN'') anschaltet und nach 5 Sekunden wieder ausschaltet. Verwendet dabei Interrupts und ''millis()''. 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); } \\ {{:intern:processing-logo.png?70 |}} =====2. Listeners in Processing ===== Listeners und ähnliche [[https://de.wikipedia.org/wiki/Ereignis_(Programmierung)|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. ==== 2.1. Listener-Methoden in Processing ==== 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); } ====2.2. Ereignisbehandlung in Processing==== 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 [[https://de.wikipedia.org/wiki/Thread_(Informatik)|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. ====2.3. Aufgabe: ein Katze-Maus-Spiel==== 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 [[https://processing.org/reference/keyPressed_.html|''keyPressed()'']] und die Variable [[https://processing.org/reference/keyCode.html|''keyCode'']], um die Katzenbewegung zu realisieren. 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 } 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 } \\
{{:skript:cat-and-mouse-game-start.png?500|}} Die "Maus" (klein, links) und die "Katze" (groß, rechts) im Spielfenster
\\ ====2.4. Aussetzen von ''draw()''===== 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.
{{:skript:cat-and-mouse-game-stopped.png?500|}} Spielende: Die "Katze" hat die "Maus" eingeholt
Anschließend werden wir noch die Möglichkeit einbauen, das Spiel mit einem Mausklick neuzustarten. Dafür implementieren wir die Listener-Methode [[https://processing.org/reference/mouseClicked_.html|''mouseClicked()'']] und rufen dort [[https://processing.org/reference/loop_.html|''loop()'']] auf. 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 }