Kitabı oku: «Mit Arduino die elektronische Welt entdecken», sayfa 6

Yazı tipi:

Was sind Konstanten?‌

Irgendwo im Internet habe ich mal gelesen, dass eine konstante Variable eine Variable ist, deren Wert nach der Initialisierung nicht mehr geändert werden kann. Lies diesen Satz zweimal und bedenke, dass Folgendes gilt:

 konstant: nicht veränderlich, beständig gleichbleibend.

 variabel: veränderbar, nicht auf eine Möglichkeit beschränkt.

Deswegen stellt der erste Satz einen Widerspruch in sich dar. Wer denkt sich so etwas aus? Jedenfalls stimmt der zweite Teil, dass eine Konstante nach der Initialisierung zur Laufzeit des Programms nicht mehr geändert werden kann. Eine Konstante wird mit dem Schlüsselwort const für konstant gekennzeichnet.

const int a = 17;

Folgende Codezeilen wären demnach nicht zulässig und führen zu einer Fehlermeldung, da der Inhalt einer Konstante nicht mehr geändert werden darf:

const int a = 17; // Konstante void setup() { a = 18; // Nicht zulässig! } ...

Die Datentypen‌

Wir sollten uns nun ein wenig mit den unterschiedlichen Datentypen und der Frage, was ein Datentyp überhaupt ist und warum es so viele unterschiedliche davon gibt, beschäftigen. Der Mikrocontroller verwaltet seine Sketche und Daten in seinem Speicher. Dieser Speicher ist ein strukturierter Bereich, der über Adressen verwaltet wird und Informationen aufnimmt oder abgibt, wobei Informationen in Form von Einsen und Nullen gespeichert werden.

Was ist ein Datentyp?


Ein Datentyp beschreibt eine bestimmte Menge von Datenobjekten (Variablen), die allesamt die gleiche Struktur haben. Jede Variable muss dabei einen bestimmten Datentyp haben.

Die kleinste logische Speichereinheit ist das Bit‌, das eben die zwei Zustände 1 oder 0 speichern kann. Stell es dir als eine Art elektronischen Schalter vor, der ein- und ausgeschaltet werden kann. Da du mit einem Bit lediglich zwei Zustände abbilden kannst, sind mehrere Bits zur Speicherung der Daten sinnvoll und notwendig. Der Verbund aus 8 Bits wird 1 Byte‌ genannt und ermöglicht es, 28 = 256 unterschiedliche Zustände zu speichern. Die Basis 2 wird verwendet, weil es sich um ein binäres System handelt, das lediglich zwei Zustände kennt. Wir können mit 8 Bits also einen Wertebereich von 0 bis 255 abdecken.

Ich liste hier für den Anfang einmal die wichtigsten Datentypen auf, mit denen du in Zukunft konfrontiert wirst:


Tabelle 1: Datentypen mit entsprechenden Wertebereichen
Datentyp Wertebereich Datenbreite Beispiel
void keiner null void setup() {}
byte 0 bis 255 1 Byte byte wert = 42;
unsigned int 0 bis 65.535 2 Bytes unsigned int sekunden = 46547;
int –32.768 bis 32.767 2 Bytes int ticks = -325;
long –231 bis 231–1 4 Bytes long wert = -3457819;
float –3.4 * 1038 bis 3.4 * 1038 4 Bytes float messwert = 27.5679;
double siehe float 4 Bytes double messwert = 27.5679;
boolean true oder false 1 Byte boolean flag = true;
char –128 bis 127 1 Byte char mw = 'm';
String variabel variabel String name = "Erik Bartmann";
Array variabel variabel int pinArray[] = {2, 3, 4, 5};

Die meisten der hier gezeigten Datentypen werden wir auch in diesem Buch verwenden.

Was sind Funktionen‌?

Eine Funktion ist in den meisten höheren Programmiersprachen die Bezeichnung eines Programmkonstrukts, mit dem der Quellcode strukturiert wird, so dass diese Programmteile – die quasi als Unterprogramm bezeichnet werden können – im eigentlichen Hauptprogramm wiederverwendbar sind und somit an unterschiedlichen Stellen mehrfach aufgerufen werden können. In der Objektorientierten Programmierung, auf die ich noch eingehen werde, gibt es vergleichbare Konstrukte, die dort die Bezeichnung Methoden besitzen. Dort kommt auch der Begriff Kapselung erstmalig zur Sprache, wobei diese Bezeichnung ebenfalls auf die Funktionen zutrifft. Eine Kapselung ist eine Zusammenfassung oder das Verbergen von Daten beziehungsweise Informationen, wobei der Zugriff über eine definierte Schnittstelle erfolgt. Diese Schnittstelle wird durch den Funktionsaufruf abgebildet. Möchtest du in deinem Sketch also zum Beispiel mehrfach den Mittelwert zweier Zahlen bilden, dann sind normalerweise immer die folgenden Codezeilen erforderlich, wobei ich das extra etwas umständlich formuliert habe, damit der Sinn der Funktion etwas deutlicher wird. Es gibt Funktionen, die einen Rückgabewert an den Aufrufer zurückliefern, wie das im Moment hier der Fall ist und es gibt Funktionen, die führen etwas aus, ohne dass ein Wert an den Aufrufer zurückgeliefert wird:

float a = 5.4, b = 7.36; float summe = a + b; float mittelwert = summe / 2;

Beim Mittelwert zweier Zahlen werden diese addiert und das Ergebnis durch 2 geteilt. Willst du nun an mehreren Stellen im Sketch diesen Mittelwert bilden, wären immer wieder die beiden unteren Zeilen einzufügen. Das wird jedoch durch die Definition einer Funktion erleichtert. Diese könnte wie folgt aussehen:

float mittelwert(float a, float b) { return (a + b)/2; }

Das sollten wir uns genauer ansehen, denn da kommen viele Dinge zusammen:


Die erste Zeile einer Funktion wird als Signatur bezeichnet und ist die Deklarationszeile einer Funktion. Innerhalb der geschweiften Klammern befindet sich die Definition einer Funktion. Soll eine Funktion etwas an den Aufrufer zurückliefern, steht zu Beginn der sogenannte Rückgabedatentyp, der hier float ist. Das macht jedoch zwingend eine return-Anweisung innerhalb der Definition erforderlich, denn diese ist im Endeffekt für die Rückgabe eines Wertes verantwortlich, womit die Funktion nach ihrem Aufruf auch letztendlich verlassen wird. Man kann einer Funktion keinen, einen oder mehrere Werte beim Aufruf übergeben. In unserem Fall werden zwei Werte vom Datentyp float erwartet, die beim Aufruf an die dort aufgeführten Parameter a beziehungsweise b übergeben werden. Diese Parameter arbeiten wie lokale Variablen, die nach dem Verlassen der Funktion wieder aus dem Speicher entfernt werden, da sie nicht weiter benötigt werden. Die beiden prominentesten Funktionen in der Arduino-Entwicklungsumgebung sind natürlich die setup- und loop-Funktion, die immer vorhanden sein müssen:

void setup() {/* ... */} void loop() {/* ... */}

Es ist zu sehen, dass beide den Rückgabedatentyp void besitzen, was übersetzt leer bedeutet, weil sie keinen Wert zurückliefern. Zudem ist keine Parameterliste zu sehen, was an den leeren runden Klammerpaaren zu erkennen ist. Es können demnach auch beim Aufruf keine Werte mit übergeben werden. Natürlich gäbe es noch viel mehr über Funktionen zu berichten, doch das ist dann Thema von weiteren Bastelkapiteln oder Teil von C++-Tutorials und würde den Umfang dieses Buches etwas sprengen.

Was sind Kontrollstrukturen?‌

In Kapitel 2 – ich hatte es erwähnt – hast du schon etwas über Befehle erfahren. Sie teilen dem Mikrocontroller mit, was er zu tun hat. Ein Sketch besteht aber in der Regel aus einer ganzen Reihe von Befehlen, die sequentiell abgearbeitet werden. Das Arduino-Board ist mit einer bestimmten Anzahl von Ein- und Ausgängen versehen, an die du diverse elektrische und elektronische Komponenten anschließen kannst. Wenn der Mikrocontroller auf bestimmte Einflüsse von außen reagieren soll, schließt du beispielsweise einen Sensor an einen Eingang an. Die einfachste Form eines Sensors ist ein Schalter oder Taster. Wenn der Kontakt geschlossen wird, soll zum Beispiel eine LED leuchten. Der Sketch muss also eine Möglichkeit haben, eine Entscheidung zu treffen: Ist der Schalter geschlossen, dann versorge die LED mit Spannung (LED leuchtet); ist der Schalter offen, dann trenne die LED von der Spannungsversorgung (LED wird dunkel).


Was ist eine Kontrollstruktur?


Kontrollstrukturen sind Anweisungen in imperativen Programmiersprachen. Sie kommen zum Einsatz, um den Ablauf eines Programms zu steuern und es in eine bestimmte Richtung zu lenken. Dabei kann eine Kontrollstruktur entweder eine Verzweigung oder eine Schleife sein. Zumeist wird ihre Ausführung über logische (boolesche) Ausdrücke gesteuert.

Wir werfen zu Beginn einen Blick auf ein Flussdiagramm, das uns zeigt, wie der Ablauf der Sketch-Ausführung in bestimmte Bahnen gelenkt wird, so dass es sich nicht mehr um einen linearen Verlauf handelt. Der Sketch steht beim Erreichen einer Kontrollstruktur an einem Scheideweg und muss sehen, wie es weitergehen soll. Als Entscheidungsgrundlage dient ihm eine Bedingung, die es zu bewerten gilt.

Die if-Anweisung

Programmtechnisch nutzen wir die if-Anweisung‌. Es handelt sich um eine Wenn-dann-Entscheidung‌.


Abb. 3: Das Flussdiagramm einer if-Kontrollstruktur

Wurde die Bedingung als wahr erkannt, folgt die Ausführung einer oder auch mehrerer Anweisungen. Hier wieder ein kurzes Beispiel:

if(tasterStatus == HIGH) digitalWrite(ledPin, HIGH);

Wenn mehrere Befehle in einer if-Anweisung ausgeführt werden sollen, musst du einen Befehlsblock mit den geschweiften Klammerpaaren bilden. Er wird dann als komplette Befehlseinheit ausgeführt:

if(tasterStatus == HIGH) { digitalWrite(ledPin, HIGH); Serial.println("HIGH-Level erreicht."); }

Die if-else-Anweisung

Es gibt noch eine erweiterte Form der if-Kontrollstruktur. Es handelt sich dabei um eine Wenn-dann-sonst-Entscheidung‌, die sich aus einer if-else-Anweisung ergibt. Das entsprechende Flussdiagramm sieht wie folgt aus:


Abb. 4: Das Flussdiagramm einer if-else-Kontrollstruktur

Das folgende Codebeispiel zeigt dir die Syntax der if-else-Anweisung:

if(tasterStatus == HIGH) digitalWrite(ledPin, HIGH); else digitalWrite(ledPin, LOW);

Die switch-case-Anweisung

Sollen mehrere Abfragen hintereinander erfolgen, kann das natürlich über ein Konstrukt mehrfacher if-then-else-Anweisungen erfolgen. Es gibt jedoch noch eine einfachere Variante, die vereinfacht zu schreiben und damit auch besser lesbar ist, die sogenannte switch-case-Anweisung (Abbildung 5).

Die Syntax dazu sieht wie folgt aus:

switch(var) { case label1: // Anweisung(en) break; case label2: // Anweisung(en) break; default: // Anweisung(en) break; }


Abb. 5: Das Flussdiagramm einer switch-case-Kontrollstruktur

Folgende Parameter sind hierbei erlaubt:

 var: eine Variable mit den erlaubten Datentypen int und char.

 label1, label2: Konstanten mit den erlaubten Datentypen int und char.

Du solltest unbedingt darauf achten, dass nach der Ausführung einer Anweisung der Schleifendurchlauf mit break unterbrochen wird, da sonst die folgenden Sprungmarken ebenfalls geprüft und die dort aufgeführten Anweisungen vielleicht ausgeführt werden. Die letzte break-Anweisung Quelltext nach der default-Anweisung ist nicht zwingend erforderlich.

Operatoren‌

Natürlich gibt es bei den Kontrollstrukturen und den zu testenden Bedingungen nicht nur die Prüfung auf Gleichheit. Die folgende Tabelle zeigt alle C++-Vergleichsoperatoren:


Tabelle 2: Vergleichsoperatoren‌
Vergleichsoperator Bedeutung Beispiel
== ist gleich if(a==b) {...}
<= ist kleiner gleich if(a<=b) {...}
>= ist größer gleich if(a>=b) {...}
< ist kleiner if(<b) {...}
> ist größer if(a>b) {...}
!= ist ungleich if(a!=b) {...}

Zudem gibt es noch Verknüpfungen über sogenannte logische Operatoren, die mehrere zu testende Bedingungen zulassen:


Tabelle 3: Logische Operationen‌
Logische Operation Funktion Bedeutung Beispiel
! NICHT (NOT) Umkehrung des logischen Zustandes. Ergebnis ist wahr (true), wenn Operand falsch (false) ist. if(!a) {...}
&& UND (AND) Ergebnis ist wahr (true), wenn beide Operanden wahr sind. if((a<=b)&&(c==5)) {...}
|| ODER (OR) Ergebnis ist wahr (true), wenn einer der beiden Operanden wahr ist. if((a>=b)||(c<=6)) {...}

Was sind Schleifen?‌

In einem Sketch kann zur Berechnung von Daten das Ausführen vieler einzelner wiederkehrender Schritte erforderlich sein. Wenn es sich bei diesen Schritten beispielsweise immer um gleichartige Befehlsausführungen handelt, ist es weder sinnvoll noch praktikabel, diese Befehle in großer Anzahl untereinander zu schreiben und sequentiell, also hintereinander, ausführen zu lassen. Aus diesem Grund wurde in der Datenverarbeitung ein spezielles programmtechnisches Konstrukt geschaffen, das die Aufgabe hat, ein Programmstück, bestehend aus einem oder mehreren Befehlen, mehrfach hintereinander auszuführen. Wir nennen dies eine Schleife.

Was ist eine Schleife?


Eine Schleife – auch Loop genannt – ist eine Kontrollstruktur in einer Programmiersprache. Sie wiederholt einen definierten Anweisungsblock – den sogenannten Schleifenkörper –, solange die Schleifenbedingung logisch wahr ist. Schleifen, deren Schleifenbedingung immer wahr ist oder in denen keine Schleifenbedingung definiert wurde, sind sogenannte Endlosschleifen.

Schauen wir uns an, wie eine Schleife grundsätzlich aufgebaut ist. Es gibt zwei unterschiedliche Schleifenvarianten:

 kopfgesteuerte Schleifen und

 fußgesteuerte Schleifen.

Beiden Varianten ist gemeinsam, dass sie eine Instanz‌ besitzen, die die Kontrolle darüber übernimmt, ob und wie oft die Schleife durchlaufen werden muss. Dieser Instanz ist ein einzelner Befehl oder ein ganzer Befehlsblock (Schleifenkörper‌ genannt) angegliedert, der durch die Instanz gesteuert und abgearbeitet wird.

Kopfgesteuerte Schleifen

Bei kopfgesteuerten Schleifen‌ befindet sich die Kontrollinstanz im Schleifenkopf, der sich – wie der Name vermuten lässt – oben befindet. Das bedeutet wiederum, dass der Eintritt in den ersten Schleifendurchlauf von der Auswertung der Bedingung abhängt und gegebenenfalls nicht stattfindet. Die Schleife wird also möglicherweise überhaupt nicht ausgeführt.


Abb. 6: Grundsätzlicher Aufbau einer kopfgesteuerten Schleife

Die Verwendung des Plurals kurz vorher in der entsprechenden Überschrift ist schon ein Hinweis darauf, dass es verschiedene Typen von Kopfschleifen gibt, die in unterschiedlichen Situationen zum Einsatz kommen.

for-Schleife‌

Die for-Schleife kommt immer dann zum Einsatz, wenn vor Beginn des Schleifenaufrufs eindeutig feststeht, wie oft die Schleife durchlaufen werden soll. Werfen wir dazu einen Blick auf das Flussdiagramm, das der grafischen Wiedergabe des Programmflusses dient:


Abb. 7: Das Flussdiagramm einer for-Schleife

In der Schleife kommt eine Variable mit der Bezeichnung Laufvariable‌ zum Einsatz. Sie wird in der Bedingung einer Bewertung unterzogen, die darüber entscheidet, ob und wie oft die Schleife durchlaufen wird. Der Wert dieser Variablen wird in der Regel im Schleifenkopf bei jedem neuen Durchlauf modifiziert, so dass die Abbruchbedingung irgendwann erreicht sein sollte, wenn du keinen Denkfehler gemacht hast. Hier ein kurzes Beispiel, das später in deinem Projekt verwendet wird:

for(int i = 0; i < 7; i++) pinMode(ledPin[i], OUTPUT);

while-Schleife‌

Die while-Schleife wird dann verwendet, wenn sich erst zur Laufzeit der Schleife ergeben soll, ob und wie oft sie zu durchlaufen ist. Wenn während des Schleifendurchlaufs zum Beispiel ein Eingang des Mikrocontrollers kontinuierlich abgefragt und überwacht wird und bei einem bestimmten Wert eine Aktion durchgeführt werden soll, muss dieser Schleifentyp verwendet werden. Wir wollen schauen, wie das entsprechende Flussdiagramm aussieht:


Abb. 8: Das Flussdiagramm einer while-Schleife

Die Abbruchbedingung‌ befindet sich bei dieser Schleife ebenfalls im Kopf. Es wird dort jedoch keine Modifikation der in der Bedingung angeführten Variablen vorgenommen. Sie muss im Schleifenkörper erfolgen. Wenn dies vergessen wird, haben wir es mit einer Endlosschleife‌ zu tun, aus der es kein Entrinnen gibt, solange der Sketch läuft. Auch hierzu ein kurzes Beispiel für eine while-Schleife:

while(i > 1) { // Kontrollinstanz Serial.println(i); i = i - 1; }

Wenn du in der Kontrollinstanz mit Werten und Variablen arbeitest, die beispielsweise vom Datentyp float sind, ist es aufgrund von Ungenauigkeiten von float sehr riskant, genau auf ein bestimmtes Ergebnis hin abzufragen. Das kann bedeuten, dass die Abbruchbedingung niemals erfüllt wird und der Sketch in einer Endlosschleife sein Dasein fristet. Verwende statt des Operators == zur Abfrage auf Gleichheit lieber die Operatoren <= oder >=.

₺1.417,76

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
1504 s. 1241 illüstrasyon
ISBN:
9783946496298
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi: