ftDuino Bedienungsanleitung (PDF-Version)

4 Programmierung

Dieses Kapitel erklärt die Programmierung eigener Programme bzw. Sketches für den ftDuino. Wer zunächst keine eigenen Programme schreiben möchte und erst einmal die vorgefertigten Beispiele und Experimente ausführen möchte ohne deren Programmcode zu verstehen kann direkt mit Kapitel 6 weitermachen.

Der ftDuino wird mit einer ständig wachsenden Anzahl vorgefertigter Beispiel-Sketches ausgeliefert. Für viele Modelle und Versuche reicht es daher, wie in den vorhergehenden Kapiteln beschrieben diese Beispiele auf den ftDuino zu laden. Aber wie der Spaß beim Bauen mit fischertechnik gerade dort beginnt, wo man die vorgegebenen Pfade der Bauanleitungen hinter sich lässt, so besteht auch beim ftDuino der eigentliche Nutzen darin, dass man ihn mit eigenen Sketches programmieren kann. Zusammen mit einem selbst entworfenen Modell ergeben sich so beeindruckende Möglichkeiten. Und ganz nebenbei lernt man nicht nur die mechanische Grundlagen kennen, sondern erhält zusätzlich einen realistischen Einblick in die Welt der Mikrocontroller-Programmierung.

Dieses Kapitel soll einen ersten Einblick in die Programmierung des ftDuino geben. Es werden die wesentlichen Sprachkonstrukte der auf dem ftDuino verwendeten Programmiersprache soweit erklärt, wie sie zum Verständnis der Programme der nachfolgenden Kapitel benötigt werden. Die Beschreibung beschränkt sich bewusst auf das allernötigste. Trotzdem sind Ende dieses recht kurzen Kapitels alle nötigen Grundlagen für erste eigene Sketches vorhanden.

4.1 Textbasierte Programmierung

Im Gegensatz zu den meisten Programmierumgebungen aus der Welt der Konstruktionsspielzeuge wird der Arduino nicht grafisch sondern textbasiert programmiert. Während die grafischen Umgebungen wie fischertechniks RoboPro oder Legos EV3-Programmier-App auf leichte Erlernbarkeit ausgelegt sind steht in der Arduino-IDE der Praxisbezug im Vordergrund. Arduino zu programmieren bedeutet auf die gleiche Art Programme zu schreiben, wie es auch professionelle Entwickler kommerzieller Produkte machen.

(a) grafisch
(b) textbasiert
Abbildung 4.1: Blinken einer Lampe an Ausgang O1

Tatsächlich hat die textuelle Darstellung einige signifikante Vorteile. Vor allem bei großen Projekten ist eine ansprechend formatierte textuelle Darstellung daher wesentlich verständlicher als eine grafische.

(a) in der Arduino-IDE
(b) auf Github
Abbildung 4.2: Darstellung des gleichen Arduino-Sketches

Ein textbasiertes Programm besteht aus unformatiertem Text. Einige Programmierumgebungen wie die Arduino-IDE lassen dennoch farbige Hervorhebungen oder unterschiedliche Schriftgrößen zu oder zeigen im Programmtext Zeilennummern an. Im Gegensatz zu Texten aus einer Textverarbeitung sind diese Formatierungen aber nicht Teil des erstellten Programms. Stattdessen sind sie Teil der Programmierumgebung selbst und die Formatierung geht z.B. bei der Weitergabe des Programmcodes verloren. Der gleiche Programmcode kann daher in einer anderen Umgebung vollkommen anders aussehen. Für die Funktion des Programms ist das bedeutungslos. Es kommt nicht darauf an wie der Programmtext dargestellt wird. Für die eigentliche Programmfunktion ist ausschließlich der Inhalt des dargestellten Texts verantwortlich, nicht sein Aussehen.

Wie in Abbildung 4.2 zu sehen unterscheidet sich die Darstellung des identischen Programmcodes zwischen der Arduino-IDE und z.B. dem Web-Dienst Github deutlich.

Auch dieses Handbuch verwendet eine eigene Darstellung und stellt zum Beispiel manchmal Zeilennummern dar. Diese Zeilennummern dienen nur der leichteren Bezugnahme auf einzelne Zeilen und dürfen nicht als Teil des Programms explizit eingegeben werden:


      1  // BlinkO1.ino
      2  //
      3  // Blinken einer Lampe an Ausgang O1
      4  //
      5  // (c) 2018 by Till Harbaum <till@harbaum.org>
      6  
      7  #include <FtduinoSimple.h>
      8  
      9  void setup() {
     10    // LED initialisieren
     11    pinMode(LED_BUILTIN, OUTPUT);
     12  }
     13  
     14  void loop() {
     15    // schalte die interne LED und den Ausgang O1 ein (HIGH bzw. HI)
     16    digitalWrite(LED_BUILTIN, HIGH);
     17    ftduino.output_set(Ftduino::O1, Ftduino::HI);
     18  
     19    delay(1000);                       // warte 1000 Millisekunden (eine Sekunde)
     20  
     21    // schalte die interne LED und den Ausgang O1 aus (LOW bzw. LO)
     22    digitalWrite(LED_BUILTIN, LOW);
     23    ftduino.output_set(Ftduino::O1, Ftduino::LO);
     24    
     25    delay(1000);                       // warte eine Sekunde
     26  }
Wer auch in der Arduino-IDE Zeilennummern dargestellt bekommen möchte kann sie in den Voreinstellungen der Arduino-IDE wie in Abbildung 4.3 dargestellt aktivieren.
Abbildung 4.3: Aktivierung von Zeilennummern in der Arduino-IDE

4.2 Die Programmiersprache C++

Arduinos und der ftDuino werden in der Programmiersprache C++, genauer dem Standard C++11 programmiert. Ein Arduino-Sketch unterscheidet sich in einigen wenigen fundamentalen Dingen von klassischen C++-Programmen, davon abgesehen entspricht die Programmierung dem Standard. Die Programmiersprache C++ ist in der professionellen Softwareentwicklung weit verbreitet. Große Teile aller gängigen Betriebssysteme wie Windows, Linux oder MacOS sind in C++ bzw. einer eng mit C++ verwandten Programmiersprache geschrieben. Die Arduino-Programmierung liefert dadurch einen realistischen Einblick in den professionellen Bereich.

Die Sketches genannten C++-Programme der Arduino-Welt werden durch ein Compiler genanntes Programm in sogenannten Maschinen- oder Binärcode übersetzt, der vom Mikrocontroller des Arduino bzw. ftDuino verstanden wird. Dieser Binärcode kann dann auf den ftDuino per Download übertragen werden.

Der Compiler, der von der Arduino-IDE verwendet wird ist der sogenannte GCC1 . Dieser Compiler findet auch in der Industrie Verwendung und wird unter anderem zur Übersetzung des Linux-Kernels verwendet, wie er auf jedem Android-Smartphone zum Einsatz kommt. Es handelt sich bei der Arduino-IDE und deren intern verwendeten Werkzeugen also keinesfalls um reine Hobby-Technik. Unter der anfängertauglichen Oberfläche verbergen sich im Gegenteil leistungsfähige und professionelle Komponenten.

4.3 Grundlagen

Ein Arduino-Sketch besteht aus einer textuellen Beschreibung. Der minimale Text, der einen gültigen Sketch bildet sieht folgendermaßen aus:


     void setup() {
     }
     
     void loop() {
     }
Um diesen Sketch einzugeben öffnet man zunächst die Arduino-IDE und wählt dann im Menü DateiNeu. Es öffnet sich ein neues Fenster, das genau die vorher abgebildeten Zeilen bereits enthält. Zusätzlich sind noch zwei mit // beginnende Zeile vorhanden. Diese können entfernt werden, sodass der Text am Ende exakt dem obigen Beispiel entspricht.
(a) Übersetzung
(b) Download
Abbildung 4.4: Schaltflächen der Arduino-IDE

Dieser minimale Sketch lässt sich in der Arduino-IDE erfolgreich übersetzen und auf den ftDuino laden. Dazu gibt es in der Arduino-IDE die beiden in Abbildung 4.4 dargestellten Schaltflächen. Eine Fläche startet die Übersetzung in Maschinencode. Diese Funktion benötigt keinen angeschlossenen ftDuino und mit ihr lässt sich schnell feststellen, ob ein Sketch keine fundamentalen Fehler enthält, die bereits die Übersetzung verhindern. Die zweite Schaltfläche startet den Download auf den ftDuino. Sollte der Sketch noch nicht übersetzt sein, so wird bei Klick auf die Download-Schaltfläche vorher noch automatisch die Übersetzung gestartet. Nur wenn diese Übersetzung erfolgreich ist wird der Download begonnen.

Hat man den Sketch per DateiNeu frisch angelegt, so wird einen die Arduino-IDE nun ggf. fragen, ob man den Sketch speichern möchte. Wenn man dem Speichern zustimmt kann man an dem Sketch später jederzeit weiter arbeiten.

Nach dem erfolgreichen Download wird der ftDuino keine sichtbare Reaktion zeigen. Der Sketch besteht aus zwei sogenannten Funktionen, eine mit dem Namen setup() und eine mit dem Namen loop(). Diese Funktionen bilden das Skelett eines jeden Arduino-Sketches. Sie enthalten jeweils in ein Paar geschweifter Klammern { und } eingebettete Anweisungen, die die eigentliche Funktionalität des Sketches beschreiben. Aufeinanderfolgende Anweisungen werden dabei per Semikolon (;) getrennt.

Befinden sich aber wie in diesem einfachen Beispiel überhaupt keine Anweisungen zwischen den Klammern, so löst der Sketch folgerichtig auch keine erkennbare Reaktion auf dem ftDuino aus.

4.3.1 Kommentare

Die beiden zunächst entfernten mit // beginnenden Zeilen fügen dem Sketch ebenfalls keine Funktionalität zu. Um das zu überprüfen kann man noch einmal DateiNeu auswählen und den Sketch diesmal unverändert lassen:


     void setup() {
       // put your setup code here, to run once:
     
     }
     
     void loop() {
       // put your main code here, to run repeatedly:
     
     }
Ein erneuter Klick auf den Download-Button wird auch diesen Sketch übersetzen und auf den ftDuino laden. Wieder erfolgt am ftDuino keine erkennbare Funktion. Das liegt daran, dass die beiden zusätzlichen Zeilen reine Kommentarzeilen sind. Sie sind dazu gedacht, einem menschlichen Leser zusätzliche Erklärungen zu geben. Für die Übersetzung in Maschinencode sind diese Zeilen bedeutungslos. Alles, was in einer Zeile hinter den doppelten Schrägstrichen // steht wird bei der Erzeugung des Maschinencodes ignoriert.

Zusätzlich kann man Kommentare in /* und */ einschließen. Die Erzeugung von Maschinencode beginnt erst wieder hinter dem schließenden Element */. Außerdem dürfen Kommentare dieser Art über mehrere Zeilen gehen.


     void setup() {
       /* mehrzeilige Kommentare
          sind auch möglich */
     
     }
Einen Hinweis auf Kommentare gibt in der Arduino-IDE die Farbgebung. Kommentare sind immer hellgrau dargestellt und damit leicht vom eigentlichen Programmcode zu unterscheiden.

4.3.2 Fehlermeldungen

Als nächstes sollen zwei Programmanweisungen zwischen den geschweiften Klammern der setup()-Funktion eingefügt werden:


      1  void setup() {
      2    pinnMode(LED_BUILTIN, OUTPUT);
      3    digitalWrite(LED_BUILTIN, HIGH);
      4  }
      5  
      6  void loop() {
      7  }
Was diese beiden kryptischen Zeilen genau bedeuten wird später erklärt werden. Zunächst soll der Sketch lediglich wieder übersetzt und auf den ftDuino geladen werden. Ist das Programm fehlerfrei übersetzbar, so wird es erneut auf den ftDuino übertragen.

Leider ist es in diesem Fall aber nicht fehlerfrei. Wurde das Programm exakt wie abgebildet eingegeben, so sollte die Übersetzung des Programms mit der in Abbildung 4.5 zu sehenden Fehlermeldung abbrechen.

Abbildung 4.5: Anzeige eines Übersetzungsfehlers

im oberen Teil des Bildschirms wird die fehlerhafte Codezeile hervorgehoben. Im unteren Teil des Fensters sind die Ausgaben des GCC-Compilers zu sehen. Die eigentliche Fehlermeldung lautet 'pinnMode' was not declared in this scope.

Damit teilt der Compiler mit, dass er mit dem Begriff pinnMode nichts anfangen kann. Schon bei der Eingabe des Sketches gab es einen Hinweis auf dieses Problem, denn die Anweisung pinnMode wurde bei der Eingabe in der IDE lediglich schwarz dargestellt während z.B. in der zweiten Zeile das Wort digitalWrite orange hervorgehoben wurde. Die Arduino-IDE färbt die meisten ihr bekannten Anweisungen ein und ein nicht-gefärbter Teil kann einen Hinweis auf einen Fehler sein.

Tatsächlich hat sich hier ein Fehler eingeschlichen und statt pinnMode hätte es pinMode heißen müssen. Ändert man das Wort entsprechend, so färbt die IDE es erwartungsgemäß orange ein, die Übersetzung gelingt und ein Download ist möglich. Der korrigierte Sketch sollte nun wie folgt aussehen.


      1  void setup() {
      2    pinMode(LED_BUILTIN, OUTPUT);
      3    digitalWrite(LED_BUILTIN, HIGH);
      4  }
      5  
      6  void loop() {
      7  }
Nach dem Download wird die rote Leuchtdiode im ftDuino leuchten, denn genau das bewirken die zwei kryptischen Zeilen, die im folgenden Abschnitt genauer erklärt werden.

4.3.3 Funktionen

Das im Rahmen der Arduino-Programmierung wichtigste C++-Sprachelement sind die sogenannten Funktionen. Mit ihnen lassen sich bereits sinnvolle einfache Programm-Sketches schreiben.

Funktionsdefinitionen fassen Anweisungen zusammen. Das folgende Programmfragment zeigt die Definition einer Funktion namens name, die in ihrem Funktionskörper die beiden Anweisungen anweisung1 und anweisung2 enthält. Anweisungen werden durch das Semikolon ; voneinander getrennt.


     void name() {
       anweisung1;
       anweisung2;
     }
Eine Funktion wird in der Regel genutzt, um alle für eine komplexe Aufgabe benötigten Anweisungen zusammenzufassen und einen passenden Namen zu geben. Die Anweisungen im Körper einer Funktion werden nacheinander von oben nach unten ausgeführt. In diesem Fall wird also erst anweisung1 ausgeführt und dann anweisung2.

Da die Ausführung der einzelnen Anweisungen je nach Komplexität nur wenige Mikrosekunden dauert entsteht oft der Eindruck, als würden Dinge gleichzeitig passieren. Tatsächlich werden alle Anweisungen aber der Reihe nach ausgeführt. Dementsprechend können auch widersprüchliche Anweisungen konfliktfrei untereinander stehen und beispielsweise eine Lampe ein- und unverzüglich danach wieder ausgeschaltet werden. Diese Ereignisse passieren so kurz nacheinander, dass der Anwender nicht erkennen kann, dass z.B. eine Leuchtdiode für wenige Mikrosekunden eingeschaltet war.

Die Ausführung der Anweisungen kann direkt etwas bewirken und z.B. eine Leuchtdiode am ftDuino aufleuchten lassen. Eine Funktion, die alle Anweisungen zum Einschalten der Leuchtdiode zusammenfasst könnte z.B. wie folgt aussehen.


     void SchalteLeuchtdiodeEin() {
       pinMode(LED_BUILTIN, OUTPUT);     // schalte Pin auf Ausgang
       digitalWrite(LED_BUILTIN, HIGH);  // setze Ausgangspin auf 'high'
     }
Eine Funktion in der Sprache C++ ist sehr nah an mathematische Funktionen angelehnt und kann wie diese ein Ergebnis liefern. Ob und was für ein Ergebnis die Funktion zurück liefert steht vor dem Funktionsnamen. Im vorliegenden Fall soll kein Ergebnis geliefert werden, daher steht vor dem Funktionsnamen void, englisch für ``nichts''. Außerdem kann eine Funktion einen oder mehrere zu verarbeitende Eingabewerte erhalten, die zwischen den runden Klammern (()) angegeben werden. Die vorliegende Funktion benötigt keine Eingabewerte, die Klammern bleiben also leer.
Abbildung 4.6: Funktionsdefinition

Die Definition einer Funktion bewirkt, dass der Compiler die entsprechenden Anweisungen übersetzt und die nötigen Maschinenbefehle hintereinander im Maschinencode ablegt. Damit ist aber noch nichts darüber gesagt, wann diese Anweisungen tatsächlich ausgeführt werden.

Ausgeführt werden die Anweisungen der Funktion, sobald die Funktion aufgerufen wird. Dazu muss lediglich der Funktionsname mit den Parametern als Anweisung eingegeben werden. Da die Funktion SchalteLeuchtdiodeEin keine Parameter erwartet bleibt der Bereich zwischen den runden Klammern beim Aufruf leer. Der Funktionsaufruf muss dabei selbst wieder im Funktionskörper einer Funktionsdefinition stehen.


     void setup() {
       SchalteLeuchtdiodeEin();
     }
Damit wird klar, dass die Anweisungen pinMode(LED_BUILTIN, OUTPUT); und digitalWrite(LED_BUILTIN, HIGH); ebenfalls Funktionsaufrufe waren. Beide Funktionen bekamen jeweils zwei durch Kommata getrennte Parameter mitgegeben.

4.3.4 Die Funktionen setup() und loop()

https://www.arduino.cc/reference/en/language/structure/sketch/setup/
https://www.arduino.cc/reference/en/language/structure/sketch/loop/

Wenn Funktionen selbst immer nur aus anderen Funktionen aufgerufen werden dürfen ergibt sich ein Henne-Ei-Problem bei der Frage, von wo aus denn der erste Funktionsaufruf erfolgt.

Abbildung 4.7: Ablauf eines Sketches

An dieser Stelle kommen die beiden Funktionsdefinitionen setup() und loop() ins Spiel, über die jeder Sketch mindestens verfügen muss. Fehlt eine oder beide Definitionen, so bricht die Arduino-IDE die Übersetzung mit einer Fehlermeldung ab.

Beiden Funktionen müssen nicht explizit aufgerufen werden. Stattdessen wird ihr Aufruf von der Arduino-IDE während der Übersetzung des Sketches automatisch in den erzeugte Maschinencode eingefügt. Die Funktion setup() wird dabei einmal bei Sketch-Start aufgerufen und die Funktion loop() wird in Folge immer wieder aufgerufen wie in Abbildung 4.7 dargestellt.

Mit Sketch-Start ist im Falle des ftDuino eine von drei Situationen gemeint:

  1. Direkt nach dem Download eines übersetzten Sketches wird dessen Code gestartet.
  2. Nach dem Anlegen der Stromversorgung an den ftDuino wird ein vorher per Download installierter Sketch-Code gestartet.
  3. Wurde der Bootloader des ftDuino durch Druck auf den Reset-Taster gestartet, so bleibt dieser für acht Sekunden aktiviert. Wird der Bootloader in dieser Zeit nicht vom PC aus angesprochen, so beendet sich der Bootloader und der zuletzt heruntergeladene Sketch-Code wird stattdessen gestartet.

Hinweis für erfahrene Nutzer

Wer schon einmal mit C++ auf einem PC oder ähnlich zu tun hatte wird an dieser Stelle ggf. überrascht sein. In jenem Fall gab es die speziellen Funktionen setup() und loop() nicht. Stattdessen gab es eine Funktion namens main(), die bei Programmstart automatisch aufgerufen wurde.

Die Entwickler der Arduino-IDE haben sich entschieden, an dieser Stelle vom üblichen Standard abzuweichen, da das Konzept der main()-Funktion eher für nur zeitweilig auf einem komplexen Computer laufende Programme entwickelt wurde und sich nur bedingt in die Welt der Hardware-Programmierung einfügt.

Bibliotheksfunktionen

Wie im Falle der name()-Funktion geschehen kann man eigene Funktionen schreiben. Die Arduino-IDE bringt aber bereits eigene Funktionssammlungen mit. Die Sammlungen bestehen in erster Linie aus häufig benötigten universellen Funktionen. Sie sind dem System von vornherein mit Namen bekannt und man kann sie in eigenen Programmen einfach aufrufen, ohne sie selbst definieren zu müssen.

Die Funktionen pinMode() und digitalWrite() sind solche Bibliotheksfunktionen. Während die Sprache C++ universell ist und sich bei der Verwendungen auf einem PC oder einem Arduino nicht unterscheidet sind die Bibliotheksfunktionen in der Regel plattformspezifisch. Die Funktion pinMode() steht dem Programmierer nur auf dem Arduino zur Verfügung und bei der Entwicklung eines Programms für einen Windows-PC würde der Aufruf dieser Funktion zu einem Übersetzungsfehler führen.

Dies ist auch der Grund, warum sich viele C++-Beispiele und Tutorials aus dem Internet nicht direkt auf den Arduino übertragen lassen. Handelt es sich bei diesen Programmen nicht zufällig bereits um Arduino- sondern um PC-Programme, dann werden dort Funktions-Bibliotheken verwendet, die wiederum auf dem Arduino nicht vorhanden sind. Das liegt in erster Linie an der deutlich unterschiedlichen Hardware der verschiedenen Plattformen. Während ein PC-Programm in erster Linie Fenster auf dem Bildschirm öffnet und mit Benutzereingaben umgeht wird ein Arduino-Programm eher Hardware-Eingänge auswerten und Ausgänge schalten.

Hinweis für erfahrene Nutzer

Auch bei der Verwendung von Bibliotheken gibt es einen Unterschied zu üblicher C++-Programmierung auf PCs. Die Arduino-IDE macht die grundlegenden Arduino-spezifischen Bibliotheken automatisch im Sketch bekannt, so dass sich Funktionen wie pinMode() direkt nutzen lassen.

Gängige C++-Compiler binden von sich aus keine Bibliotheken ein. Sie müssen durch sogenannte #include-Anweisungen explizit bekannt gemacht werden. Dies ist auf dem Arduino zumindest für die meisten mitgelieferten Bibliotheken nicht nötig.

4.3.5 Beispiel

Damit sind die wesentlichen Grundlagen erklärt und das folgende Beispiel verständlich.


      1  /* Funktion zum Einschalten der LED */
      2  void SchalteLedEin() {
      3    pinMode(LED_BUILTIN, OUTPUT);
      4    digitalWrite(LED_BUILTIN, HIGH);
      5  }
      6  
      7  void setup() {
      8    SchalteLedEin();
      9  }
     10  
     11  void loop() {
     12  }
Bei Systemstart wird zunächst die setup()-Funktion aufgerufen. Die Funktion setup() enthält eine einzelne Anweisung in Form eines Aufrufs der Funktion SchalteLedEin. Diese Funktion ist wiederum in den Zeilen 2 bis 5 des Sketches definiert und besteht ihrerseits aus dem Aufruf zweier Bibliotheksfunktionen, die die eingebaute Leuchtdiode des ftDuino aufleuchten lassen.

Die loop()-Funktion wird nach Systemstart permanent immer wieder aufgerufen. Sie ist aber leer, über das Einschalten der Leuchtdiode direkt bei Systemstart zeigt der Sketch also keine weitergehende Funktion.

Der Kommentar in Zeile eins dient lediglich der Erklärung und hat keinen Einfluss auf die Übersetzung des Sketches oder seine Funktion.

4.4 Hilfreiche Bibliotheksfunktionen

Wie in den vorigen Abschnitten angedeutet bringt die Arduino-IDE einige Bibliotheken mit, die Funktionen zur Verwendung spezieller Eigenschaften des Arduino bieten. Zusätzlich bringt die ftDuino Installation eigene Bibliotheken mit, um auf die fischertechnik-Ein- und -Ausgänge des ftDuino zuzugreifen.

Im folgenden werden sieben der am häufigsten benötigten Funktionen beschrieben. Mit ihnen lassen sich bereits vielfältige Sketches schreiben.

Eine Beschreibung weiterer Funktionen der Arduino-IDE findet man online in der Arduino-Language-Reference unter https://www.arduino.cc/reference/en/#functions während weitere ftDuino-spezifische Funktionen im Kapitel 9 beschrieben sind.

4.4.1 pinMode(pin, mode)

https://www.arduino.cc/reference/en/language/functions/digital-io/pinMode/

Diese Funktion konfiguriert einen Pin des ATmega32u4-Mikrocontrollers als Ein- (mode = INPUT) oder Ausgang (mode = OUTPUT). Diese Funktion wird auf Arduinos sehr häufig verwendet, da dort direkt mit den Anschlüssen des Mikrocontrollers gearbeitet wird. Auf dem ftDuino befinden sich an den meisten Anschlüssen Zusatzschaltungen zur Verbindung mit fischertechnik-Komponenten, die die direkte Verwendung der pinMode()-Funktion unnötig machen. Stattdessen bringen die ftDuino-Bibliotheken alle Funktionen mit, um die Ein- und Ausgänge fischertechnik-konform zu bedienen.

Die wesentliche Ausnahme bildet der Anschluss der internen roten Leuchtdiode. Für pin muss in dem Fall LED_BUILTIN verwendet werden.


     // internen Anschlusspin der Leuchtdiode zum Ausgang erklären
     pinMode(LED_BUILTIN, OUTPUT);

4.4.2 digitalWrite(pin, value)

https://www.arduino.cc/reference/en/language/functions/digital-io/digitalwrite/

Die Funktion digitalWrite() steuert einen durch die Funktion pinMode() zum Ausgang erklärten Pin. Sie wird daher im ftDuino in der Regel für die Leuchtdiode verwendet und in seltenen Fällen zur Steuerung der beiden am I²C-Anschluss verfügbaren Signale.

Für pin gelten die gleichen Werte wie bei pinMode()-Funktion. Der Wert für value kann HIGH oder LOW sein, was den entsprechenden Pin ein- oder ausschaltet.


     // internen Anschlusspin der Leuchtdiode zum Ausgang erklären
     pinMode(LED_BUILTIN, OUTPUT);
     // Leuchtdiode einschalten
     digitalWrite(LED_BUILTIN, HIGH);

4.4.3 delay(ms)

https://www.arduino.cc/reference/en/language/functions/time/delay/

Die meisten Aufgaben erledigt auch ein so einfacher Mikrocontroller wie der ATmega32u4 schneller als für Menschen wahrnehmbar. Um Abläufe auf ein passendes Maß zu senken ist die delay()-Funktion hilfreich. Sie erwartet als Parameter eine Wartezeit in Millisekunden.

Wie in Abschnitt 4.3.4 beschrieben wird die Funktion setup() nur einmal aufgerufen, die Function loop() aber daraufhin immer wieder. Der folgende Sketch lässt die Leuchtdiode daher kontinuierlich blinken.


     void setup() {
       // internen Anschlusspin der Leuchtdiode zum Ausgang erklären
       pinMode(LED_BUILTIN, OUTPUT);
     }
     
     void loop() {
       // Leuchtdiode einschalten
       digitalWrite(LED_BUILTIN, HIGH);
       // 1 Sekunde warten
       delay(1000);
       // Leuchtdiode ausschalten
       digitalWrite(LED_BUILTIN, LOW);
       // 1 Sekunde warten
       delay(1000);
     }

4.4.4 Serial.begin(speed)

https://www.arduino.cc/reference/en/language/functions/communication/serial/begin/

Die Kommunikationsmöglichkeiten des ftDuino beschränken sich direkt am Gerät im Wesentlichen auf die fischertechnik Ein- und Ausgänge. Bei komplexeren Projekten wird es schwierig, nur am Verhalten des ftDuino den Programmfluss zu verfolgen. Ein einfacher Weg, aus dem Sketch heraus Informationen an den Benutzer auszugeben ist die Verwendung der Serial-Bibliothek.

Auf PC-Seite werden die Ausgaben dieser Bibliothek über den sogenannten seriellen Monitor angezeigt. Dessen Benutzung wurde in Abschnitt 3.3.1 genauer beschrieben. Die Funktion Serial.begin() bereitet den Sketch auf die Verwendung des seriellen Monitors vor. Sie sollte daher am Beginn des Sketches bzw. in der setup()-Funktion aufgerufen werden.

Die Funktion Serial.begin() erwartet einen Parameter namens speed. Dieser Wert ist für die USB-Verbindung des ftDuino belanglos und sollte z.B. auf einen Wert von 115200 gesetzt werden.


     void setup() {
       // Vorbereiten der seriellen Verbindung 
       Serial.begin(115200);
     }
     
     void loop() {
     }

4.4.5 Serial.print(val) und Serial.println(val)

https://www.arduino.cc/reference/en/language/functions/communication/serial/print/
https://www.arduino.cc/reference/en/language/functions/communication/serial/println/

Ist eine serielle Verbindung per Serial.begin() eingerichtet, so können die Funktionen Serial.print() und Serial.println() zur Ausgabe von Nachrichten an den seriellen Monitor verwendet werden. Der Unterschied zwischen Serial.print() und Serial.println() liegt darin, dass nach der Ausgabe per Serial.println() eine neue Ausgabezeile begonnen wird, während weitere Ausgaben nach Serial.print() direkt in der gleichen Zeile erfolgen. Die Funktion Serial.println() wird daher oft genutzt, um komplexere Ausgaben abzuschließen.

Für val können unter anderem Zeichenketten und Zahlen verwendet werden. Zeichenketten müssen in doppelte Anführungszeichen (``) eingeschlossen werden.


     void setup() {
       // Vorbereiten der seriellen Verbindung 
       Serial.begin(115200);
       // 2 Sekunden Verzögerung, um dem PC Zeit zu geben,
       // die Verbindung entgegen zu nehmen
       delay(2000);
       // ein paar Ausgaben
       Serial.print("Die Antwort lautet: ");
       Serial.println(42);
     }
     
     void loop() {
     }

4.4.6 ftduino.input_get(), ftduino.output_set() und ftduino.motor_set()

Ein Controller für ein fischertechnik-Modell muss natürlich Eingaben aus dem Modell empfangen und Reaktionen im Modell auslösen können. Die ftDuino-Bibliotheken sind in allem Detailgrad in Kapitel 9 beschrieben. In diesem Absatz wird daher nur das absolute Minimum beschrieben.

Es wurde in Abschnitt 4.3.4 erklärt, dass es eine Besonderheit ist, dass Bibliotheksfunktionen ohne weitere Vorbedingungen zur Verfügung stehen und im Sketch verwendet werden können. Dies trifft aber nur auf die Arduino-eigenen Bibliotheken zu wie sie in den bisherigen Beispielen verwendet wurden. Die ftDuino-Bibliotheken stehen nicht automatisch zur Verfügung sondern müssen am Anfang des Sketches mit einer #include-Anweisung bekannt gemacht werden.


     #include <FtduinoSimple.h>
       
     void setup() {
     }
     
     void loop() {
     }
Erst dann sind die Funktionen aus dieser Bibliothek im Sketch nutzbar.
Abbildung 4.8: Lampe an Ausgang O1

Die wichtigsten Funktionen dafür sind ftduino.input_get(port) zum Abfragen eines Eingangs und ftduino.output_set(port, mode) zum Schalten eines Ausgangs. Bei ftduino.input_get(port) sind für port Werte von Ftduino::I1 bis Ftduino::I8 erlaubt. Zum Schalten eines Ausgangs gibt es die Funktion ftduino.output_set(port, mode), wobei port Werte von Ftduino::O1 bis Ftduino::O8 annehmen darf und mode auf Ftduino::HI gesetzt wird, wenn der Ausgang eingeschaltet werden soll und auf Ftduino::OFF gesetzt wird, um den Ausgang auszuschalten.


     #include <FtduinoSimple.h>
       
     void setup() {
       // Eingang I1 lesen
       ftduino.input_get(Ftduino::I1);
       // Ausgang O1 einschalten
       ftduino.output_set(Ftduino::O1, Ftduino::HI);
     }
     
     void loop() {
     }
Dieser Sketch würde eine wie in Abbildung 4.8 angeschlossene Lampe zum Leuchten bringen. Achtung: Dazu muss der ftDuino mit 9 Volt versorgt sein.

Die Funktion ftduino.motor_set(port, mode) ist ftduino.output_set(port, mode) sehr ähnlich, nur dass hier ein Motorausgang M1 bis M4 geschaltet werden kann. Der Wert for mode kann dann Ftduino::LEFT, Ftduino::RIGHT oder Ftduino::OFF sein, je nachdem, ob sich der Motor linksherum, rechtsherum oder gar nicht drehen soll.


     #include <FtduinoSimple.h>
       
     void setup() {
       // Eingang I1 lesen
       ftduino.input_get(Ftduino::I1);
       // Motor an M1 linksdrehend einschalten
       ftduino.motor_set(Ftduino::M1, Ftduino::LEFT);
     }
     
     void loop() {
     }

4.5 Variablen

https://www.arduino.cc/reference/en/#variables

Oft besteht der Bedarf in einem Sketch etwas zu ``merken'' bzw. zu speichern. Wenn etwas beispielsweise mit einer bestimmten Häufigkeit passieren soll, dann muss währenddessen irgendwo vermerkt werden, wie oft es schon passiert ist bzw. wie oft es noch zu passieren hat. Ein anderes Beispiel sind einmalige Ereignisse wie ``der Benutzer hat einen Taster gedrückt''. Auch wenn dieses Ereignis vorübergeht und der Benutzer den Taster losgelassen hat soll die Aktion ggf. weitergeführt werden. Dazu muss irgendwo vermerkt werden, dass der Taster vor kurzem noch gedrückt war.

Für diesen Zweck gibt es sogenannte Variablen. Mit ihnen weist man den Compiler an, Platz im Speicher des ftDuino für etwas ``gemerktes'' zu reservieren. Während der Sketch im sogenannten Flash-Speicher des ftDuino abgelegt wird und daher auch über das Ausschalten des ftDuino hinaus erhalten bleibt. Wird der Speicherplatz für Variablen im flüchtigen RAM-Speicher des ftDuino abgelegt. Der gespeicherte Wert einer Variablen geht also beim Ausschalten des ftDuino verloren.

Eine Variable wird wie eine Funktion im Sketch definiert. Dazu erhält sie wie eine Funktion einen Namen. Außerdem muss angeben werden, welcher Art die in der Variablen abzulegenden Daten sind. Im folgenden Beispiel wird eine Variable namens variablenName für Daten vom Typ int angelegt.


     int variablenName;
     
     void setup() {
     }
     
     void loop() {
     }  

4.5.1 Datentyp int

https://www.arduino.cc/reference/en/language/variables/data-types/int/

Der Datentyp int ist der Standard-Datentyp. Beim ftDuino erlaubt dieser Datentyp das Ablegen ganzzahliger Werte im Bereich von -32768 bis +32767. Für die meisten Verwendungen ist das ausreichend.

Eine Variable wird genutzt, um Daten in ihr abzulegen und später wieder abzurufen. Das Ablegen von Werte geschieht, indem der Variablen ein Wert mit dem Gleichheitszeichen (=) zugewiesen wird. Die Ausdrücke auf der rechten Seite der Zuweisung können komplexe mathematische Funktionen beinhalten und sogar Funktionsaufrufe, um beispielsweise den Zustand eines Eingangs des ftDuino zu speichern.


     // eine einfache Zuweisung
     variablenName = 42;
     // ein komplexer Ausdruck
     variablenName = (4*8*8+38)/7;
     // Zustand des Eingangs I1
     variablenName = ftduino.input_get(Ftduino::I1);
Außerdem können in den Ausdrücken wiederum Variablen enthalten sein. Dabei kann auch die auf der linken Seite stehende Variable auch auf der rechten Seite auftauchen, was dem mathematischen Verständnis einer Gleichung etwas widerspricht.

     // Wert aus einer anderen Variable übernehmen
     variablenName = andererVariablenName;
     // Wert der Variablen verändern und wieder der Variablen zuweisen
     variablenName = variablenName + 1;
Man darf diese Art der Zuweisung nicht als mathematischen Vergleich lesen, auch wenn die Darstellung das nahelegt. Stattdessen wird zunächst der Ausdruck auf der rechten Seite berechnet und das Ergebnis wird danach der Variablen auf der linken Seite zugewiesen. Dieser zeitliche Ablauf führt dazu, dass eine Zuweisung wie

     variablenName = variablenName + 1;
einen Sinn ergibt.

Variablen können auch als Parameter bei Funktionsaufrufen verwendet werden.


     int variablenName;
     
     void setup() {
       Serial.begin(115200);
       variablenName = 192/4;
       
       Serial.print("Variableninhalt: ");
       Serial.println(variablenName);
     }
     
     void loop() { 
     }
Wichtig ist hier die korrekte Verwendung der Anführungszeichen (``). Ein Wort oder Text in Anführungszeichen steht für den Text selbst und wird nicht weiter interpretiert. Ein Wort ohne Anführungszeichen kann u.a. für eine Anweisung, einen Funktionsnamen oder einen Variablennamen stehen und der Compiler versucht diesem Wort eine Bedeutung zuzuweisen.

       // gib das Wort variablenName aus
       Serial.println("variablenName");
       // gib den Inhalt der Variablen namens variablenName aus
       Serial.println(variablenName);

4.6 Bedingungen

Bisher haben alle Beispiele aus Anweisungen in einer festen Reihenfolge bestanden. Alle Anweisung wurden immer auf die gleiche Weise ausgeführt und z.B. Leuchtdioden haben geblinkt oder Meldungen wurden ausgegeben. Es ist aber zu keiner Zeit etwas abhängig von irgendeinem anderen Ereignis passiert.

Es ist aber bei der Robotersteuerung und auch generell in der Programmierung oft nötig, dass ein Programm auf Ereignisse reagiert. Der C++-Mechanismus dafür sind Anweisungen, die auf Bedingungen reagieren können.

4.6.1 if-Anweisung

https://www.arduino.cc/reference/en/language/structure/control-structure/if/
Abbildung 4.9: if-Anweisung

Die if-Anweisungen ist solch eine Anweisung. Sie erwartet eine Bedingung in runden Klammern und die auf die if-Anweisung folgende Anweisung im Bedingungskörper wird nur dann ausgeführt, wenn die Bedingung sich als ``wahr'' herausstellt.


       if(12 > 5+3)
         Serial.println("zwoelf ist groesser als die Summe aus fuenf und drei");
In der Bedingung können unterschiedliche Vergleichsoperatoren verwendet werden. Die wichtigsten sind:
C++-Schreibweise Vergleichsoperation

> größer als
< kleiner als
== gleich
!= ungleich
Abbildung 4.10: Taster an Eingang I1

Die Bedingung kann auch Funktionsaufrufe enthalten. Im Falle der ftduino.input_get()-Funktion ist das Ergebnis des Funktionsaufrufs bereits ein Wahrheitswert (wahr oder unwahr) und der Funktionsaufruf kann direkt verwendet werden. Soll mehr als eine Anweisung von der if-Anweisung betroffen sein, so kann man sie durch geschweifte Klammern ({ und }) zusammenfassen.


     #include <FtduinoSimple.h>
     
     void setup() {
       // Vorbereiten der seriellen Verbindung 
       Serial.begin(115200);
     }  
     
     void loop() {
       // testen, on die Taste an I1 gedrückt ist
       if(ftduino.input_get(Ftduino::I1)) {
         Serial.print("Der Taster ist gedrueckt");
         // eine viertel Sekunde warten
         delay(250);
       }
     }

4.7 Schleifen

Programmschleifen sind ebenfalls ein sehr fundamentales Konzept. Erst durch sie wird es möglich, Teile eines Programms mehrfach auszuführen. Ohne sie würden alle Anweisungen eines Programms einmal der Reihe nach abgearbeitet.

Allerdings gab es in den bisherigen Beispielen bereits das ein oder andere Programm, das etwas mehrfach getan hat. Das liegt an der Arduino-spezifischen loop()-Funktion. Sie wird automatisch während des Programmablaufs immer wieder aufgerufen wie in Abbildung 4.7 abgebildet. Dadurch lassen sich in Arduino-Sketches auch ohne den Einsatz von entsprechenden C++-Anweisungen Programmteile wiederholen.

Dennoch ist es hilfreich, auch auf die C++-eigenen Mechanismen für Schleifen zurückgreifen zu können. Im Folgenden werden zwei davon beschrieben.

4.7.1 while-Schleife

https://www.arduino.cc/reference/en/language/structure/control-structure/while/
Abbildung 4.11: while-Schleife

Die while-Schleife erlaubt es, den folgenden Befehl im sogenannten Schleifenkörper solange zu wiederholen, wie die Bedingung zwischen ihren runden Klammern erfüllt ist. Wie schon bei der if-Anweisung können mehrere folgenden Anweisungen durch geschweifte Klammern zusammengefasst werden. Die while-Schleife gilt dann für den gesamten Anweisungsblock. Ist die Bedingung von vornherein nicht erfüllt, so wird der Schleifenkörper nicht ausgeführt.


     while(variablenName < 12) {
       Serial.println("Variable ist kleiner 12");
       variablenName = variablenName + 1;
     }
Die Bedingung gleicht ebenfalls der der if-Anweisung und kann die gleichen Operatoren enthalten. Das folgende Beispiel erweitert das Beispiel aus dem Abschnitt der if-Anweisung so, dass auf das Loslassen der Taste gewartet wird.

     #include <FtduinoSimple.h>
     
     void setup() {
       // Vorbereiten der seriellen Verbindung 
       Serial.begin(115200);
     }  
     
     void loop() {
       // testen, on die Taste an I1 gedrückt ist
       if(ftduino.input_get(Ftduino::I1)) {
         Serial.print("Der Taster ist gedrueckt");
         // eine viertel Sekunde warten
         delay(250);
     
         // warten, bis die Taste wieder losgelassen wird
         while(ftduino.input_get(Ftduino::I1)) {
           // der Raum zwischen den geschweiften Klammern kann
           // auch ganz leer bleiben, wenn während der
           // Wiederholung keine weiteren Anweisungen aus
           // geführt werden sollen
         }
       }
     }

4.7.2 for-Schleife

https://www.arduino.cc/reference/en/language/structure/control-structure/for/

Etwas komplexer als die while-Schleife ist die for-Schleife. Sie enthält zwischen ihren Runden Klammern gleich drei durch Semikolon getrennte Anweisungen. Die erste wird vor Schleifenbeginn einmal ausgeführt, die zweite wird während der Ausführung der Schleife ausgewertet und bestimmt, wie die Bedingung der while-Schleife wie häufig die Schleife ausgeführt wird. Die dritte Anweisung wird schließlich nach jeder Ausführung des Schleifenkörpers ausgeführt. Ist die Bedingung von vornherein nicht erfüllt, so wird der Schleifenkörper nicht ausgeführt. Die Vor-Anweisung wird in jedem Fall ausgeführt.

Abbildung 4.12: for-Schleife

Was recht aufwendig klingt wird verständlich, wenn man sich die übliche Anwendung der for-Schleife ansieht: Das Wiederholung eines Befehls mit einer bestimmten Häufigkeit.


     for(variablenName = 0 ; variablenName < 12; variableName = variableName + 1)
       Serial.println("Dieser Text wird 12 mal ausgegeben");
Die drei Anweisungen bzw. Bedingungen innerhalb der runden Klammern lauten:

       variablenName = 0;
       variablenName < 12;
       variableName = variableName + 1;
Die erste Anweisung wird nur einmal zu Beginn der Schleife ausgeführt. In diesem Fall schreibt sie den Wert 0 in die Variable variablenName die zweite Anweisung ist die Bedingung, die bestimmt wie oft die Schleife ausgeführt wird. In diesem Fall solange der Inhalt der Variable variablenName kleiner als 12 ist. Und die dritte Anweisung wird schließlich am Ende jedes Durchlaufs der for-Schleife ausgeführt. In diesem Fall wird dort der Inhalt der Variablen variablenName um eins erhöht. In diesem Beispiel passiert also:

  1. Der Inhalt der Variablen variablenName wird auf 0 gesetzt
  2. Solange der Inhalt der Variablen variablenName kleiner als 12 ist ...
    1. ... werden die Anweisungen im Schleifenkörper ausgeführt ...
    2. ... und danach der Inhalt der Variablen variablenName um eins erhöht

Der Schleifenkörper wird also genau 12 mal ausgeführt.

4.8 Beispiele

Die in diesem Kapitel vorgestellten C++-Sprachkonstrukte sowie die ausgewählten Funktionen der Arduino- und der ftDuino-Bibliotheken bilden zwar nur einen geringen Teil ab. Aber schon diese wenigen Informationen reichen, um einige interessante Sketches selbst zu schreiben.

4.8.1 Einfache Ampel

Dieses Beispiel stellt eine eine einfache Bedarfs-Ampel dar. Nach dem Einschalten zeigt sie zunächst rot. Sobald der Taster gedrückt wird springt sie für 10 Sekunde auf grün und wechselt dann zurück in den Anfangszustand.

Abbildung 4.13: Einfache Ampel

Der dazugehörige Sketch ist sehr einfach. In der setup()-Funktion wird in Zeile 5 die rote Lampe eingeschaltet.

In der loop()-Funktion wird in Zeile 10 permanent getestet, ob der Taster gedrückt ist. Ist er gedrückt, so wird der gesamte Bedingungskörper in den Zeilen 11 bis 20 ausgeführt.

Dort wird in Zeile 12 die rote Lampe aus- und in Zeile 14 die grüne Lampe eingeschaltet. In Zeile 16 wird 10000 Millisekunden, also 10 Sekunden gewartet, bevor in den Zeilen 18 und 20 die grüne Lampe zunächst aus- und die rote dann eingeschaltet wird.

Dies ist ein Beispiel für Dinge, die aufgrund der Geschwindigkeit des Mikroprozessors als gleichzeitig wahrgenommen werden. Obwohl die Lampen in den Zeilen 12 und 14 bzw. 18 und 20 jeweils nacheinander ein- bzw. ausgeschaltet werden ist keine zeitliche Differenz erkennbar. Der Abstand von wenigen Mikrosekunden ist nicht erkennbar.


      1  #include <FtduinoSimple.h>
      2  
      3  void setup() {
      4    // beim Start der Ampel leuchtet die rote Lampe
      5    ftduino.output_set(Ftduino::O1, Ftduino::HI);
      6  }  
      7  
      8  void loop() {
      9    // testen, on die Taste an I1 gedrückt ist
     10    if(ftduino.input_get(Ftduino::I1)) {
     11      // rote Lampe ausschalten
     12      ftduino.output_set(Ftduino::O1, Ftduino::OFF);
     13      // grüne Lampe einschalten
     14      ftduino.output_set(Ftduino::O2, Ftduino::HI);
     15      // zehn Sekunden warten
     16      delay(10000); 
     17      // grüne Lampe ausschalten
     18      ftduino.output_set(Ftduino::O2, Ftduino::OFF);
     19      // rote Lampe einschalten
     20      ftduino.output_set(Ftduino::O1, Ftduino::HI);
     21    }
     22  }

4.8.2 Schranke

Das Schrankenbeispiel ist etwas komplexer. Es besteht aus einer motorisierten Schranke mit jeweils zwei Endtastern. Der Taster an I2 wird betätigt, wenn sich die Schranke komplett geöffnet hat, der an I3 wird betätigt, wenn die Schranke vollständig geschlossen ist.

Abbildung 4.14: Schranke

Achtung: Taster I2 und I3 sind so verdrahtet, dass der Kontakt im unbetätigten Zustand geschlossen ist. Er öffnet sich, sobald die Taster betätigt werden, die Schranke also ganz geöffnet oder geschlossen ist.

Bei Sketchstart wird zunächst der Motor in Zeile 10 so lange linksherum gedreht, solange der Kontakt des Tasters an I2 in Zeile 15 als geschlossen erkannt wird, also solange die Schranke nicht vollständig geöffnet ist. Sobald der Taster betätigt wird wird der Motor in Zeile 18 gestoppt.

Sobald die Taste an I1 in Zeile 23 gedrückt wird wird der Motor in Zeile 26 diesmal rechtsherum gestartet bis der Taster an I3 betätigt wird. In Zeile 28 wird der Motor dann gestoppt.

In den Zeilen 31 bis 40 wird die Lampe an O3 insgesamt fünfmal mit einer Pause von jeweils 500 Millisekunden ein- und wieder ausgeschaltet.

Danach wird die Schranke schließlich in den Zeilen 43 bis 45 wieder geschlossen.


      1  #include <FtduinoSimple.h>
      2  
      3  // Zählervariable zum Blinken
      4  int zaehler;
      5  
      6  void setup() {
      7    // Beim Start fährt die Schranke auf
      8  
      9    // motor linksherum drehen
     10    ftduino.motor_set(Ftduino::M1, Ftduino::LEFT);
     11    
     12    // Warten bis Schranke geschlossen offen ist. Da der Taster als
     13    // Öffner verdrahtet ist läuft der Motor, solange der Taster
     14    // geschlossen ist
     15    while(ftduino.input_get(Ftduino::I2)) { }
     16  
     17    // Sobald der Taster nicht mehr geschlossen ist Motor stoppen
     18    ftduino.motor_set(Ftduino::M1, Ftduino::OFF);
     19  }  
     20  
     21  void loop() {
     22    // testen, on die Taste an I1 gedrückt ist
     23    if(ftduino.input_get(Ftduino::I1)) {
     24      
     25      // Motor rechtsherum drehen, bis Taster I3 geöffnet ist
     26      ftduino.motor_set(Ftduino::M1, Ftduino::RIGHT);
     27      while(ftduino.input_get(Ftduino::I3)) { }
     28      ftduino.motor_set(Ftduino::M1, Ftduino::OFF);
     29  
     30      // fünfmal mit der Lampe blinken
     31      for(zaehler=0;zaehler<5;zaehler=zaehler+1) {
     32        // Lampe an
     33        ftduino.output_set(Ftduino::O3, Ftduino::HI);
     34        // 500ms warten
     35        delay(500);
     36        // Lampe aus
     37        ftduino.output_set(Ftduino::O3, Ftduino::OFF);
     38        // 500ms warten
     39        delay(500);
     40      }
     41  
     42      // Motor linksherum drehen, bis Taster I2 geöffnet ist
     43      ftduino.motor_set(Ftduino::M1, Ftduino::LEFT);
     44      while(ftduino.input_get(Ftduino::I2)) { }
     45      ftduino.motor_set(Ftduino::M1, Ftduino::OFF);
     46    }
     47  }

4.9 Die Warnung Wenig Arbeitsspeicher

Bei der Programmierung größerer Projekte und speziell bei der großzügigen Verwendung von Bibliotheken kann es leicht zu der in Abbildung 4.15 dargestellten Warnmeldung kommen.

Abbildung 4.15: Warnung der Arduino-IDE über geringen Arbeitsspeicher

Da der ftDuino lediglich über 2560 Bytes dynamischen Speicher (RAM-Speicher) verfügt ist der Umgang mit dieser knappen Resource oft nicht einfach.

Die Warnung über den geringen Arbeitsspeicher bezieht ausschließlich auf den dynamischen RAM-Speicher und nicht auf den ebenfalls in der Meldung erwähnten Programmspeicherplatz (oft auch als Flash-Speicher bezeichnet). Der Programmspeicher kann bedenkenlos zu 100% gefüllt werden.

4.9.1 Auswirkungen

Im Flash-Speicher des ftDuino sind zu jeder Zeit zwei Programme gespeichert:

Bootloader
Der Bootloader (siehe Abschnitt 1.2.1) ist in einem geschützten Teil des Flashspeichers abgelegt. Er wird genutzt, um Sketches aus der Arduino-IDE über USB in den übrigen Flash-Speicher zu laden.
Sketch
Der Sketch wird vom Anwender mit Hilfe der Arduino-IDE und des Bootloaders installiert und kann sämtlichen Flash-Speicher nutzen, der nicht durch den Bootloader belegt wird. Im ftDuino stehen neben dem vom Bootloader belegten Bereich noch 28672 Bytes des Flash-Speichers für eigene Sketche zur Verfügung.

Für beide Programme hat die Meldung eine (unterschiedliche) Bedeutung.

Auswirkungen auf den Sketch

Die genauen Auswirkungen von RAM-Mangel auf den Sketch sind kaum vorhersagbar. Problematisch wird der Mangel an dynamischem Speicher dadurch, dass während der Sketchausführung immer mal wieder zusätzlicher dynamischer Speicher benötigt wird um beispielsweise Zwischenergebnisse von Rechnungen zu speichern oder zu speichern, wo die Sketchausführung nach dem Aufruf von Unterfunktionen fortgesetzt werden muss. Durch den Speichermangel werden dann Zwischenergebnisse oder Fortsetzungspunkte verfälscht. Die gesamte Programmausführung kann dann zu völlig unerwarteten und unsinnigen Reaktionen führen.

Ein solcher defekter Sketch kann jederzeit durch einen korrigierten Sketch ersetzt werden, um eine korrekte Programmausführung wieder herzustellen.

Im Zweifelsfall sollte man zunächst einen einfachen und bekanntermaßen funktionsfähigen Sketch installieren. Der Blink-Sketch unter DateiBeispiele01. BasicsBlink bietet sich dafür an.

Auswirkungen auf den Bootloader

Der Bootloader selbst ist ein eigenständiges Programm im Flash. Er ist von Engpässen beim dynamischen Speicher nicht betroffen, da er sich diesen Speicher nicht mit dem Sketch teilen muss. Allerdings gibt es einen kleinen zum Bootloader gehörigen Funktionsblock innerhalb eines jeden Sketches, der von der Arduino-IDE für den Anwender unsichtbar in den Code des Sketches integriert wird.

Dieser Teil realisiert die USB-Kommunikation mit dem PC während der Sketchausführung wird. Vor allem reagiert dieser Teil auf den von der Arduino-IDE gesendeten Befehl, den Bootloader in Vorbereitung eines Sketch-Uploads zu starten. Stellt dieser Teil des laufenden Sketches fest, dass die Arduino-IDE einen neuen Sketch übertragen will, so aktiviert sie den Bootloader. Der Bootloader wiederum handhabt in Folge den eigentlichen Empfang des neuen Sketches.

Bei dynamischem Speichermangel kann es passieren, dass dieser verborgene Teil des Sketches nicht korrekt funktioniert. Die Arduino-IDE kann dadurch selbst nicht mehr den Start des Bootloaders veranlassen und Upload-Versuche durch die Arduino-IDE scheitern. Unter Umständen ist die USB-Kommunikation zum PC so sehr beeinträchtigt, dass der ftDuino während der Sketch-Ausführung vom PC nicht korrekt oder ggf. sogar überhaupt nicht erkannt wird.

Für diesen Fall verfügt der ftDuino über einen Reset-Taster (siehe Abschnitt 1.2.3). Ist die Arduino-IDE durch Speichermangel im Sketch nicht mehr in der Lage, den ftDuino über USB anzusprechen und zur Aktivierung des Bootloaders zu veranlassen, so ist es mit dem Reset-Taster manuell immer noch möglich, den Bootloader im passenden manuell zu aktivieren wie in Abschnitt 1.3.2 beschrieben. Die Funktion des Reset-Tasters ist immer vorhanden und sie kann nicht durch einen fehlerhaften Sketch beeinflusst werden. Mit seiner Hilfe ist es immer möglich, einen neuen Sketch auf den ftDuino zu laden.

4.9.2 Vorbeugende Maßnahmen

Taucht die o.g. Warnung auf, so sollte man im Zweifelsfall davon Abstand nehmen, den Sketch auf den ftDuino zu laden. Auch wenn es mit Hilfe des Reset-Tasters immer möglich ist, den fehlerhaften Sketch zu ersetzen, so erfordert dieses Vorgehen ein gutes Timing und es kann einige Versuche erfordern, bis der störrische Sketch erfolgreich ersetzt ist.

Verwendung von Flash-Speicher für konstante Daten

In vielen Sketchen werden Textausgaben gemacht, entweder über den seriellen Monitor oder z.B. auf ein kleines Display. Üblicherweise enthält ein Sketch daher Zeilen wie die folgende.


     Serial.println("Hallo Welt!");
Dabei ist nicht offensichtlich, dass hier 12 Bytes des kostbaren dynamischen RAM-Speichers unnötigerweise belegt werden. Das lässt sich leicht in einem minimalen Sketch testen:

      1  void setup() {
      2    Serial.begin(9600);
      3  }
      4  void loop() {
      5    Serial.println("Hallo Welt!");
      6    delay(1000);
      7  }
Dieser Sketch belegt laut Arduino-IDE 163 Bytes bzw. 6% des dynamischen Speichers (der genaue Wert kann je nach Version der Arduino-IDE etwas schwanken). Kommentiert man Zeile 5 durch vorangestellte // aus, so reduziert sich der Speicherverbrauch auf 149 Bytes, es werden also ganze 14 Bytes des dynamischen Speichers gespart.

Der Grund liegt darin, dass die Arduino-IDE davon ausgeht, dass die Zeichenkette ``Hallo Welt!'' weiter verarbeitet werden soll. Daher legt die Arduino-IDE die Zeichenkette ``Hallo Welt!'' im dynamischen Speicher ab, wo sie vom Sketch verändert werden könnte. Da wir aber nicht vor haben, diese Zeichenkette jemals während der Sketch ausgeführt wird zu verändern könnte man sie auch im Flash-Speicher belassen. Genau dafür gibt es Hilfsfunktionen in der Arduino-IDE. Ein schlichtes F(...) um die Zeichenkette herum erledigt genau das. Dazu ersetzt man die bisherige ``Hallo-Welt''-Ausgabe durch das folgende Konstrukt.


     Serial.println(F("Hallo Welt!"));
Dieser Sketch verhält sich identisch zur vorherigen Version, er belegt aber nur 151 statt 163 Bytes. Die Differenz entspricht genau der Länge der Zeichenkette ``Hallo Welt!'' zuzüglich eines weiteren Bytes, das die Zeichenkette beendet. In vielen Sketches lässt sich auf diese Weise bereits signifikant dynamischer Speicher sparen.

Der Umgang mit dem Flash-Speicher ist allerdings nicht ganz einfach. Das folgende Beispiel nutzt einen weiteren Hilfsmechanismus namens PROGMEM um eine Zeichenkette im Flash abzulegen. Leider lässt sich diese dann nicht einfach per println() ausgeben.


     // das folgende klappt nicht, denn der ftDuino weiss nicht, ob er RAM
     // oder Flash lesen soll
     static const char str[] PROGMEM = "Hallo Welt!";
     Serial.println(str);
Das Problem ist, dass println() nicht erkennen kann, ob str auf Flash- oder RAM-Speicher verweist. Eine mögliche Lösung ist, die Zeichen einzeln aus dem Flash-Speicher mit Hilfe spezieller Funktionen auszulesen und auszugeben wie im folgenden Beispiel:

     static const char str[] PROGMEM = "Hallo Welt!\n";
     for (char c = 0; c < strlen_P(str); c++)
       Serial.print((char)pgm_read_byte_near(str + c));
Die Dokumentation der Arduino-IDE hält viele weitere Beispiel zur Nutzung des Flash-Speichers bereit. Unter dem Stichwort PROGMEM finden sich weiterführende Informationen unter anderem unter

https://www.arduino.cc/reference/en/language/variables/utilities/progmem/

und

http://playground.arduino.cc/Main/PROGMEM.

Diese Technik lässt sich nicht nur auf Zeichenketten anwenden, sondern auf alle Arten statischer Daten wie Töne, Werte-Tabellen usw.

Verwendung von alternativen Bibliotheken

Bibliotheken sind eine praktische Sache, keine Frage. Und bei funktionsreichen Bibliotheken ist die Wahrscheinlichkeit umso höher, dass man alles findet, das man für sein konkretes Problem benötigt.

Oft sind es aber gerade die besonders umfangreichen Bibliotheken, die besonders ressourcenhungrig sind und eine große Menge Speicher belegen. Bei Verwendung des OLED-Displays aus Abschnitt 6.13.3 am ftDuino sind es zum Beispiel die Grafikbibliotheken von Adafruit, die alle Funktionen für aufwändige Grafiken mitbringen, aber gleichzeitig einen hohen Speicherverbrauch haben.

Mit wenigen Zeile Code bringt das folgende Beispiel die Nachricht ``Hallo Welt!'' auf den OLED-Bildschirm.


      1  #include <Adafruit_GFX.h>
      2  #include <Adafruit_SSD1306.h>
      3  Adafruit_SSD1306 display(-1);
      4  
      5  void setup() {
      6    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
      7    display.clearDisplay();
      8    display.setTextColor(WHITE);
      9    display.println("Hallo Welt!");
     10    display.display();
     11  }
     12  
     13  void loop() { }
Schon dieses einfache Beispiel belegt 1504 Bytes bzw 58% des dynamischen Speichers. Das ist vor allem der Tatsache geschuldet, dass die Bibliothek für komplexe Zeichenoperationen eine Kopie des Bildschirminhalts im dynamischen Speicher vorhält. Bei 128 x 64 Pixel sind das bereits 128*64/8=1024 Bytes.

Ist man auf Grafikfähigkeiten aber gar nicht angewiesen, sondern kann mit einer Darstellung von 16 x 8 Zeichen auskommen, dann bieten sich sparsame Alternativen an.

Eine davon ist die U8g2-Bibliothek und dort speziell die mitgelieferte U8x8-Bibliothek. Sie lässt sich direkt im Bibliotheksmanager der Arduino-IDE installieren. Das ``Hallo Welt!''-Beispiel sieht in diesem Fall wie folgt aus.


      1  #include <U8x8lib.h>
      2  U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);
      3  
      4  void setup(void)
      5  {
      6    u8x8.begin();
      7    u8x8.setPowerSave(0);
      8    u8x8.setFont(u8x8_font_chroma48medium8_r);
      9    u8x8.drawString(0,0,"Hallo Welt!");
     10  }
     11  
     12  void loop() { }
Hier werden nur 578 Bytes entsprechend 22% des dynamischen Speichers belegt und es bleibt wesentlich mehr für andere Bibliotheken und eigenen Code übrig.

Bei vielen Bibliotheken ist es ähnlich und es kann sich lohnen, genau zu schauen, welche Ansprüche man hat und welche Bibliothek diese Ansprüche mit minimalem Aufwand und Ressourceneinsatz erfüllen kann. Oft lassen sich mit nur kleinen Einschränkungen bereits große Gewinne erzielen.

4.10 Weiterführende Informationen

Es gibt im Internet viele Tutorials sowohl zur C++-Programmierung2 als auch zur Arduino-Programmierung3 .

Diese und ähnliche Tutorials erklären viele weitere Sprachkonstrukte und Bibliotheksfunktionen. Solche Tutorials sind nicht auf den ftDuino zugeschnitten. Aber dieses Kapitel hat die nötigen Grundlagen geliefert, um auch mit anderen Tutorials weiterarbeiten zu können. Nicht alles aus der Welt der PC-Programmierung oder der Arduino-Programmierung lässt sich auf den ftDuino übertragen. Zusammen mit den Experimenten und Modellen der folgenden Kapitel sowie den fertig mitgelieferten Beispielprogrammen ist der tiefere Einstieg in die professionelle C++-Programmierung aber möglich.


1) GNU-Compiler-Collection GCC: https://gcc.gnu.org/
2) C++-Tutorial http://www.online-tutorials.net/c-c++-c/c++-tutorial-teil-1/tutorials-t-1-58.html
3) Arduino-Tutorial http://www.arduino-tutorial.de