Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

skript:interrupts


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.

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 ''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 ''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.

1.1. Erkennen des Knopfdrucks

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.

 Schaltung mit einem Knopf
Abbildung 1: Schaltung mit einem Knopf

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
}

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().

Euer Ergebnis könnt ihr mit dieser Musterlösung vergleichen.

Euer 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);
}


2. Listeners in Processing

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.

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 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 ''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
}


Abbildung 2: 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.

Abbildung 3: 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 ''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
}
skript/interrupts.txt · Zuletzt geändert: 2021/01/27 19:02 von d.golovko