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.
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.
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.
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.
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.
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ü Datei ► Neu. 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.
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 Datei ► Neu 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.
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 Datei ► Neu 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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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);
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);
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);
}
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() {
}
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() {
}
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.
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() {
}
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() {
}
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);
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.
if
-Anweisungif
-AnweisungDie 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 |
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);
}
}
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.
while
-Schleifewhile
-SchleifeDie 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
}
}
}
for
-SchleifeEtwas 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.
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:
Der Schleifenkörper wird also genau 12 mal ausgeführt.
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.
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.
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 }
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.
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 }
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.
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.
Im Flash-Speicher des ftDuino sind zu jeder Zeit zwei Programme gespeichert:
Für beide Programme hat die Meldung eine (unterschiedliche) Bedeutung.
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 Datei ► Beispiele ► 01. Basics ► Blink bietet sich dafür an.
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.
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.
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.
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.
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.