Tinkercad Übung 12 - RGB LED Farben mischen (1)

Farben mischen

In dieser Übung stellst du mit Tastern die Farbe einer RGB-LED ein.
  • Empfohlenes Vorwissen: Bedienung Tinkercad Circuits, pinMode(), digitalWrite(),  Verzweigungen, Prozeduren
  • Neue Inhalte: Funktionen, Wertebereiche von Datentypen, constrain()

Schritt 1 - Demo-Entwurf öffnen und inspizieren

In Übung 11 hast du eine LED gedimmt. Jetzt wollen wir das einen Schritt weiter treiben und damit Farben zusammen mischen.

Baue folgende Schaltung auf oder kopiere den Demo-Entwurf:
https://www.tinkercad.com/things/eayVRUgUaqp-kkg-robotik-ubung-12-starter
Abbildung 1 - Demo-Entwurf

Der Demo-Entwurf enthält 6 Taster und eine RGB-LED. Eine RGB-LED ist eigentlich 3 LEDs in einer, weswegen sie auch 4 Pins hat (von links nach rechts):
  1. rote LED (Plus-Pol)
  2. GND (Minus-Pol) für alle LEDs
  3. blaue LED (Plus-Pol)
  4. grüne LED (Plus-Pol)
Mit den 6 Tastern wirst du später die Helligkeiten der einzelnen Farbkanäle erhöhen oder verringern.

RGB steht für Rot, Grün und Blau. Das sind die 3 Farbkanäle. Da die 3 farbigen LEDs innerhalb der RGB-LED sehr nah beieinander gebaut sind, mischen sich die 3 Farbkanäle für unser Auge zu einer Farbe. Auf diese Art werden auch Displays und Fernseher gebaut: Jeder Pixel besteht aus jeweils einer roten, grünen und blauen LED. Die Farbmischung ist "additiv" (im Gegensatz zum Tuschkasten - da ist die Farbmischung subtraktiv).
Abbildung 1 - AMOLED Handydisplay Makroaufnahme
Wie sich die Farbanteile zusammenmischen, kannst du hier ausprobieren:
https://alloyui.com/examples/color-picker/hsv.html

Ziehe den kleinen Cursor über das angezeigte Farbspektrum und beobachte, wie sich die Werte R, G und B ändern. Bei einer eher roten Farbe sollte der R-Wert der größte sein, bei einer primär blauen Farbe der B-Wert usw. Das wird später auch noch mal wichtig, wenn wir mit Farbsensoren arbeiten.

Schritt 2 - Farbe einstellen

Schreibe nun ein Programm, dass die 3 PWM-Pins als Ausgang konfiguriert (Übung 5) und mit analogWrite() jeweils die Helligkeiten einstellt (Übung 11).

// Konstanten:
const int PIN_LED_ROT = 6;
const int PIN_LED_GRUEN = 3;
const int PIN_LED_BLAU = 5;

Da wir die Helligkeiten später verändern wollen, kannst du diese schon direkt als Variable übergeben.
Du kannst dir diese 3 Variablen auch über den Serial Monitor ausgeben. Das wird dir später helfen, wenn wir mit den Variablen rechnen.
Lösungsvorschlag: https://www.tinkercad.com/things/8dtFmmTCIei-kkg-robotik-ubung-12-farbe-einstellen
Abbildung 2 - Farben  mischen

Schritt 3 - Farben mit Tastern einstellen

Schreibe nun ein Programm, dass alle Taster-Pins als Eingänge konfiguriert. In loop() werden die Zustände der Taster nacheinander ausgelesen und damit die Helligkeits-Variablen der 3 Farbkanäle geändert. Es hilft, wenn du dir eine Konstante für die Schrittweite definierst. Ich habe 51 gewählt. Das macht 255 / 51 = 5 Helligkeitsschritte. Du kannst aber auch eine andere Schrittweite wählen. Dann kannst du die neuen Helligkeiten so ausrechnen:

Pseudocode:

// wenn Taster "weniger" gedrückt:
  // neue Helligkeit = alte Helligkeit - Helligkeitsschritt
// wenn Taster "mehr" gedrückt:
  // neue Helligkeit = alte Helligkeit + Helligkeitsschritt

...und das für alle 3 Farbkanäle. Denke daran, dass in loop() eine Verzögerung eingebaut werden muss, damit die Simulation flüssig läuft.

// Konstanten:
const int TASTER_PIN_ROT_WENIGER = 13;
const int TASTER_PIN_ROT_MEHR = 12;
const int TASTER_PIN_GRUEN_WENIGER = 11;
const int TASTER_PIN_GRUEN_MEHR = 10;
const int TASTER_PIN_BLAU_WENIGER = 9;
const int TASTER_PIN_BLAU_MEHR = 8;
const int HELLIGKEIT_SCHRITT = 51;        // Tastverhältnis

(Konstanten: Übung 7, Variablen & Verzweigungen: Übung 8, Taster auslesen: Übung 6, LED dimmen: Übung 11, Werte über Serial Monitor ausgeben: Übung 6)

Starte die Simulation und halte einen Taster gedrückt, um die Farbe der LED zu ändern. Mische Farben.
Das Programm ist schon wieder recht groß geworden mit viel kopierten Befehlszeilen. Verschiebe den Code aus loop() in Prozeduren, sodass in loop() nur noch folgendes übrig bleibt:

void loop()
{
  taster_auslesen();
  variablen_ausgeben();
  led_aktualisieren();
}

(Prozeduren: Übung 10)
Lösungsvorschlag: https://www.tinkercad.com/things/154QAKB5aaw-kkg-robotik-ubung-12-taster-auslesen
Abbildung 3 - Farben mit Tastern  mischen

Schritt 4 - Funktionen

In der Prozedur taster_auslesen() haben wir nun wiederholt das Konstrukt;

Pseudocode:

// wenn Taster "weniger" gedrückt:
  // neue Helligkeit = alte Helligkeit - Helligkeitsschritt

oder

// wenn Taster "mehr" gedrückt:
  // neue Helligkeit = alte Helligkeit + Helligkeitsschritt

Das könnte man jeweils auch in eine Prozedur auslagern. Es geht aber noch besser: Nämlich mit Funktionen. Man kann Prozeduren nämlich auch schreiben wie mathematische Funktionen (daher der Name):

Eine mathematische Funktion definieren:

f(x) = 2 * x

Eine mathematische Funktion benutzen:

y = f(x)

zum Beispiel

y = f(3) = 2 * 3 = 6

Code-ifiziert sieht das so aus (Funktionsname "f" mal ein bisschen sprechender in "mal_2" umbenannt):

Definition:

int mal_2(int x) {
  return x * 2;
}

Benutzung:

int y = mal_2(x);

zum Beispiel

int y = mal_2(3); // y hat jetzt den Wert 6

Im Beispiel wird die mathematische Funktion f(x) definiert, die x mal 2 ausrechnet. Darunter die C++ Entsprechung. Die Funktion f nimmt einen Parameter x (eine Zahl) auf und gibt dann (x * 2) wieder zurück. Wenn man für dieses Argument nun 3 einsetzt, wird x durch 3 ersetzt, somit (3 * 2) gerechnet. Das Ergebnis von f(3) ist also 6.

Die C++ Variante davon sieht der mathematischen Schreibweise sogar recht ähnlich. Da Computer doof sind, muss man ihnen die Datentypen ausbuchstabieren. In unserem Fall kann die Funktion mal_2 nur Zahlen mal 2 nehmen. Der Eingangswert ist also ein int und das Ergebnis auch.

Das Ergebnis gibt man mit dem Schlüsselwort return zurück. Das Ergbnis hat den Datentyp int, den man vor den Namen der Funktion schreibt. Der Parameter x (Eingangswert) ist auch vom Typ int.

int mal_2(int x) {
  return x2;
  // Dieser Code wird ignoriert!
}

Sobald mit return das Ergebnis zurückgeliefert wird, ist die Funktion beendet und wird nicht weiter ausgeführt. Der Code danach wird übersprungen.

Wollten wir also einen Taschenrechner programmieren, könnten wir folgendes schreiben:

int multipliziere(int aint b) {
  return a * b;
  // Dieser Code wird ignoriert!
}

int a = 3;
int b = 5;
int c = multipliziere(a, b); // c hat den Wert 15

Die Werte für a und b werden in multipliziere() eingesetzt, ausgerechnet und die Funktion anschließend durch ihr Ergebnis ersetzt:

int multipliziere(35) {
  return 3 * 5;
}

int a = 3;
int b = 5;
int c = 15;                  // c hat den Wert 15


Das wollen wir nun mit den Helligkeiten auch so machen.

Schreibe das Programm so um, dass die Rechnungen

alte Helligkeit - Helligkeitsschritt

und

alte Helligkeit + Helligkeitsschritt

jeweils von einer Funktion ausgerechnet werden und benutze die Funktionen in taster_auslesen(). Helligkeitsschritt muss nicht als Parameter an die Funktion übergeben werden, da dies ja eine Konstante ist und sich somit nie ändert.

Lösungsvorschlag: https://www.tinkercad.com/things/2s3FMDfVvG0-kkg-robotik-ubung-12-funktionen
Starte die Simulation und teste, ob noch alles genauso funktioniert wie vorher. Was beobachtest du, wenn du einen Taster länger gedrückt hältst?

Schritt 5 - Überlauf vermeiden

Du hast sicherlich gerade festgestellt, dass wenn du einen Taster gedrückt hältst, sich der Farbverlauf immer wiederholt, also z.B. "Rot mehr" gedrückt gehalten ist die RGB-LED erst aus, wird dann immer röter, dann schlagartig ist sie wieder aus, dann wird sie wieder immer röter usw.

Wenn dein Programm die Werte der 3 Helligkeits-Variablen bereits im Serial Monitor ausgibst, öffne den Serial Monitor. Wenn nicht, kopiere den Lösungsvorschlag aus Schritt 3 und öffne den Serial Monitor.

Starte die Simulation und halte einen Taster gedrückt. Was beobachtest du?

So lange der Taster gedrückt bleibt, ändert sich auch ständig der Wert der Helligkeits-Variable, weil ja wiederholt entweder z.B.

helligkeit_rot = helligkeit_rot - HELLIGKEIT_SCHRITT;

oder

helligkeit_rot = helligkeit_rot + HELLIGKEIT_SCHRITT;

gerechnet wird. Die LED-Helligkeit (das PWM-Tastverhältnis) kann aber nur zwischen 0 und 255 eingestellt werden. Irgendwann ist der Wert der Variablen helligkeit_rot aber größer oder kleiner als das, weil man ja so oft die Taster betätigen (oder gedrückt halten) kann wie man will. Dann "springt" die Helligkeit:
  • bei 256 wieder zurück auf Helligkeit 0 (Überlauf)
  • bei -1 wieder zurück auf Helligkeit 255 (Unterlauf)
Der Grund für den Über- oder Unterlauf ist, dass wir den zulässigen Wertebereich der Variable über oder unterschritten haben. Der Wertebereich wird vom Datentyp vorgegeben.

Schau mal in der Arduino-Referenz, was die zu dem Befehl analogWrite() zu sagen hat:
Besonders interessant ist der Hinweis bei dem Parameter "value".

Bisher hast du folgende Datentypen kennen gelernt (Übung 6):
  • Text (mehrere Zeichen)
  • ein einzelnes Zeichen
  • Zahlen
Das nehmen wir ab jetzt ein bisschen genauer. Für Zahlen haben wir bisher immer den Datentyp int verwendet. Der kann sehr große negative Zahlen bis sehr große positive Zahlen abspeichern.

Es gibt aber noch andere Zahlen-Datentypen, die entweder nur kleinere Zahlen oder sogar größere Zahlen abspeichern können. Manche Datentype können negative und positive Zahlen, manche nur positive Zahlen speichern. Manche Datentypen können Nachkommastellen abspeichern, andere nur Ganzzahlen (so wie int).

Kleine Datentypen benutzt man, wenn man weiß, dass die Zahl nie einen gewissen Wert überschreiten wird (z.B. volle Helligkeit einer LED, volle Geschwindigkeit eines Motors). Große Datentypen verwendet man, wenn man etwas ausrechnen oder messen will, wo sehr große Zahlen herauskommen könnten oder man es einfach nicht genau vorhersagen kann, was herauskommen wird (z.B. wenn man eine Stoppuhr programmiert, kann die der Benutzer des Programms ja theoretisch unendlich lang laufen lassen, wodurch die gemessene Zeit sehr lang und somit die Zahl sehr groß wird, die in der Variablen abgespeichert werden muss).

Variablen mit großen Wertebereichen oder mit Nachkommastellen verbrauchen generell mehr Speicherplatz im RAM und es ist für die CPU schwieriger und somit auch langsamer mit ihnen zu rechnen. Wenn man also möchte, dass das Programm möglichst schnell läuft und möglichst wenig RAM verbraucht, wählt man immer den Datentyp mit dem kleinstmöglichen Wertebereich.

Im Cheat Sheet findest du eine Übersicht aller Datentypen und ihre Wertebereiche:

Schau dir im Cheat Sheet die Datentypen an. Welcher passt denn zum Wertebereich 0 bis 255?

Ausschnitt aus dem Cheat Sheet:
Abbildung 4 - Datentypen

Wir haben es hier mit dem Datentyp byte zu tun (auch wenn die Arduino-Referenz das nicht so genau sagt). Dieser kann nur 1 Byte Daten, also Werte zwischen 0 bis 255 abspeichern. Der größte Datentyp ist übrigens long: Der kann über 2 Milliarden positive und negative Zahlen abspeichern. Da passt alles rein, es sei denn man möchte Sterne oder Sandkörner zählen. Die Stoppuhr könnte man 63 Jahre lang laufen lassen, wenn sie Sekunden zählen soll - mit byte aber nur knapp über 4 Minuten.

Wir wollen nun versuchen den Über- und Unterlauf zu vermeiden, indem die Helligkeit nur weiter verändert wird, wenn dadurch nicht ein Ergebnis kleiner 0 oder größer 255 herauskommt:

Pseudocode:

// wenn Taster "weniger" gedrückt:
  // neue Helligkeit = alte Helligkeit - Helligkeitsschritt
  // wenn neue Helligkeit < 0, dann
    // neue Helligkeit = alte Helligkeit
// wenn Taster "mehr" gedrückt:
  // neue Helligkeit = alte Helligkeit + Helligkeitsschritt
  // wenn neue Helligkeit > 255, dann
    // neue Helligkeit = alte Helligkeit

Wir werfen also gewissermaßen das Ergebnis der Rechnung weg und verändern den Wert nicht, wenn das Ergebnis der Rechnung außerhalb des Wertebereiches liegen sollte.

Glücklicherweise musst du diesen Code jetzt nicht 6x kopieren, da du das Ausrechnen der neuen Helligkeit ja schon in eine Funktion ausgelagert hast, die mehrfach benutzt werden kann. Du muss dies also nur jeweils 1x in jeder Funktion hinzufügen. Erweitere das Programm so, dass die Funktion den Eingangswert (Parameter) wieder zurück gibt, wenn der errechnete Wert zu groß oder zu klein wird.

Öffne den Serial Monitor, starte die Simulation und halte einen Taster gedrückt.
Abbildung 5 - Blau steigt nie über 255
Der Blau-Wert übersteigt nie 255, egal wie lang der Taster gedrückt bleibt.

Schritt 6 - constrain()

Es gibt bereits eine fertige Arduino-Funktion, die uns dabei hilft:

int beschraenkte_zahl = constrain(zahl, untere_grenze, obere_grenze);

Lies dir die Beschreibung zu der Funktion in der Arduino-Referenz durch:

Die constrain()-Funktion macht genau das, was du in Schritt 5 programmiert hast: Ist zahl zu groß, wird obere_grenze zurückgegeben, ist die zahl zu klein, untere_grenze.

Baue das Programm so um, dass es anstatt der

  // neue Helligkeit = alte Helligkeit - Helligkeitsschritt
  // wenn neue Helligkeit < 0, dann
    // neue Helligkeit = alte Helligkeit

und

  // neue Helligkeit = alte Helligkeit - Helligkeitsschritt
  // wenn neue Helligkeit > 255, dann
    // neue Helligkeit = alte Helligkeit

Konstrukte die constrain()-Funktion benutzt.

Lösungsvorschlag: https://www.tinkercad.com/things/dt0v3OUUTb4-kkg-robotik-ubung-12-constrain

Zusammenfassung

In dieser Übung hast du analogWrite() verwendet, um die Helligkeiten der einzelnen Farbkanäle einer RGB-LED einzustellen und damit Farben zu mischen. Du hast Funktionen kennen gelernt und weißt, dass die Datentypen von Variablen und Konstanten Wertebereiche haben, auf die man aufpassen muss.

Du weißt nun alles, um das Projekt "Digitales Klavier" zu probieren.

Weiter zu Übung 13

Kommentare

Kommentar veröffentlichen

Beliebte Posts aus diesem Blog

Tinkercad Übung 6 - LED mit Taster ansteuern

Tinkercad Übung 11 - LED dimmen

Tinkercad Übung 15 - Ultraschallsensor auslesen