Benutzer-Werkzeuge

Webseiten-Werkzeuge


skript:klassen-arduino

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Nächste Überarbeitung
Vorhergehende Überarbeitung
skript:klassen-arduino [2018/10/07 18:18]
d.golovko angelegt
skript:klassen-arduino [2021/06/11 11:28] (aktuell)
d.golovko
Zeile 1: Zeile 1:
-=====Klassen in C++=====+=====Klassen in Arduino/C++=====
  
-In dieser Aufgabe gehen wir davon aus, dass ihr Grundkenntnisse über Klassen in Java/​Processing habt im Umfang der 3. WocheLINK ​des Crashkurses. ​+In dieser Aufgabe gehen wir davon aus, dass ihr Grundkenntnisse über Klassen in Java/​Processing habt im Umfang der [[skript:​klassen|3. Woche]] ​des Crashkurses ​sowie Grundkenntnisse über [[einleitung:​arrays|Arrays in C++]].
  
 In Arduino kann man auch mit Klassen arbeiten. Im Folgenden ​  ​wollen wir auf dem Piezo unterschiedliche Melodien abspielen und diese mit Hilfe einer Klasse ''​Song''​ modellieren. Baut eine Schaltung mit dem Piezo auf.  In Arduino kann man auch mit Klassen arbeiten. Im Folgenden ​  ​wollen wir auf dem Piezo unterschiedliche Melodien abspielen und diese mit Hilfe einer Klasse ''​Song''​ modellieren. Baut eine Schaltung mit dem Piezo auf. 
  
-====Die Song-Klasse====+====1. Deklaration====
  
-Jeder Song hat zwei Eigenschaften:​ die Liste von abzuspielenden Noten in Form eines ''​int''​-Arrays und der Pin, welcher mit dem Piezo verbunden ist. +Jeder Song soll zwei Eigenschaften ​haben: die Liste von abzuspielenden Noten in Form eines ''​int''​-Arrays und der Pin, welcher mit dem Piezo verbunden ist. 
  
 Eine Klasse in C++ besteht in der Regel aus zwei Dateien: einer Header-Datei ''​.h'',​ die alle Variablen, Konstruktoren und Methoden benannt werden, und einer ''​.cpp''​-Datei,​ die die eigentliche Impementieriung der Methoden beihnaltet. In unserem Beispiel kann die Klasse so aussehen: Eine Klasse in C++ besteht in der Regel aus zwei Dateien: einer Header-Datei ''​.h'',​ die alle Variablen, Konstruktoren und Methoden benannt werden, und einer ''​.cpp''​-Datei,​ die die eigentliche Impementieriung der Methoden beihnaltet. In unserem Beispiel kann die Klasse so aussehen:
Zeile 14: Zeile 14:
 class Song { class Song {
   public:   public:
-    Song(); // Konstruktor, muss noch mit Parametern versehen werden+    Song(int noteList[], int arraySize, int pinNumber); // Konstruktor
     void play(); // Methode zum Abspielen der Melodie     void play(); // Methode zum Abspielen der Melodie
   private:   private:
-    int notes[]; // Noten-Frequenzen in Hz+    int *notes; // Noten-Frequenzen in Hz; ist ein Zeiger, da Array-Laenge noch nicht bekannt 
 +    int arraySize; // Laenge des Noten-Arrays
     int pin; // Piezo-Pin     int pin; // Piezo-Pin
 }; };
Zeile 24: Zeile 25:
 <file cpp Song.cpp>​ <file cpp Song.cpp>​
 #include "​Song.h"​ #include "​Song.h"​
-Song::​Song() {+Song::Song(int noteList[], int arraySize, int pinNumber) {
   // TODO: Implementierung des Konstruktors fehlt   // TODO: Implementierung des Konstruktors fehlt
 } }
Zeile 36: Zeile 37:
  
 Ihr sieht oben, dass die Header-Datei zwei Blöcke hat, die mit ''​public''​ und ''​private''​ anfangen. Diese Schlüsselworte weisen auf die [[https://​de.wikipedia.org/​wiki/​Sichtbarkeit_(Programmierung)|Sichtbarkeit]] der Variablen und Funktionen hin. ''​Public''​ bedeutet, dass die Variable/​Funktion nur innerhalb der Klasse sichtbar ist, ''​protected''​ innerhalb der Klasse und ihrer Unterklassen,​ ''​public''​ überall. (Ähnliche Ebenen gibt es übrigens auch in Java.) Der Faustregel ist, die Sichtbarkeit möglichst begrenzt zu wählen. Die Begründung ist die gleiche wie bei globalen und lokalen Variablen: Je mehr Stellen haben Zugriff auf die Variable, desto mehr fehleranfällig ist der Code. Wir machen den Konstruktor und die Funktion ''​play()''​ ''​public'',​ damit wir darauf von unserem Haupt-Tab zugreifen können. Die beiden Variablen sind ''​private'',​ sie sollen nur innerhalb der Klasse verfügbar sein. Es ist üblich, in der Header-Datei den ''​public''​-Block ganz oben zu platzieren. ​ Ihr sieht oben, dass die Header-Datei zwei Blöcke hat, die mit ''​public''​ und ''​private''​ anfangen. Diese Schlüsselworte weisen auf die [[https://​de.wikipedia.org/​wiki/​Sichtbarkeit_(Programmierung)|Sichtbarkeit]] der Variablen und Funktionen hin. ''​Public''​ bedeutet, dass die Variable/​Funktion nur innerhalb der Klasse sichtbar ist, ''​protected''​ innerhalb der Klasse und ihrer Unterklassen,​ ''​public''​ überall. (Ähnliche Ebenen gibt es übrigens auch in Java.) Der Faustregel ist, die Sichtbarkeit möglichst begrenzt zu wählen. Die Begründung ist die gleiche wie bei globalen und lokalen Variablen: Je mehr Stellen haben Zugriff auf die Variable, desto mehr fehleranfällig ist der Code. Wir machen den Konstruktor und die Funktion ''​play()''​ ''​public'',​ damit wir darauf von unserem Haupt-Tab zugreifen können. Die beiden Variablen sind ''​private'',​ sie sollen nur innerhalb der Klasse verfügbar sein. Es ist üblich, in der Header-Datei den ''​public''​-Block ganz oben zu platzieren. ​
 +
 +Wenn in C++ Arrays als Parameter an eine Funktion übergeben werden, wird nur der Zeiger auf das erste Array-Element übergeben. D.h. die Funktion bekommt keine Kopie des ganzen Arrays, sondern nur die Adresse des ersten Elementes. Eine Lösung ist, die Array-Länge als einen weiteren Parameter (''​arraySize''​ in unserem Beispiel) zu übergeben. In der Liste der Membervariablen wird ebenso nur ein Zeiger ''​int *notes''​ verwendet, da die Deklaration eines Arrays ohne seiner Länge nicht möglich ist. 
 +
 +====2. Implementierung====
 +
 +Im Beispiel oben steht ein Konstruktor ohne Parameter ''​Song()''​ als Platzhalter – das wollen wir jetzt ändern. Wenn ein ''​Song''​ erzeugt wird, soll ihm die Liste der Noten sowie die Pinnummer übergeben werden. Also wir wollen sowas: ​ \\
 +''​Song(int noteList[], int pinNumber)''​. In diesem Fall besteht aber das Problemm, dass in C++ Arrays nicht als Parameter an eine Funktion übergeben werden können. Stattdessen wird nur der Zeiger auf das erste Array-Element übergeben, die Iteration durch das Array ist nicht möglich. Die Lösung ist, zusätzlich noch die Array-Länge zu übergeben: ​
 +
 +<file cpp Song.cpp>​
 +//[...]
 +// Konstruktor;​ noteList: Notenabfolge,​ arraySize: Laenge des Noten-Arrays,​ pin: Piezo-Pin
 +Song::​Song(int noteList[], int arraySize, int pinNumber) {
 +  this->​arraySize = arraySize;
 +  this->​notes = noteList;
 +  this->​pin = pinNumber;
 +}
 +
 +//[...]
 +</​file>​
 +
 +Genauso wie in Java wird ''​this''​ benutzt, um auf die Instanz der Klasse (d.h. auf die gerade erzeugte Objekt vom Typ ''​Song''​) zu zeigen. Achtung: in C++ wird statt Punkt ein Pfeil verwendet.  ​
 +
 +Jetzt implementieren wir auch die Funktion ''​play()''​. Wenn wir ''​tone()''​ verwenden wollen, bekommen wir erstmal einen Fehler, da sie nicht zu unserer Klasse gehört. Die Lösung ist, die Klasse ''​Arduino.h''​ miteinzuschließen -- sie macht die Arduino-spezifische Funktionalität verfügbar. ​
 +<file cpp Song.cpp>​
 +#include "​Arduino.h"​
 +//[...]
 +// Spielt die Melodie ab
 +void Song::​play() {
 +  for (int i = 0; i < arraySize ; i++) {
 +    tone(pin, notes[i]);
 +    delay(500);
 +  }
 +  noTone(pin);​
 +}
 +//[...]
 +</​file>​
 +
 +<hidden Komplette Datei Song.cpp>​
 +<file cpp Song.cpp>​
 +#include "​Song.h"​
 +#include "​Arduino.h"​
 +
 +// Konstruktor;​ noteList: Notenabfolge,​ arraySize: Laenge des Noten-Arrays,​ pin: Piezo-Pin
 +Song::​Song(int noteList[], int arraySize, int pinNumber) {
 +  this->​arraySize = arraySize;
 +  this->​notes = noteList;
 +  this->​pin = pinNumber;
 +}
 +
 +// Spielt die Melodie ab
 +void Song::​play() {
 +  for (int i = 0; i < arraySize ; i++) {
 +    tone(pin, notes[i]);
 +    delay(500);
 +  }
 +  noTone(pin);​
 +}
 +</​file>​
 +</​hidden>​
 +\\
 +
 +====3. Aufruf====
 +
 +Nun werden wir einen Song im Haupt-Tab erstellen und aufrufen, z.B. so (Piezo auf Pin 2):
 +
 +<file cpp variante1>​
 +#include "​Song.h"​
 +
 +void setup() {
 +  int notes[] = {262, 294, 330, 349, 392}; // Notenfrequenzen
 +  Song mySong (notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2);
 +  mySong.play();​
 +}
 +
 +void loop() { 
 +}
 +</​file>​
 +
 +Achtung: wir müssen dem Compiler mit ''#​include Song.h''​ Bescheid sagen, dass eine Klasse aus einer anderen Datei verwendet wird. Falls wir auf die Variable ''​mySong''​ sowohl von ''​setup()''​ als auch von ''​loop()''​ zugreifen wollen, können wir diese inkl. Konstruktoraufruf auch global haben:
 +<file cpp variante2>​
 +#include "​Song.h"​
 +int notes[] = {262, 294, 330, 349, 392};
 +Song mySong (notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2);
 +
 +void setup() {
 +  mySong.play(); ​
 +}
 +
 +void loop() {
 +  mySong.play();​
 +}
 +</​file>​
 +
 +Der Konstruktor hier wird aufgerufen, bevor ''​setup()''​ anfängt. Das kann ungünstig sein im Fall, wenn der Konstruktor Parameter hat, deren Werte am Anfang des Programms noch nicht bekannt sind (z.B. wenn sie von Sensormessungen abhängen, die in ''​setup()''​ gemacht werden). Anders als in Java, können wir nicht einfach ''​Song mySong;''​ in Zeile 3 schreiben. Das liegt daran, dass ''​mySong''​ eine Referenz ist, und Referenzen dürfen nicht gleich NULL sein. 
 +
 +<​note>​
 +Die Unterscheidung zwischen Referenzen und Zeigern (Englisch: pointers) in C++ ist ein separates großes Thema -- bei Interesse lesst [[https://​www.geeksforgeeks.org/​pointers-vs-references-cpp/​|diesen Link]] oder sucht Informationen dazu online. Wir werden dieses Thema hier nicht angehen. ​
 +</​note>​
 +
 +Wir werden stattdessen einen //Zeiger// nutzen -- man kann das so verstehen, dass, statt des Objektes an sich, haben wir nur einen Verweis darauf, wo dieses Objekt sich befindet. Ein Zeiger wird mit einem ''​*''​ markiert, und der Zugriff auf Funktionen und Variablen geht über ''​->''​.
 +
 +<file cpp variante3>​
 +#include "​Song.h"​
 +Song* mySong; // Zeiger
 +
 +void setup() {
 +  int notes[] = {262, 294, 330, 349, 392};
 +  mySong = new Song(notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2);
 +  mySong->​play(); ​
 +}
 +
 +void loop() {
 +  mySong->​play();​
 +}
 +</​file>​
 +
skript/klassen-arduino.1538929096.txt.gz · Zuletzt geändert: 2018/10/07 18:18 von d.golovko