Teil 3
In diesem Beitrag setzen wir die Grundlagen der Programmierung in C fort. Mit Funktionen, Schleifen und Arrays kann man vieles einfacher umsetzen. Auf den ersten Blick eventuell schwer zu verstehen. Wir werden versuchen, dieses Buch mit sieben Siegeln zu öffnen.Funktionen
Der Name round wird hier nicht in Klammern gesetzt, wie beim Casten mit (int)(). Wie bereits erwähnt handelt es sich um eine Funktion. Mit setup() und loop() verwenden wir bereits Funktionen. Man kann sie auch als Module verstehen, die bestimmte Aufgaben übernehmen. Ganz abstrakt ist es jemand, dem ich verschiedene Zutaten gebe und er mir daraus ein Produkt erzeugt. Dabei sind Art und Anzahl der Zutaten fest vorgeschrieben. Es gibt nur ein einziges Produkt und auch deren Art ist klar definiert. Ich habe ein Fach in einem Regal mit einer fest vorgeschriebenen Größe. Das ist noch leer. Ich bin dann der, der den Jemand ruft und ihm die Zutaten gibt. Er erwartet von jeder Zutat eine bestimmte Sorte in einer festgelegten Reihenfolge. Gebe ich ihm die Zutaten so, wie er sie verlangt, kann er mir daraus mein Produkt erzeugen. Die muss ich aber in einer Kiste zurückbekommen, die genau in mein leeres Fach in meinem Regal passt.
So kann man sich den Austausch zwischen Aufrufer und der Funktion vorstellen. Der Vorteil ist, dass ich die Funktion immer wieder verwenden kann, ohne den Code dafür immer wieder neu zu schreiben. Außerdem kann ich die Funktionen auch in anderen Programmen verwenden, wenn ich sie dort einbinde. Das macht man zum Beispiel mit Bibliotheken. Dazu folgt später auch ein kurzer Abschnitt.
Wie kann ich die abstrakte Erklärung nun für den Arduino verwenden?
Nehmen wir unser Programm, das zwei Zahlen addiert. Wir lagern die Berechnung in eine Funktion aus. Wir nennen diese Funktion addition. Mein Regalfach ist so groß wie ein Integer. Also 16 Bit. Ich nenne das Fach ergebnis.
Also ist die Deklaration int ergebnis. Der Platz ist reserviert, es liegt aber noch nichts darin. Nun suche ich mir einen Experten, der besonders gut Ganzzahlen addieren kann. Den „rufe ich auf“. Er soll mir mein Ergebnis in mein Regalfach ergebnis legen. Er erwartet von mir zwei Ganzzahlen als Zutaten, die er berechnen wird. Die Zutaten werden Parameter genannt. Somit sieht das so aus:
ergebnis = addition(zahl1, zahl2);
Das ist mein Funktionsaufruf, der mir ein Ergebnis zurückgibt, das ich speichern werde. Es gibt auch Funktionen, die nichts zurückgeben. Dafür gibt es den Datentyp void. Meistens wird so eine Funktion für die Bildschirmausgabe verwendet oder für das Versenden von Daten an einen Empfänger (auch die Bildschirmausgabe ist technisch gesehen ein Empfänger).
Tipp für Fortgeschrittene: Man sieht, dass man nur ein Ergebnis zurückbekommen kann. Möchte man mehrere Ergebnisse zurückerhalten, muss man andere Lösungswege wählen. Dazu übergibt man Adressen auf bestimmte Speicherplätze an die Funktion. Die Funktion gibt dann selbst nichts zurück. Es werden dort direkt Ergebnisse in die Speicherplätze geschrieben. Wird die Funktion wieder verlassen, kann man auf die verschiedenen Speicherplätze mit den Ergebnissen zugreifen.
Auch Funktionen, die keine Parameter enthalten, sind möglich.
Nun muss im Programmcode der Ganzzahladditionsexperte beschrieben werden. Die Syntax (also Schreibweise) sieht vor, dass der Kopf der Funktion den zurückzugebenden Datentyp, den Namen und dann die Parameter enthält. Gefolgt von dem Rumpf, der durch geschweifte Klammern eingefasst wird. In mein Regalfach passt nur ein Integer, also int. Der Name ist addition und meine Parameter sind ebenfalls vom Typ int. Der Kopf meiner Funktion sieht demnach so aus:
int addition(int zahl1, int zahl2) { }Rückgabetyp int, Name ist addition und in den Klammern folgen die Parameter. Anschließend folgt der Rumpf. Die Namen der Parameter können hier an dieser Stelle neu vergeben werden. Die Variablen werden neu deklariert. Nach Beenden der Funktion stehen sie nicht mehr weiter zur Verfügung. Es sind quasi Kopien meiner Parameter aus dem Aufruf.
Tipp: Auch dafür muss Speicher zur Verfügung stehen. Ist das Kompilieren gerade so noch möglich, der Speicher aber schon so gut wie voll, kann es dann an dieser Stelle zu Fehlverhalten des Mikrocontrollers kommen. Es fehlt dann erst zur Laufzeit genügend Programmspeicher.
Ich kann nun in den Rumpf meine Berechnung einfügen und muss anschließend das Ergebnis zurückgeben. Für Zwischenberechnungen können hier lokale Variablen eingerichtet werden. Wie bereits vorher erwähnt, stehen diese später nicht mehr zur Verfügung.
Ruft man im Programm die Funktion auf, bevor sie dem Compiler bekannt gemacht wird, tritt ein Fehler auf. Die Bekanntmachung geschieht dadurch, dass die Funktion vor dem Aufruf in den Programmcode integriert wird. Es ist auch möglich, die sogenannte „Vorwärtsdeklaration“ zu verwenden. Damit wird nur der Kopf der Funktion deklariert. Die gesamte Funktion kann dann auch an das Ende des Programmcodes geschrieben werden.
Tipp: Die Arduino IDE kümmert sich selbständig um die Bekanntmachung von Funktionen. Eine eigenständige Vorwärtsdeklaration ist nicht notwendig.
Wir ändern also unseren Programmcode so wie beschrieben:
int ergebnis = 0; String satz1 = "Das Ergebnis lautet: "; int addition(int zahl1, int zahl2) { int zwischenergebnis = 0; zwischenergebnis = zahl1 + zahl2; return zwischenergebnis; } void setup() { // put your setup code here, to run once: Serial.begin(115200); ergebnis = addition(3, 4); Serial.print(satz1); Serial.println(ergebnis); } void loop() { // put your main code here, to run repeatedly: }
Wir deklarieren uns zuerst die Variablen für das Ergebnis und unseren Satz, den wir ausgeben möchten. Anschließend folgt die Funktion für die Addition. Wir deklarieren dort eine Variable für das Zwischenergebnis, führen die Addition der übergebenen Parameter aus und geben mit return das Ergebnis zurück an den Aufrufer, der den Wert in unsere Variable ergebnis schreibt. Danach werden der Satz und das Ergebnis im Monitor ausgegeben. Das Ergebnis ist 7.
Wir können den Quellcode nun etwas optimieren. Wir brauchen keine Zwischenvariable in der Additionsfunktion. Außerdem können wir den Aufruf für die Funktion direkt in die Ausgabe auf dem Monitor schreiben, da wir keine weiteren Berechnungen mit unserem Ergebnis durchführen möchten.
Wir sparen Codezeilen und etwas Speicher. 3132 Bytes Programmspeicher und 228 Bytes dynamischer Speicher im Gegensatz zu 3078 Bytes Programmspeicher und 226 Bytes dynamischer Speicher. Das scheint auf den ersten Blick nicht viel. Bei umfangreicheren Programmen muss man dringend darauf achten.
Der aktualisierte Quellcode lautet wie folgt:
String satz1 = "Das Ergebnis lautet: "; int addition(int zahl1, int zahl2) { return zahl1 + zahl2; } void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.print(satz1); Serial.println(addition(3, 4)); } void loop() { // put your main code here, to run repeatedly: }
Die Additionsfunktion kann jederzeit genutzt werden. Auch in der loop()-Funktion.
Schleifen
Neben der Hauptschleife loop() des Arduinoprogramms kann man auch eigene Schleifen programmieren. Es gibt in C und C++ die for-Schleife, die do-while-Schleife und die while-Schleife. Mit for-Schleifen lassen sich einfach Zähler umsetzen. Zuerst wird eine Variable benötigt:
int i = 0;
An der gewünschten Stelle im Programmcode wird dann die Schleife eingesetzt:
for (i = 0; i < 10; i++) { //hier passiert irgendwas }
In den Klammern wird zuerst die Variable auf den Startwert (hier 0) gesetzt. Dann wird die Abbruchbedingung festgelegt. In diesem Fall steht hier: Schleife ausführen, solange i kleiner 10 ist. Das letzte ist dann der Befehl für das Inkrementieren bzw. Dekrementieren. Die Variable wird in jedem Schritt um 1 erhöht.
Tipp: i++ ist eine Kurzschreibweise. Die Ausführliche Schreibweise wäre i = i + 1; also addiere die Variable um 1 und überschreibe die alte Variable mit dem Ergebnis.
Tipp: Man kann später erneut eine Schleife mit der gleichen Variablen programmieren. Dabei ist darauf zu achten, dass man den Startwert erneut setzt. Am Ende der zuvor durchgeführten Schleife bleibt die Variable auf dem letzten Wert stehen (hier 10).
Für einen Mikrocontroller macht es Sinn, den Zähler z.B. für eine PWM zu verwenden (PulsWeitenModulation). Damit lassen sich unter anderem dimmbare LEDs umsetzen, aber auch Motoren können damit angesteuert werden.
Wir nutzen die Schleife für einen Test in unserem Programm. Wir verändern den Code so, dass von 1 bis 10 gezählt wird.
int i = 0; int addition(int zahl1, int zahl2) { return zahl1 + zahl2; } void setup() { // put your setup code here, to run once: Serial.begin(115200); for (i = 0; i < 10; i++) { Serial.println(addition(1, i)); } Serial.println(i); } void loop() { // put your main code here, to run repeatedly: }
Der String zu Beginn und im setup() wurde entfernt. Dafür wurde die Schleife eingefügt. Hier sieht man, dass man nun die Additionsfunktion unverändert nutzen kann.
Die Ausgabe auf dem Monitor sollte untereinander 1 bis 10 anzeigen. Außerdem eine weitere 10. Zur Verdeutlichung wurde hier eine weitere Zeile mit der Ausgabe des Wertes der Variablen i eingefügt. So kann man erkennen, dass der Wert auch nach der Schleife noch 10 beträgt.
Als Nächstes fügen wir in unser Programm eine while-Schleife ein. Hier muss man vor der Schleife die Variable i auf den Startwert setzen. In der Klammer steht dann nur die Abbruchbedingung. Das Inkrementieren geschieht dann innerhalb der Schleife, sowie die Ausgabe auf dem Monitor.
int i = 0; int addition(int zahl1, int zahl2) { return zahl1 + zahl2; } void setup() { // put your setup code here, to run once: Serial.begin(115200); for (i = 0; i < 10; i++) { Serial.println(addition(1, i)); } Serial.println(i); i = 0; while (i < 10) { Serial.println(addition(1, i)); i++; } } void loop() { // put your main code here, to run repeatedly: }
Die Funktion der Schleife ist hier die gleiche. Auch die Ausgabe auf dem Monitor ist identisch. Vor der Schleife setzen wir den Startwert auf 0, innerhalb der Klammern im Kopf der Schleife legen wir wieder die Abbruchbedingung fest. Die Schleife soll solange laufen, wie die Variable i kleiner 0 ist. Innerhalb der Schleife geben wir die Addition in jedem Schritt mit 1 und dem jeweiligen Wert von i auf dem Monitor aus. Anschließend wird i um 1 erhöht.
Tipp: Man könnte denken, dass die Abbruchbedingung „wenn i < 10“ bedeutet. Man sollte sich das Wort „solange“ einprägen, um Fehler zu vermeiden.
Wir ergänzen den Programmcode nun durch die dritte Variante, die do-while-Schleife. Hier steht die Abbruchbedingung am Ende der Schleife.
int i = 0; int addition(int zahl1, int zahl2) { return zahl1 + zahl2; } void setup() { // put your setup code here, to run once: Serial.begin(115200); for (i = 0; i < 10; i++) { Serial.println(addition(1, i)); } Serial.println(i); i = 0; while (i < 10) { Serial.println(addition(1, i)); i++; } i = 0; do { Serial.println(addition(1, i)); i++; } while (i < 10); } void loop() { // put your main code here, to run repeatedly: }
Auch hier setzen wir zuerst i wieder auf den Startwert, dann wird innerhalb der Schleife die Addition ausgegeben und die Variable hochgezählt. Die Schleife läuft, solange i kleiner 10 ist.
Arrays
Nutzt man mehrere Sensoren, kann man mit einer Schleife über die Anzahl der Sensoren „iterieren“. So nennt man das Durchzählen mittels Schleife. Man kann dafür Werte in ein Array eintragen. Das sind zusammenhängende Felder. So kann man z.B. 10 Integer-Variablen zusammenlegen und mit Schleifen abfragen oder Werte hineinspeichern.
Man müsste andernfalls 10 separate Variablen deklarieren und müsste für die Lese- und Schreibvorgänge jeweils 10 verschiedene Codezeilen mit den entsprechenden Befehlen schreiben.
Ein Array wird folgendermaßen deklariert und alle Felder in einem Rutsch mit 0 definiert:
int array[10] = {0};
Es sind 10 zusammenhängende 16 Bit große Felder, die auch direkt mit 0 initialisiert werden. Nun kann ich in einer Schleife auf schnellem Weg mit wenigen Codezeilen Werte auslesen oder hineinschreiben. Wir ergänzen unser Programm durch das Array und eine for-Schleife.
Tipp: Wir definieren eine maximale Anzahl an Feldern, die wir gleichzeitig als Abbruchbedingung in der Schleife einsetzen. So kann nicht über das Array hinaus gezählt werden, was zu Fehlverhalten führen würde. Das kann man mit const int i = 10 machen (der Compiler erwartet eine Konstante, daher muss der Datentyp const sein), oder eine Konstante mit dem Preprozessorbefehl #define MAX 10 definieren.
Tipp: Die Anzahl der Felder in dem Array ist hier 10. Die Nummerierung ist aber 0 bis 9. Das ist eine häufige Fehlerquelle, wenn man die Abbruchbedingung der Schleife festlegt.
int i = 0; const int MAX = 10; int mein_array[MAX] = {0}; int addition(int zahl1, int zahl2) { return zahl1 + zahl2; } void setup() { // put your setup code here, to run once: Serial.begin(115200); for (i = 0; i < MAX; i++) { mein_array[i] = addition(1, i); } for (i = 0; i < MAX; i++) { Serial.println(mein_array[i]); } } void loop() { // put your main code here, to run repeatedly: }
Es wurde ein Array mit 10 Feldern angelegt. Die erste Schleife füllt alle Felder mit dem jeweiligen Additionsergebnis. Die zweite Schleife dient der Ausgabe auf dem Monitor. Dort wird noch einmal gezeigt, dass diese Werte auch in das Array geschrieben wurden. Es sollten auch hier die Zahlen 1 bis 10 ausgegeben werden.
Soviel zu den Grundlagen in C bzw. C++, die man für einfache Projekte mit dem Arduino benötigt.
Weiterlesen:
Arduino IDE - Programmieren für Einsteiger - [Teil 1]
Arduino IDE - Programmieren für Einsteiger - [Teil 2]
Arduino IDE - Programmieren für Einsteiger - [Teil 4]
Arduino IDE - Programmieren für Einsteiger - [Teil 5]
6 commenti
Andreas Wolter
@Rüdiger: im Moment nich. Ich werde sie erstellen und dann die Downloads in den Beiträgen ergänzen.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Rüdiger
Hallo und Danke für die hervorragenden Erklärungen.
Vielleicht habe ich es übersehen : Kann man die Lektionen irgendwo als PDF herunterladen um sie offline als Unterstützung beim Programmieren zu nutzen?
Das wäre super
Hans-Jürgen Purps
Hallo, sehr gute Serie – habe alle drei Beiträge studiert und nun schon meine ersten Projekte verwirklicht. Dran bleiben……Bin Rentner und habe damit die schlimme Zeit (Corona-Krise) überstanden….
Bernd Albrecht
Unser französischer Freund fragt: Haben Sie eine Bibliothek auf Ihrer Site, in der Sie all diese Dokumentationen speichern können, die für Projekte nützlich sind, die auf Arduino oder ESP32 oder anderen basieren?
@ Yves G und alle Interessierten:
Bei fast allen unseren Produkten findet man nützliche Hinweise, ein eBook und Links zu weiteren Informationen in der Rubrik “Wichtige Downloads & Links”.
Didi
Super und verständlich erklärt. Ich bin Anfänger Rentner und mein Hobby Arduino begeistert. Beruf:Techniker
Yves G
Bravo et merci pour cet article. Avez vous une bibliothèque sur votre site pour stocker toute cette documentation qui nous sera utile en fonction des projets à base d’Arduino ou ASP32 ou autres ?
Bonne journée à l’équipe