Tinkercad Übung 10 - LED Lauflicht (4)

LED Animation mit Prozeduren

Das Ergebnis dieser Übung ist ähnlich wie in Übung 8, nur nutzt du Prozeduren und Schleifen dafür.
  • Empfohlenes Vorwissen: Bedienung Tinkercad Circuits, Übung 8Übung 9
  • Neue Inhalte: Prozeduren mit und ohne Parameter

Schritt 1 - Demo-Entwurf öffnen und inspizieren

Kopiere diesen Demo-Entwurf (eine der Animationen aus Übung 7):
https://www.tinkercad.com/things/9a2KP94iaaO-kkg-robotik-ubung-10-lauflicht-4-starter
Abbildung 1 - LED-Animation aus dem Demo-Entwurf

Starte die Animation und betätige den Taster. Schau in das Programm:

Das Programm im Demo-Entwurf konfiguriert alle LED-Pins (4 bis 13) als Ausgang. Dafür wird eine for-Schleife benutzt. For-Schleifen hast du in Übung 9 kennen gelernt.

loop() implementiert 5 Animationsschritte:
  1. alle LEDs ausschalten, dann LED 4 und 13 einschalten
  2. alle LEDs ausschalten, dann LED 5 und 12 einschalten
  3. alle LEDs ausschalten, dann LED 6 und 11 einschalten
  4. alle LEDs ausschalten, dann LED 7 und 10 einschalten
  5. alle LEDs ausschalten, dann LED 8 und 9 einschalten
Das sieht dann so aus, als würden 2 Punkte im Kreis laufen.

In dem Programm kommen viele Zeilen mehrfach vor. Sie sind einfach kopiert und haben nur kleine Änderungen (die Pin-Nummern):

// Animationsschritt 1
for (int pin=4; pin <= 13; pin++) {
  digitalWrite(pin, LOW); // alle LEDs ausschalten
}
digitalWrite(4, HIGH);
digitalWrite(13, HIGH);
delay(ANIMATIONSGESCHWINDIGKEIT);

// Animationsschritt 2
for (int pin=4; pin <= 13; pin++) {
  digitalWrite(pin, LOW); // alle LEDs ausschalten
}
digitalWrite(5, HIGH);
digitalWrite(12, HIGH);
delay(ANIMATIONSGESCHWINDIGKEIT);
usw...

Das wollen wir nun verbessern.

Schritt 2 - Duplizierten Code in eine Prozedur auslagern

Funktionen/Prozeduren hast du schon einige verwendet. Wir haben sie bisher nur als Arduino-"Befehle" bezeichnet, ähnlich den Blöcken in der EV3-Programmiersprache. Sie werden benutzt, um mehrere Befehle in einem Block zu gruppieren. Wenn man einen Block mal programmiert hat, kann man ihn so oft benutzen wie man möchte.

Diese Arduino-Funktionen/Prozeduren hast du bereits benutzt:
  • void setup()
  • void loop()
  • void pinMode(int pin, int modus)
  • void digitalWrite(int pin, bool zustand)
  • bool digitalRead(int pin)
  • void Serial.begin(int uebertragungsgeschwindigkeit)
  • void Serial.println(int wert)
  • void Serial.println(char buchstabe)
  • void Serial.println(String text)
Du weißt bereits, dass manche Befehle Parameter aufnehmen, mit denen man einstellen kann, was sie genau machen sollen. Du weißt auch, dass z.B. digitalRead() einen Rückgabewert hat (HIGH oder LOW) und man diesen in einem Vergleich verwenden oder wieder als Parameter für einen anderen Befehl benutzen kann.

Solche Befehle kannst du auch selbst erstellen. Stell sie dir wie "eigene Blöcke" in der EV3-Programmiersprache vor.

Abbildung 2  - Funktionen im Cheat Sheet

In C++:

void meine_prozedur() {
  // Befehle in der Prozedur "meine_prozedur"
}

Der Name der Prozedur ist meine_prozedur. Den Namen kann man sich aussuchen. Es dürfen aber keine Leerzeichen, Sonderzeichen (z.B. Umlaute) verwendet werden. Der Rückgabewert ist hier void. Das heißt die Prozedur hat keinen Rückgabewert. Den ignorieren wir erstmal. Parameter nimmt diese Prozedur auch keine auf. Die würden normalerweise in Klammern stehen. In geschweiften Klammern befindet sich der Inhalt der Prozedur.

Da können wir jetzt Programmcode reinpacken. Nehmen wir diese Befehlszeilen

digitalWrite(4, HIGH);
delay(ANIMATIONSGESCHWINDIGKEIT);
delay(ANIMATIONSGESCHWINDIGKEIT);
digitalWrite(13, HIGH);

und verschieben delay(ANIMATIONSGESCHWINDIGKEIT); in eine Prozedur:

void verzoegern() {
  delay(ANIMATIONSGESCHWINDIGKEIT);
}

Dann kann man diese Prozedur nun so oft aufrufen wie man will:

digitalWrite(4, HIGH);
verzoegern();
verzoegern();
digitalWrite(13, HIGH);

Das ist doch schon viel sprechender. Du sieht, ein Vorteil von Prozeduren ist, dass das Programm einfacher zu lesen ist.

Bei der Ausführung passiert folgendes:
  1. digitalWrite(4, HIGH); wird ausgeführt
  2. Einsprung in verzoegern(); hinein.
    1. delay(ANIMATIONSGESCHWINDIGKEIT); wird ausgeführt
  3. Sprung zurück an die Stelle, wo in verzoegern(); das 1. Mal hinein gesprungen wurde.
  4. erneut Einsprung in verzoegern(); hinein.
    1. delay(ANIMATIONSGESCHWINDIGKEIT); wird ausgeführt
  5. Sprung zurück an die Stelle, wo in verzoegern(); das 2. Mal hinein gesprungen wurde.
  6. digitalWrite(13, HIGH); wird ausgeführt.
Der Wert einer Prozedur besteht aber vor allem darin, dass man das Programm damit wesentlich kleiner machen kann, indem oft kopierte Befehlszeilen in eine Prozedur ausgelagert und dann mehrfach verwendet werden können.

Teste, ob das Programm momentan funktioniert. Starte dazu die Simulation.

Stoppe die Simulation.
Aus loop(), verschiebe

for (int pin=4; pin <= 13; pin++) {
  digitalWrite(pin, LOW); // alle LEDs ausschalten
}

in eine Prozedur und ersetze alle Vorkommnisse von dieser for-Schleife durch den Aufruf der Prozedur. Das Programm wird nun schon viel kleiner, sollte aber noch genauso funktionieren.
Teste dies, indem du die Simulation startest,

Wenn du möchtest, kannst du natürlich auch alle delay(ANIMATIONSGESCHWINDIGKEIT); Zeilen in eine Prozedur auslagern. Das wird das Programm aber nicht kleiner machen, vielleicht aber leichter lesbar.

Schritt 3 - Duplizierten Code in einer Schleife ausführen

Wir wollen nun nach Wegen suchen den Inhalt von loop() noch weiter zu schrumfen und gehen die verbleibenden Befehle an:

// Animationsschritt 1
alle_aus();
digitalWrite(4, HIGH);
digitalWrite(13, HIGH);
delay(ANIMATIONSGESCHWINDIGKEIT);

// Animationsschritt 2
alle_aus();
digitalWrite(5, HIGH);
digitalWrite(12, HIGH);
delay(ANIMATIONSGESCHWINDIGKEIT);
usw...

Die einzelnen Animationsschritte sehen sich sehr ähnlich und können bestimmt auch in eine Prozedur ausgelagert werden. Aber Moment: Sie unterscheiden sich leider immer ein bisschen (Pin-Nummern). Prozedur geht nicht.

Versuchen wir erstmal was anderes: In Übung 9 hast du Schleifen kennen gelernt. Diese können zählen. Vielleicht ist es ja möglich die Animationsschritte in einer Schleife durchzuführen:

for (int schritt = 4; schritt <= 8; schritt++) {
  alle_aus(); // alle LEDs ausschalten
  // LEDs für schritt einschalten
  digitalWrite(schritt, HIGH);
  digitalWrite(schritt, HIGH);
  delay(ANIMATIONSGESCHWINDIGKEIT); // verzögern
}

Für den ersten digitalWrite()-Befehl klappt das ja ganz gut. Wenn wir uns die einzelnen Animationsschritte anschauen, kann man die recht gut hochzählen:
  • Animationsschritt 1: Pin 4
  • Animationsschritt 2: Pin 5
  • Animationsschritt 3: Pin 6
  • Animationsschritt 4: Pin 7
  • Animationsschritt 5: Pin 8
Für den zweiten digitalWrite()-Befehl klappt das allerdings nicht. Da muss eigentlich das hier rauskommen:
  • Animationsschritt 1: Pin 13
  • Animationsschritt 2: Pin 12
  • Animationsschritt 3: Pin 11
  • Animationsschritt 4: Pin 10
  • Animationsschritt 5: Pin 9
Wir könnten jetzt die Schleife umschreiben für das 2. digitalWrite(), sodass sie von 13 bis 9 herunterzählt, aber dann passt es für das erste wieder nicht:

for (int schritt = 13; schritt >= 9; schritt--) {
  alle_aus(); // alle LEDs ausschalten
  // LEDs für schritt einschalten
  digitalWrite(schritt, HIGH);
  digitalWrite(schritt, HIGH);
  delay(ANIMATIONSGESCHWINDIGKEIT); // verzögern
}

Wir brauchen also jeweils eine Formel, mit der wir von der Zählvariable schritt zu den eigentlichen Pin-Nummern für die beiden digitalWrite()-Befehle kommen:

Tabelle:

schritt     Pin Nummer 1. digitalWrite    Pin Nummer 2. digitalWrite
1            4                            13
2            5                            12
3            6                            11
4            7                            10
5            8                             9

Fällt dir eine Formel ein?

Zum Beispiel

erste_pin_nummer = schritt + 3
zweite_pin_nummer = 14 - schritt

Setzt man da die Schritte 1 bis 5 ein, kommen die richtigen Pin Nummern heraus.

In Übung 8 hast du gelernt, dass man mit Variablen auch rechnen kann und schritt ist eine Variable.

for (int schritt = 1; schritt <= 5; schritt++) {
  alle_aus(); // alle LEDs ausschalten
  // LEDs für schritt einschalten
  digitalWrite(schritt + 3, HIGH);
  digitalWrite(14 - schritt, HIGH);
  delay(ANIMATIONSGESCHWINDIGKEIT); // verzögern
}

Beim ersten Schleifendurchlauf ist schritt = 1, weil das im Schleifenkopf so initialisiert wird. Dann werden erstmal die Pin-Nummern ausgerechnet:

  digitalWrite(4, HIGH);
  digitalWrite(13, HIGH);

Dann erst wird digitalWrite() aufgerufen.

Das ganze wird wiederholt mit den Werten schritt = 2, 3, 4, 5.

Bau das Programm so um, dass alle 5 Animationsschritte ersetzt werden durch eine Schleife. Das Programm wird nun schon viel kleiner, sollte aber noch genauso funktionieren.
Teste dies, indem du die Simulation startest,

Schritt 4 - Einen Parameter definieren

Das Programm ist nun schön kurz geworden, kann aber immer noch das selbe wie vorher. Jetzt kann man es noch ein bisschen schöner machen. Vielleicht bekommen wir es ja doch hin

digitalWrite(3 + schritt, HIGH);
digitalWrite(14 - schritt, HIGH);

in eine Prozedur auszulagern. Es gibt nun keine unterschiedlichen Pin-Nummern mehr im Code, dafür aber die Variable schritt.

Die können wir aber als Parameter übergeben:

// 2 LEDs einschalten je nach gegebenem
// Animationsschritt (1 bis 5)
void einschalten(int schritt) {
  digitalWrite(3 + schritt, HIGH);
  digitalWrite(14 - schritt, HIGH);
}

Benutzung:

einschalten(1); // Animationsschritt 1
einschalten(2); // Animationsschritt 2
usw...

Diese Prozedur definiert nun einen Parameter mit dem Namen schritt vom Typ int (also eine Zahl). Wenn man die Prozedur benutzt, muss man diesen Parameter angeben. Das kann direkt eine Magic Number sein, eine Variable oder eine Konstante.

Bei der Ausführung passiert folgendes:
  1. Einsprung in einschalten(1); hinein. Der Parameter schritt wird mit dem Wert 1 übergeben.
    1. In einschalten() werden alle Vorkommnisse von schritt durch 1 ersetzt:
  2. Sprung zurück an die Stelle, wo in einschalten(1); hinein gesprungen wurde.
  3. Einsprung in einschalten(2); hinein. Der Parameter schritt wird mit dem Wert 2 übergeben.
    1. In einschalten() werden alle Vorkommnisse von schritt durch 2 ersetzt:
  4. Sprung zurück an die Stelle, wo in einschalten(2); hinein gesprungen wurde.
Baue das Programm so um, dass in loop() kein digitalWrite() mehr vorkommt, sondern die beiden Zeilen in eine eigene Prozedur ausgelagert sind. Diese nimmt einen Parameter vom Typ int (Zahl) auf, der in der Prozedur für das Ausrechnen der richtigen Pin-Nummern verwendet wird.

Teste, ob das Programm noch funktioniert, indem du die Simulation startest,

Zusammenfassung

Du hast gelernt wie du Programmcode, den du oft verwendest in einer Prozedur verpacken und so einfach wiederverwenden kannst. Du kannst Parameter an eine Prozedur übergeben und so einstellen, was sie genau machen soll.


Kommentare

Beliebte Posts aus diesem Blog

Tinkercad Übung 6 - LED mit Taster ansteuern

Tinkercad Übung 11 - LED dimmen

Tinkercad Übung 15 - Ultraschallsensor auslesen