Einfache Objektorientierung mit Arduino
Klassen und Objekte stellen eine Weiterentwicklung des Konzeptes von Variablen und Funktionen dar. Während eine Variable nur einen Wert (oder als Array auch mehrere Werte) besitzen kann, so kann ein Objekt zusätzlich auch Methoden besitzen. Dies ermöglicht es, einen gut lesbaren und flexibel einsetzbaren Code zu schreiben. Es wird dann auch von objektorientierter Programmierung gesprochen.
Stehen bei Variablen vordefinierte Variablentypen zur Verfügung (int
, float
, char
, ...), so ist es bei Objekten notwendig, den Objekttyp selbst als eine Art Bauplan für das Objekt zu programmieren. In der objektorientierten Programmierung wird dieser (selbst geschriebene) Objekttyp Klasse genannt.
Aber was ist nun der Vorteil von Klassen bzw. Objekten. Nehmen wir als Beispiel für den Arduino eine Schaltung mit 5 LEDs. Jede LED hat einen eigenen Pin-Anschluss, einen eigenen Zustand (an/aus) und soll mit festgelegten An-/Auszeiten blinken können. Dieses Verhalten ließe sich ohne Weiteres auch ohne eine Klasse für die LEDs realisieren. Allerdings müssten dafür eine Menge globaler Variablen deklariert werden, damit jede LED unabhängig voneinander gesteuert werden kann. Das macht den Code schnell unübersichtlich und schwer zu lesen:
bool zustandLED1;
bool zustandLED2;
// ...
int anZeit1;
int anZeit2;
// ...
int ausZeit1;
int ausZeit2;
// ...
Der Vorteil der objektorientierten Programmierung liegt aber darin, das wir nur eine Klasse für beiliebig viele LEDs schreiben müssen. Ist die Klasse programmiert, können beliebig viele Objekte für die realen LEDs erzeugt werden. Der Code bleibt übersichtlich und leicht verständlich.
Beispiel einer LED-Klasse
Am Beispiel einer einfachen LED-Klasse, welche eine LED umschalten (togglen) soll, wird im Folgenden das Grundkonzept der objektorientierten Programmierung erläutert.
Klassendefinition
Um eine Klasse zu schreiben (oder besser zu definieren) wird das Schlüsselwort class
verwendet. Zwischen die geschweiften Klammern { ... }
folgt der Code für die Klasse. Nach der Klassenbeschreibung folgt ein Semikolon.
class Led {
// Beschreibung der Klasse
}; // Semikolon am Ende
Die LED soll später mit einem einfachen Befehl (toggle()
) ihren Zustand umschalten können (an/aus). Dafür muss die Klasse Attribute (Variablen) zum Speichern des aktuellen LED-Zustands (stateLed
) und des verwendeten LED-Pins (pinLed
) haben.
class Led {
// Attribute (Variablen) der Klasse
int pinLed; // LED-Pin
bool stateLed; // Zustand der LED
};
Konstruktor
Da eine Klasse nur eine Art Bauplan für die späteren Objekte ist, muss es auch eine Möglichkeit geben, dem Objekt beim Erzeugen ein paar Informationen zu übergeben. Hierfür wird der Konstruktor verwendet.
Der Konstruktor ist eine spezielle Methode, welche einmalig beim Erzeugen eines Objektes aufgerufen wird. Der Konstruktor hat, wie alle anderen Methoden einer Klasse auch, Zugriff auf die Attribute (Variablen) einer Klasse. Der Konstruktor wird genauso wie eine normale Methode definiert, jedoch ohne das Schlüsselwort void
. Der Name des Konstruktors entspricht dem Klassennamen.
Damit auf den Konstruktor auch zugegriffen werden kann, muss das der Klasse durch das Schlüsselwort public
mitgeteilt werden. Auf alle Methoden und Attribute, die nach public:
folgen, kann später direkt zugegriffen werden.
class Led { // Beginn der Klassendefinition
// Attribute (Eigenschaften) der Klasse
int pinLed; // LED-Pin
bool stateLed; // Zustand der LED
public: // public: öffentliche Methoden und Attribute
Led(int pPin) { // Konstruktor (ohne void, int, etc.)
pinLed = pPin; // Attribut `pinLed` erhält den Wert
// des Konstruktor-Parameters `pPin`
stateLed = false; // Zustand der LED auf false (LOW)
pinMode(pinLed, OUTPUT); // pinLed als Ausgang definieren
}
};
Der Konstruktor im obigen Beispiel erwartet als Parameter (pPin
) den verwendeten Pin. Innerhalb des Konstruktors wird dem Attribut pinLed
dann der Wert des Parameters übergeben. Das Objekt speichert also den für die LED verwendeten Pin als Attribut. Außerdem wird der Zustand der LED in stateLed
auf false
gesetzt. Die LED soll also aus sein. Als letztes wird der angegeben Pin als Ausgang definiert.
Methode zum Umschalten
Was der LED-Klasse an dieser Stelle noch fehlt, ist die Methode toggle()
zum Umschalten der LED. Da auf die Methode, genauso wie den Konstruktor, öffentlich zugegriffen werden soll, wird sie nach dem Schlüsselwort public
definiert.
// Methode der Led-Klasse
// Die Methoden einer Klassen können auf dessen Attribute zugreifen
void toggle() {
stateLed = !stateLed; // `stateLed`: Attribute der Klasse
digitalWrite(pin, stateLed);
}
Mit der Zeile
stateLed = !stateLed;
wird der logische Wert des Attributs stateLed
negiert. War stateLed
zuerst true
, ist es danach false
. Wird anschließend der Zustand der LED mit dem Befehl
digitalWrite(pin, stateLed);
aktualisiert, so ändert die LED bei jedem Aufruf der Methode ihren Zustand.
Das vollständige Programm
Im vollständigen Programm sollen mit der obigen Klasse zwei LED Objekte ledGreen
und ledRed
erzeugt werden. Das Erzeugen eines neuen Objektes wird auch instanziieren genannt, was nichts anderes bedeutet, als dass mit der Klasse eine neue Instanz erzeugt wird.
Mit dem Klassennamen (Led
) gefolgt von einem frei wählbaren Namen für das Objekt (z.B. ledGreen
) und dem Parameter für den Konstruktor können nun verschiedene Objekte erzeugt werden.
Led ledGreen(4); // ein `Led` Objekt für Pin 4
Led ledRed(5); // ein weiteres `Led` Objekt für Pin 5
Zu guter Letzt muss die Method toggle()
für das jeweilige Objekt noch aufgerufen werden. Dafür wird das Objekt mit einem Punkt .
mit dem Methodennamen verknüpft.
ledGreen.toggle(); // Methodenaufruf vom Objekt `ledGreen`
ledRed.toggle(); // Methodenaufruf vom Objekt `ledRed`
Zusammengefasst ergibt sich folgendes Programm für zwei umschaltbare LEDs mit einer Led-Klasse.
class Led { // Beginn der Klassendefinition
int pinLed; // LED-Pin (Attritbute der Klasse)
bool stateLed; // Zustand der LED
public: // public: öffentliche Methoden und Attribute
Led(int pPin) { // Konstruktor (ohne void, int, etc.)
pinLed = pPin;
stateLed = false;
pinMode(pinLed, OUTPUT);
}
void toggle() { // Die Methoden können auf die Attribute zugreifen
stateLed = !stateLed;
digitalWrite(pinLed, stateLed);
}
}; // Eine Klassendefinition endet mit einem SEMIKOLON
Led ledGreen(4); // ein `Led` Objekt mit dem Namen `ledGreen` mit Pin 4
Led ledRed(5); // ein weiteres `Led` Objekt mit Pin 5
void setup() {
// bleibt leer
}
void loop() {
ledGreen.toggle(); // Methodenaufruf vom Objekt ledGreen
delay(200);
ledRed.toggle(); // Methodenaufruf vom Objekt ledGreen
delay(800);
ledGreen.toggle();
delay(200);
ledRed.toggle();
delay(800);
}
Wie nutze ich Tabs mit Klassen
Das vollständige Programm mit der LED Klasse kann auch in mehrere Tabs unterteilt werden. Wird eine Klasse in einem separaten Tab definiert, so muss der Name des Tabs die Dateiendung .cpp
erhalten. Außerdem muss mit dem Befehl #include "LedKlasse.cpp"
der entsprechende Tab in das Hauptprogram eingebunden werden.
Das Hauptprogramm (mit void setup()
und void loop()
) steht in dem ersten Tab mit dem Namen des Sketches (Blink
). Klassen, welche in einem anderen Tab definiert werden, müssen mit dem Befehl #include "LedKlasse.cpp"
in das Hauptprogramm eingebunden werden.
Tabs, in denen Klassen definiert werden, müssen mit der Dateiendung .cpp
(z.B. LedKlasse.cpp
) benannt werden. Damit in diesen Tabs auch die Arduino-Befehle genutzt werden können, muss die Arduino-Bibliothek mit dem Befehl #include <Arduino.h>
eingebunden werden.
Außerdem müssen die Tabs (eigentlich eigene Dateien) dem Hauptprogramm mit dem #inlcude
Befehl zugänglich gemacht werden.
- Blink
- LedKlasse.cpp
// Einbinden der Datei LedKlasse.cpp mit der `Led` Klasse
#include "LedKlasse.cpp"
#define LED_GREEN 3
#define LED_RED 6
Led ledGreen(LED_GREEN); // ein `Led` Objekt mit dem Namen `ledGreen` mit Pin 4
Led ledRed(LED_RED); // ein weiteres `Led` Objekt mit Pin 5
void setup() {
// bleibt leer
}
void loop() {
ledGreen.toggle(); // Methodenaufruf vom Objekt ledGreen
delay(200);
ledRed.toggle(); // Methodenaufruf vom Objekt ledGreen
delay(800);
ledGreen.toggle();
delay(200);
ledRed.toggle();
delay(800);
}
#include <Arduino.h> // Einbinden der Datei Arduino-Bibliothek
class Led { // Beginn der Klassendefinition
int pinLed; // LED-Pin (Attritbute der Klasse)
bool stateLed; // Zustand der LED
public: // public: öffentliche Methoden und Attribute
Led(int pPin) { // Konstruktor (ohne void, int, etc.)
pinLed = pPin;
stateLed = false;
pinMode(pinLed, OUTPUT);
}
void toggle() { // Die Methoden einer Klassen können auf die Attribute zugreifen
stateLed = !stateLed;
digitalWrite(pinLed, stateLed);
}
}; // Eine Klassendefinition endet immer mit einem Semikolon
Aufgaben
Erweitern Sie die
Led
Klasse im obigen Beispielprogramm um die Methodenanschalten()
undausschalten()
. Testen Sie die neuen Methoden mit einem geeignetem Programm.Schreiben Sie eine Klasse
Blinker
, welche eine Led blinken lässt.Um die LED mit den vorgegebenen An- und Auszeiten blinken zu lassen, wird eine Methode
update()
benötigt, welche regelmäßig aufgerufen werden muss. Die Methode misst die Zeit seit dem letzten Umschalten der LED und überprüft, ob die LED wieder umgeschaltet werden muss.Nutzen Sie die Vorlage und ergänzen Sie den Code für die Methoden und
update()
undsetBlink()
(legt die An-/Auszeit des Blinkers fest).#define LED1 3
#define LED2 5
class Blinker
{
int onTime; // in ms
int offTime; // in ms
int pinLed;
int stateLed;
unsigned long lastTime;
public:
Blinker(int pin)
{
onTime = 1000;
offTime = 1000;
pinLed = pin;
lastTime = 0;
stateLed = LOW;
pinMode(pinLed, OUTPUT);
}
// Lässt die LED durch regelmäßiges Aufrufen blinken.
void update()
{
// ***** Fehlenden Code ergänzen *****
}
// Konfiguriert die An- und Auszeit des Blinkers
void setBlink(int pOn, int pOff)
{
// ***** Fehlenden Code ergänzen *****
}
};
Blinker blink1(LED1);
Blinker blink2(LED2);
void setup() {
blink1.setBlink(500, 100);
blink2.setBlink(400, 200);
}
void loop() {
blink1.update();
blink2.update();
}
Ein Array mit Objekten
Grundsätzlich ist es bei einem Array egal, von welchen Datentyp die Elemente sind (solange alle Elemente den selben Datentyp haben). Deshalb kann auch ein Array aus LED-Objekten erzeugt werden.
Um ein Array mit Objekten zu erzeugen, wird zuerst der Objekttyp (Klassenname) Led
und danach der Namen des Arrays ledListe
geschrieben. Nach dem Arraynamen folgen zwei eckige Klammern []
, welche anzeigen, dass es sich um einen Array handelt.
Anschließend werden in einer geschweiften Klammer { ... }
die einzelnen Elemente, also die jeweiligen LED-Objekte übergeben.
Led ledListe[] = {Led(3), Led(6), Led(9), Led(10)};
Soll nun ein einzelnes Objekt des Array angesprochen werden, so wird das mit dem entsprechenden Index in eckigen Klammern getan.
ledListe[0].toggle(); // Schaltet die 1. LED
ledListe[1].toggle(); // Schaltet die 2. LED
Aufgaben
- Erweitern Sie die Schaltung der Aufgabe 2 auf insgesamt 5 LEDs. Überlegen Sie sich ein interessantes Blinkmuster, welches Sie mit der Led-Klasse und einem Array aus Led-Objekten programmieren.