Arduino

Entscheidungen, Entscheidungen – Verzweigungen

Dein Programm reagiert auf den Joystick und trifft eigene Entscheidungen.

Verschiedene Wege

Bislang laufen deine Programme stur Zeile für Zeile durch – das soll sich nun ändern.

Eine Verzweigung sagt deinem Programm, dass es unter bestimmten Bedingungen Unterschiedliches tun soll. Das ist mit einer Entscheidung vergleichbar, die du triffst: Je nach Situation tust du verschiedene Dinge.

In C++/Arduino erstellst du eine Verzweigung mit der „if“-Anweisung. Sie prüft eine Bedingung und führt einen Abschnitt nur dann aus, wenn die Bedingung erfüllt ist. Ist sie nicht erfüllt, wird der Abschnitt übersprungen.

Grün, wenn der Joystick gedrückt ist: if

#define LEDR 5                             // rote LED an Pin 5
#define LEDG 7                             // grüne LED an Pin 7
#define KEY_CENTER 0                       // Druck auf die Mitte des Joysticks

void setup(void) {
  pinMode(KEY_CENTER, INPUT_PULLUP);       // Eingang: Der Taster wird gelesen.
  pinMode(LEDR, OUTPUT);                   // Ausgang: Er schaltet eine LED.
  pinMode(LEDG, OUTPUT);                   // Ausgang: Er schaltet eine LED.

  digitalWrite(LEDG, LOW);                 // Schaltet die grüne LED aus.
  digitalWrite(LEDR, HIGH);                // Schaltet die rote LED ein.

  if (digitalRead(KEY_CENTER) == LOW) {    // Falls die Mitte gedrückt ist ...
    digitalWrite(LEDG, HIGH);              // ... schalte die grüne LED ein ...
    digitalWrite(LEDR, LOW);               // ... und die rote LED aus.
  }
}

void loop() {}

Anmerkungen

  • Dieses Programm steht komplett in setup() und wird daher nur ein einziges Mal durchlaufen – und das sehr schnell. Du merkst einen Unterschied also nur, wenn du den Joystick gedrückt hältst, während du den PicoBoy mit der Taste „RES“ neu startest. Keine Bange, du siehst bald, wie es besser geht.
  • Durch INPUT_PULLUP liegt der Pin im Ruhezustand auf HIGH. Gedrückt ist der Taster also dann, wenn digitalRead() den Wert LOW liefert – das wirkt zunächst verdreht, ist aber bei Tastern der Normalfall.
  • Beachte die zusätzlichen geschweiften Klammern „{ … }“. Sie begrenzen den Codeabschnitt, der bei erfüllter Bedingung ausgeführt wird.
  • Die Vergleichsoperatoren in C++ sind „<“ (kleiner), „>“ (größer), „<=“ (kleiner oder gleich), „>=“ (größer oder gleich), „==“ (gleich) und „!=“ (ungleich).
  • Ein doppeltes „==“ vergleicht, ein einfaches „=“ weist zu. Das zu verwechseln ist der häufigste Anfängerfehler in C++.

Programmablaufplan: Pins festlegen, rote LED an und grüne LED aus, Frage Joystick-Mitte gedrückt; bei ja grüne LED an und rote LED aus

Soll bei erfüllter Bedingung ein Teil des Quelltextes ausgeführt werden und bei nicht erfüllter Bedingung ein anderer, kannst du eine „else“-Anweisung hinzufügen.

Immer genau eine LED: if … else

#define LEDR 5
#define LEDG 7
#define KEY_CENTER 0

void setup(void) {
  pinMode(KEY_CENTER, INPUT_PULLUP);
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);

  if (digitalRead(KEY_CENTER) == LOW) {
    digitalWrite(LEDG, HIGH);
    digitalWrite(LEDR, LOW);
  } else {
    digitalWrite(LEDG, LOW);
    digitalWrite(LEDR, HIGH);
  }
}

void loop() {}

Anmerkungen

  • Während der Ausführung zeigt sich kein sichtbarer Unterschied zum ersten Programm. Der Unterschied steckt im Aufbau und geht aus dem Programmablaufplan hervor: Hier wird immer genau einer der beiden Abschnitte ausgeführt.

Programmablaufplan: Pins festlegen, Frage Joystick-Mitte gedrückt; bei ja grüne LED an und rote aus, bei nein grüne LED aus und rote an

Verzweigungen in loop()

Natürlich lassen sich Verzweigungen auch mit den Grafikbefehlen kombinieren. Im nächsten Beispiel stehen sie in der Funktion loop() und werden daher unentwegt wiederholt, nachdem setup() abgearbeitet ist. So kannst du den Joystick endlich wirklich benutzen: Du steuerst einen Punkt über das Display.

Einen Punkt mit dem Joystick bewegen

#include <U8g2lib.h>
#include <SPI.h>

#define KEY_RIGHT 2
#define KEY_DOWN 3
#define KEY_LEFT 4
#define KEY_UP 1
#define KEY_CENTER 0

U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, 10, 8, 9);

// Diese beiden Variablen stehen außerhalb der Funktionen,
// damit setup() und loop() sie gemeinsam nutzen können.
int xPos;
int yPos;

void setup(void) {
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.sendBuffer();

  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_CENTER, INPUT_PULLUP);

  xPos = 64;                               // Startposition: Mitte des Displays
  yPos = 32;
}

void loop() {
  if (digitalRead(KEY_UP) == LOW) {        // nach oben: y um 1 kleiner
    yPos = yPos - 1;
  }
  if (digitalRead(KEY_DOWN) == LOW) {      // nach unten: y um 1 größer
    yPos = yPos + 1;
  }
  if (digitalRead(KEY_LEFT) == LOW) {
    xPos = xPos - 1;
  }
  if (digitalRead(KEY_RIGHT) == LOW) {
    xPos = xPos + 1;
  }
  if (digitalRead(KEY_CENTER) == LOW) {    // Mitte: löschen, zurück zum Start
    u8g2.clearBuffer();
    xPos = 64;
    yPos = 32;
  }
  delay(50);
  u8g2.drawPixel(xPos, yPos);              // Punkt an der aktuellen Position
  u8g2.sendBuffer();
}

Anmerkungen

  • Der Displayspeicher wird nur beim Druck auf die Mitte geleert. Deshalb bleiben die gezeichneten Punkte stehen und du malst eine Linie – wie bei einer Zaubertafel.
  • Die fünf Abfragen sind voneinander unabhängig. Drückst du den Joystick schräg, treffen zwei davon gleichzeitig zu und der Punkt wandert diagonal.
  • delay(), drawPixel() und sendBuffer() sollen auf jeden Fall ausgeführt werden und stehen daher außerhalb der geschweiften Klammern. Sie laufen, nachdem alle if-Blöcke durchlaufen sind.
  • Das delay(50) bremst den Punkt. Ohne diese Pause würde loop() so oft pro Sekunde durchlaufen, dass der Punkt sofort am Rand wäre.

Logische Operatoren

Führst du das Programm mit dem Punkt aus, zeigt sich ein Problem: Du kannst aus dem Display „herauszeichnen“. Der Punkt wandert über den Rand hinaus und ist nicht mehr zu sehen, weil seine Koordinaten außerhalb des Displays liegen.

Mit sogenannten logischen Operatoren lässt sich das verhindern. Eine Bedingung für einen Schritt nach rechts könnte lauten: Gehe nach rechts, wenn der Joystick nach rechts gedrückt wird und die aktuelle Position noch mindestens einen Schritt von der rechten Kante entfernt ist. Das „und“ schreibt man in C++ als „&&“.

Am Rand stoppen: &&

#include <U8g2lib.h>
#include <SPI.h>

#define KEY_RIGHT 2
#define KEY_DOWN 3
#define KEY_LEFT 4
#define KEY_UP 1
#define KEY_CENTER 0

U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, 10, 8, 9);

int xPos;
int yPos;

void setup(void) {
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.sendBuffer();

  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_CENTER, INPUT_PULLUP);

  xPos = 64;
  yPos = 32;
}

void loop() {
  if (digitalRead(KEY_UP) == LOW && yPos > 0) {        // noch Platz nach oben?
    yPos = yPos - 1;
  }
  if (digitalRead(KEY_DOWN) == LOW && yPos < 63) {     // 63: unterste Zeile
    yPos = yPos + 1;
  }
  if (digitalRead(KEY_LEFT) == LOW && xPos > 0) {
    xPos = xPos - 1;
  }
  if (digitalRead(KEY_RIGHT) == LOW && xPos < 127) {   // 127: rechte Spalte
    xPos = xPos + 1;
  }
  if (digitalRead(KEY_CENTER) == LOW) {
    u8g2.clearBuffer();
    xPos = 64;
    yPos = 32;
  }
  delay(50);
  u8g2.drawPixel(xPos, yPos);
  u8g2.sendBuffer();
}

Der Schritt wird nur ausgeführt, wenn beide Bedingungen gelten. Steht der Punkt bei y = 0, ist yPos > 0 nicht mehr erfüllt, und nach oben geht es nicht weiter, egal wie du am Joystick drückst.

Neben dem Und gibt es weitere Operatoren, um logische Aussagen zu verknüpfen. Hier ein Überblick – in den Tabellen steht, was bei welcher Kombination herauskommt:

UND (&&)

Nur wahr, wenn beide Aussagen wahr sind.

aba && b
falsefalsefalse
falsetruefalse
truefalsefalse
truetruetrue

ODER (||)

Wahr, wenn mindestens eine der beiden Aussagen wahr ist.

aba || b
falsefalsefalse
falsetruetrue
truefalsetrue
truetruetrue

NICHT (!)

Dreht eine Aussage um: aus wahr wird falsch und umgekehrt.

anot a
falsetrue
fruefalse

ODER und NICHT im Programm

Das nächste Programm benutzt beide. Die gelbe LED leuchtet, solange du den Joystick nach links oder rechts drückst. Die grüne LED leuchtet, solange er nicht nach links gedrückt ist.

#define LEDY 6
#define LEDG 7
#define KEY_RIGHT 2
#define KEY_LEFT 4

void setup(void) {
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(LEDY, OUTPUT);
  pinMode(LEDG, OUTPUT);
}

void loop() {
  // Ein Vergleich ergibt true oder false, das passt in eine boolean-Variable.
  boolean links = digitalRead(KEY_LEFT) == LOW;
  boolean rechts = digitalRead(KEY_RIGHT) == LOW;

  if (links || rechts) {  // ODER: mindestens eine der beiden Richtungen
    digitalWrite(LEDY, HIGH);
  } else {
    digitalWrite(LEDY, LOW);
  }

  if (!links) {           // NICHT: solange links NICHT gedrückt ist
    digitalWrite(LEDG, HIGH);
  } else {
    digitalWrite(LEDG, LOW);
  }

  delay(20);
}

Anmerkungen

  • Ein Vergleich liefert true oder false. Dieses Ergebnis kannst du in einer boolean-Variablen aufheben, wie hier in links und rechts. Die Bedingungen darunter werden dadurch kürzer und lesen sich fast wie ein Satz.
  • Links und rechts von && und || muss je eine vollständige Bedingung stehen. xPos > 0 && < 127 versteht der Compiler nicht, und 0 < xPos < 127 tut nicht, was es in der Mathematik bedeutet. Richtig ist xPos > 0 && xPos < 127.
  • Hinter die Klammer von if gehört kein Semikolon. if (xPos > 0); beendet die Verzweigung sofort, und der Abschnitt in den geschweiften Klammern läuft dann immer.

Mehrere Fälle: else if

Schließen sich mehrere Fälle gegenseitig aus, hängst du sie mit else if aneinander. Das Programm prüft von oben nach unten und führt nur den ersten Abschnitt aus, dessen Bedingung erfüllt ist. Alle weiteren werden übersprungen.

if (punkte >= 100) {             // ab 100 Punkten: grün
  digitalWrite(LEDG, HIGH);
} else if (punkte >= 50) {       // sonst ab 50 Punkten: gelb
  digitalWrite(LEDY, HIGH);
} else {                         // alles andere: rot
  digitalWrite(LEDR, HIGH);
}

Bei 120 Punkten leuchtet also nur die grüne LED, obwohl auch die zweite Bedingung erfüllt wäre.

Übung

Schreibe ein Programm, das den Joystick in loop() abfragt: Nach oben leuchtet die rote LED, nach links oder rechts die gelbe und nach unten die grüne. Lässt du los, gehen alle LEDs aus. Tipp: Schalte am Anfang von loop() alle drei LEDs aus und danach die passende wieder ein.