Die Grundstruktur
eines Arduino-Programms besitzt immer die beiden Funktionen setup()
und loop()
.
Somit sieht der minimale Code für ein Programm (Sketch ) so aus:
void setup() // Programmstart { // Anweisungen } void loop() // Hauptschleife { // Anweisungen }
Listing A.1: Arduino-Sketch: Grundstruktur
Die Setup-Funktion wird einmalig beim Start des Arduino-Boards oder nach einem Reset ausgeführt. In dieser Funktion werden Grundeinstellungen wie Variablendeklarationen oder Konfiguration der seriellen Schnittstelle vorgenommen. Zusätzlich werden in der Setup-Funktion die Ein- und Ausgänge gesetzt.
int ledPin = 13; // LED an Pin 13 int buttonPin = 2; // Button an Pin 2 void setup() { pinMode(ledPin, OUTPUT); // Pin 13 als Ausgang pinMode(buttonPin, INPUT); // Pin 2 als Eingang Serial.begin(9600); // Initialisierung der seriellen Schnittstelle }
Listing A.2: Setup-Funktion: Definition von Ein- und Ausgängen und Konfiguration der seriellen Schnittstelle
Die Setup-Funktion ist zwingend notwendig und muss immer vorhanden sein, auch wenn keine Deklarationen gemacht werden müssen. In diesem Fall bleibt die Funktion ohne Anweisungen.
void setup() // Setup ohne Deklaration oder pinMode-Konfiguration { }
Listing A.3: Arduino-Sketch: Setup-Funktion ohne Deklarationen
Die Loop-Funktion ist der zweite Teil der Grundstruktur eines Arduino-Programms und übernimmt die Aufgabe eines Hauptprogramms . Nach dem einmaligen Durchlaufen der Setup-Funktion wird die Loop-Funktion durchlaufen – wie der Name schon sagt als endlose Schleife. In dieser Schleife werden alle weiteren Anweisungen und Funktionsaufrufe untergebracht, die im Normalbetrieb für die gewünschte Lösung benötigt werden.
void loop() // Schleife durch das Hauptprogramm { digitalWrite(ledPin, HIGH); // LED einschalten delay(1000); // 1 Sekunde warten digitalWrite(ledPin, LOW); // LED ausschalten delay(500); // 0,5 Sekunden warten // und weiter geht’s am Start det der Schleife }
Listing A.4:
Arduino-Sketch: Hauptschleife loop()
Eine Funktion ist ein in sich geschlossener Satz von Programmzeilen. Funktionen besitzen einen eigenen Namen und werden mittels dieses Namens aus anderen Programmteilen aufgerufen.
Funktionen werden verwendet, um den Programmcode zu strukturieren und um wiederkehrende Anweisungen nicht mehrfach zu programmieren.
Beim Aufruf einer Funktion
können ein oder mehrere Parameter
übergeben werden. Das Resultat eines Funktionsaufrufs ist die Ausführung verschiedener Anweisungen und gegebenenfalls ein Rückgabewert
. Der Typ des Rückgabewert
es entspricht dem Typ, der bei der Funktionsdefinition
angegeben wurde. Wird beispielsweise eine Funktion mit dem Typ int
definiert, so entspricht der Rückgabewert dieser Funktion dem Typ int
, also Integer. Wird kein Typ angegeben, so wird der Typ void
verwendet.
Der Grundaufbau einer Funktion sieht also so aus:
Typ NameDerFunktion (Parameter) { // Anweisungen }
Im folgenden Beispiel wird eine Funktion aufgerufen, die einen Analogwert eines externen Sensors einliest, umrechnet und zurückgibt. Als Parameter wird die jeweilige Portnummer übergeben.
float ReadSensor(int tempPinIn) // Abfrage von Analogport { float tempC = analogRead(tempPinIn); // Anlogwert einlesen tempC = (5.0 * tempC * 100.0)/1024.0; // Wert umrechnen Serial.println(tempC); // Ausgabe Wert an ser. Schnittstelle return tempC; // Rückgabe Wert }
Listing A.5: Arduino-Sketch: Aufruf einer Funktion
Durch die Verwendung einer Funktion können ein oder mehrere analoge Eingänge abgefragt werden. Bei jedem Aufruf der Funktion wird einfach die entsprechende Pin-Nummer übergeben.
int tempPin=1; // Pinnummer von Analogport ReadSensor(tempPin); // Aufruf Funktion
Bei der Programmierung der Sketche müssen einige Regeln eingehalten werden, damit am Schluss auch ein lauffähiges Programm auf das Arduino-Board geladen werden kann.
In den Arduino-Sketchen werden drei verschiedene Arten von Klammern unterschieden: runde Klammern
(),
geschweifte Klammern
{}
und eckige Klammern
[]
.
Runde Klammern werden beim Aufruf von Funktionen, bei mathematischen Umrechnungen oder auch bei der Ausgabe über den seriellen Port verwendet.
Beispiele:
ReadSensor(tempPin); tempC = (5.0 * tempC * 100.0)/1024.0; Serial.println("Temperatur in Grad");
Geschweifte Klammern werden in vielen Programmiersprachen verwendet und sind oft für Programmiereinsteiger etwas gewöhnungsbedürftig. Diese Klammern definieren Beginn und Ende von Anweisungen, Funktionen und Codebereichen. Im Arduino-Code werden die geschweiften Klammern auch bei den Anweisungen if
und for
verwendet.
Beispiele:
// Funktion float ReadSensor(int tempPinIn) { // Beginn Funktion // Anweisungen } // Ende Funktion // for-Anweisung for (i = 0; i < 100; i++) { // Anweisungen } // if-Anweisung if (millis() - previousMillis > interval) { // Anweisungen }
Die Arduino-Entwicklungsumgebung unterstützt den Programmierer bei der Verwendung der geschweiften Klammern, indem jeweils beim Anklicken einer öffnenden Klammer die dazugehörige schließende Klammer hervorgehoben wird (und umgekehrt).
Abb. A.1: Codierung: Darstellung von öffnenden und schließenden Klammern in der Entwicklungsumgebung
Eckige Klammern werden bei Arrays verwendet.
Beispiel:
// Liste mit Werten int myWerte[5] = {34, 12, 64, 5, 18};
Arrays werden in einem späteren Abschnitt genauer beschrieben.
Eine Anweisung wird jeweils mit einem Semikolon abgeschlossen. Das Semikolon ist zwingend notwendig. Ein fehlendes Semikolon erzeugt beim Kompilieren in der Entwicklungsumgebung eine Fehlermeldung. Leider sind die Fehlermeldungen in der IDE nicht immer sehr aussagekräftig. Darum sollte im Fehlerfall jeweils überprüft werden, ob jede Anweisung mit einem Semikolon abschließt.
int tempGrad = 12; const int ledPin = 13; Serial.println(tempC);
Ohne Semikolon verwendet wird die Definitionsanweisung
#define
.
#define DCF77PIN 2 // Port 2 als DCF-Eingang #define BLINKPIN 13 // Port 13 als LED-Ausgang #define TEMPERATURE 2 // Analogeingang 2 für Temperatursensor
Kommentare und Bemerkungen im Programmcode unterstützen den Programmierer bei der sauberen und verständlichen Darstellung der Codezeilen. Kommentare werden vom Arduino-Programm nicht interpretiert und benötigen keinen Speicherplatz.
Die Darstellung von Kommentaren kann als Kommentarblock oder als einzelner einzeiliger Kommentar auf einer Zeile angewendet werden.
Ein Kommentarblock beginnt mit /*
und wird mit */
abgeschlossen. Der gesamte Text dazwischen wird vom Programm als Kommentar betrachtet.
Ein einzeiliger Kommentar beginnt mit //
und wird mit dem Zeilenende ohne weitere Anweisung abgeschlossen.
/* Das ist ein Kommentarblock. Er beinhaltet Beschreibungen und Informationen zu einem Arduino-Sketch. Der Kommentar kann sich über mehrere Zeilen erstrecken. */ // Das ist ein einzeiliger Kommentar // Ein Kommentar kann auch hinter einer Anweisung zur näheren Erklärung // gemacht werden tempC = (5.0 * tempC * 100.0)/1024.0; // Umrechnung auf Grad Celsius
Bei der Programmierung lohnt es sich, genügend Kommentare zu verfassen, weil es dadurch anderen Personen leichter fällt, den Code zu warten.
Mittels des Kommentarblocks kann ein ganzer Codebereich für das Debugging aktiviert oder deaktiviert werden.
In den meisten Arduino-Projekten werden Daten von externen Sensoren gelesen und verarbeitet. Die Verarbeitung und Zwischenspeicherung dieser Daten, beispielsweise ein Abstandswert von einem Ultraschallsensor oder ein Temperaturwert, erfolgt mittels Variablen. Eine Variable besitzt einen Namen und einen Wert eines bestimmten Datentyps. Ein Datentyp beschreibt den erlaubten Wertebereich und die möglichen Operationen dieser Variablen.
Nachfolgend werden die meistbenutzten Datentypen in Arduino-Programmen aufgelistet.
int
(Integer) ist der am häufigsten verwendete Datentyp. Integerwerte sind ganzzahlig
ohne Kommastellen. Integerzahlen besitzen eine Länge von 16 Bit.
Wertebereich: -32.768 bis 32.767
Beispiel:
int SensorAbstand = 3478; // Variable SensorAbstand als Integer
Zu beachten ist, dass bei einem »Überlauf« des Wertes, also bei 32.767 + 1, der Variablenwert auf -32.768 gesetzt wird.
Dieser Datentyp entspricht dem Datentyp int
, außer dass unsigned int
keine negativen Werte speichert.
Wertebereich: 0 bis 65.535
Beim Datentyp byte
sind die Variablenwerte ebenfalls ganzzahlig. Die Länge des Wertes beträgt 8 Bit.
Wertebereich: 0 bis 255
Beispiel:
byte Helligkeit = 197; // Variable Helligkeit als Datentyp byte
Integerzahlen mit einem erweiterten Wertebereich werden als Datentyp long
gespeichert. Die Long-Zahlen werden als ganzzahlige 32-Bit-Zahl im Speicher abgelegt.
Wertebereich: -2.147.483.648 bis 2.147.483.647
Beispiel:
long Anzahl = 324645; // Variable Anzahl als Datentyp long
Bei diesem long
-Datentyp können nur positive Werte als 32-Bit-Zahl gespeichert werden.
Wertebereich: 0 bis 4.294.967.295
Zahlen vom Datentyp float
werden als 32-Bit-Fließkommazahl
mit Nachkommastellen
gespeichert. Berechnungen mit Fließkommazahlen sind langsamer als Integerberechnungen. Zur schnelleren Ausführung von Rechenoperationen sollten darum Integerzahlen verwendet werden.
Wertebereich: -3,4028235E+38 bis 3,4028235E+38
Beispiele:
float SensorKorrektur = 2.134; // Variable Sensorkorrektur als Datentyp float float PiWert = 3.141; // Wert von Pi
Der Datentyp double
entspricht bei der Arduino-Programmierung dem Datentyp float
.
Mit dem Datentyp char
werden Werte von Buchstaben und Zeichen als 8-Bit-Wert gespeichert.
Wertebereich: -128 bis 127
Beispiele:
char myCharacter = ’T’; // Wert von Buchstabe T als char gespeichert char myCharacter = ’84’; // Wert als Dezimalzahl von Buchstabe T
Eine Tabelle mit den ASCII-Werten der einzelnen Buchstaben und Zeichen ist unter http://arduino.cc/en/Reference/ASCIIchart zu finden.
Wie aus dem Wertebereich für char
zu erkennen ist, gehört dieser Datentyp zu den signed
-Datentypen. Der Wertebereich liegt im negativen und positiven Bereich. Der entsprechende 8-Bit-Datentyp ohne Vorzeichen ist der Datentyp byte
.
Der Datentyp boolean
besitzt nur zwei mögliche Werte: true
oder false
. Mit diesem Datentyp werden die Binärwerte
1 oder 0 gespeichert.
Wertebereich: true , false
Beispiel:
int pinMotor = 8; // Motoransteuerung an Pin 8 int EndschalterVornPin = 13; // Eingang von Endschalter (High = gedrückt) boolean RunStatus = false; void setup() { pinMode(pinMotor, OUTPUT); pinMode(EndschalterVornPin, INPUT); digitalWrite(pinMotor, LOW); // Motor aus } void loop() { digitalWrite(pinMotor, HIGH); // Motor an RunStatus = true; if (digitalRead(EndschalterVornPin) == HIGH) // Endschalter gedrückt { RunStatus = false; digitalWrite(pinMotor, LOW); // Motor aus // weiter mit Ausweichaktion } }
Wie das Beispiel zeigt, wird boolean
meist für die Speicherung eines Betriebszustands wie »Motor läuft« oder ähnlich verwendet.
Eine Aneinanderreihung von Zeichen des Datentyps char
wird als String (Zeichenkette) bezeichnet.
Beispiele:
// leerer String mit fixer Länge char myStr1[15]; // Zeichenkette char myStr2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'}; // Zeichenkette mit 0 zum Anzeigen des Endes char myStr3[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o', '\0'}; // Zeichenkette mit Anführungszeichen char myStr4[ ] = "arduino"; // Zeichenkette mit Anführungszeichen und fixer Länge char myStr5[8] = "arduino"; // Zeichenkette mit fixer Länge char myStr6[15] = "arduino";
Bei einem String muss jeweils die Länge angegeben werden. Der Abschluss eines Strings wird mit einer 0 angegeben. Die angehängte 0 kennzeichnet bei der String-Verarbeitung durch den Arduino das eindeutige String-Ende. Eine fehlende 0 kann unerwartete Resultate bei der Sketch-Ausführung hervorrufen.
Wie man im Beispiel-String myStr2
erkennen kann, benötigt das Wort Arduino
nur sieben Zeichen. Das letzte reservierte Zeichen wird für die angehängte 0 benötigt.
Um die Initialisierung der String-Variablen zu vereinfachen, kann man mit der Verwendung von doppelten Anführungszeichen eine Zeichenkette wie im Beispiel-String myStr4
definieren. Eine explizite Angabe der String-Größe
und eine angehängte 0 als Abschluss ist nicht zwingend erforderlich.
Bei der Nutzung von Zeichenketten empfiehlt es sich daher, immer ein Auge auf den nötigen Speicherbedarf zu werfen und nur so viel Speicherplatz, sprich String-Größe, wie nötig zu initialisieren.
Um größere Zeichenketten zu speichern, beispielsweise Daten zur Anzeige auf LC-Displays oder Zeichenketten von einem GPS-Modul, wird eine komplexere Zeichenkettenverarbeitung genutzt. In diesem Fall werden die Zeichenketten nicht direkt in der Tabelle (String
-Array) abgelegt, sondern nur ein Zeiger
(Verweis oder Pointer) auf eine weitere Tabelle. Dieses Konstrukt einer Zeigertabelle entstammt der fortgeschrittenen C-Programmierung und wird mit einem * (Asterisk
) nach der Typdefinition char
angezeigt.
Ein Beispiel soll zeigen, wie dies im Arduino-Sketch verwendet wird.
char* myStrings[]={ "String Zeile 1", "String Zeile 2", "String Zeile 3", "String Zeile 4", "String Zeile 5" }; void setup(){ Serial.begin(9600); // Ausgabe einzelne Zeilen Serial.println(myStrings[2]); } void loop(){ // Schleife über die einzelnen Einträge im Array for (int i = 0; i < 6; i++){ Serial.println(myStrings[i]); delay(500); } }
Ein Array ist, wie im vorherigen Beispiel erwähnt, eine Art Tabelle und dient zur Speicherung von Daten während des Programmablaufs.
Ein Array wird eingesetzt, wenn man im Programm nicht nur einen einzelnen Wert speichern und verarbeiten möchte.
Bevor man ein Array nutzen kann, muss dieses definiert werden.
// Definition eines Arrays // Array mit 5 Positionen, keine Werte definiert int myArray[4] // Liste mit Portnummern, keine Definition der Array-Größe int myPorts[] = {8, 9, 11, 10}; // Liste mit Werten int myWerte[5] = {34, 12, 64, 5, 18}; // Array mit Text char myText[8] = "Arduino";
Ein Array beinhaltet also immer einen Namen, eine Angabe der Größe und eine Werteliste.
Das erste Beispiel myArray[4]
wird initialisiert, hat aber noch keine Werte gespeichert.
Im zweiten Beispiel myPorts[]
wird das Array initialisiert und mit Werten gefüllt. Es ist keine Größe des Arrays definiert. In diesem Fall wird die benötigte Größe anhand der übergebenen Werte intern ermittelt.
Das vierte Beispiel myText[8]
zeigt ein Array des Typs char
. In diesem Fall ist zu beachten, dass bei der Größe des Arrays die Anzahl der Zeichen zuzüglich 1 angegeben werden muss, weil die 0 als Abschluss des Strings ebenfalls Speicherplatz belegt.
Die Größe eines Arrays ist also ein wichtiger Wert und muss bei der Verwendung dieses Datentyps beachtet werden. Eine falsche Angabe kann einen Fehler im Programmablauf erzeugen, da der Prozessor einen fehlerhaften Wert liest und weiterverarbeitet. Da die Array-Werte im internen Speicher des Prozessors gespeichert sind, kann bei einer zu großzügigen Definition schnell ein Speicherplatzproblem entstehen. Dies ist meist an merkwürdigem, unstabilem Ausführen des Programms zu erkennen und muss nicht zwingend eine Fehlermeldung oder einen Programmabbruch hervorrufen.
Die Abfrage eines Wertes aus dem Array erfolgt jeweils über die Angabe des Indexes des gewünschten Werts.
// Abfrage eines Array-Wertes wert = myWerte[2]; // Abfrage des Wertes 64 aus dem Array wert2 = myPorts[3]; // Abfrage ergibt den Wert 10
Da der erste Wert im Array den Index 0 besitzt, wird der dritte Wert innerhalb des Arrays mit dem Index 2 aufgerufen. Diese Tatsache ist speziell zu beachten, wenn man mittels einer Schleife ein ganzes Array durchsucht.
Ein Array der Länge 5 besitzt also Indexwerte von 0 bis 4. Der Index des letzten Wertes innerhalb des Arrays ist somit immer die Größe des Arrays minus 1.
Die Speicherung eines einzelnen Wertes in einem Array erfolgt nach der Initialisierung:
// Wert in Array speichern myArray[0] = 23; // Wert 23 speichern an erster Position myArray[3] = 12; // Wert 12 speichern an letzter Position
Wie bereits erwähnt, werden Arrays oftmals mittels Schleifen abgefragt oder mit Werten gefüllt. Das Beispiel speichert Zufallswerte in einem Array und gibt diese über die serielle Schnittstelle aus.
Beispiel:
// Zufallszahl in Array speichern int ArrayRandom[4]; // Array mit Zufallszahlen int i; int zufallszahl; void setup() { Serial.begin(9600); // Initialisierung serielle Schnittstelle } void loop() { for (i = 0; i < 5; i = i + 1) { zufallszahl= random(1, 99); // Zufallszahl generieren ArrayRandom[i] = zufallszahl; // Zufallszahl in Array Serial.println(ArrayRandom[i]); } delay(1000); }
Für umfangreichere Datenmengen, beispielsweise beim Einsatz einer LED-Matrix, kann die Struktur eines Arrays um eine zusätzliche Ebene erweitert werden. Das Resultat ist eine mehrdimensionale Tabelle, die jeweils zwei Indexwerte verwendet.
Der generelle Aufbau eines mehrdimensionalen Arrays ist jeweils:
Typ ArrayName[AnzahlEbenen][AnzahlWerte]
Beispiel:
// Array mit 3 Ebenen und jeweils 3 Werten int 2EbenenArray[3][3]; // Initialisierung Array // Werte speichern in Array int 2EbenenArray[3][3] = { { 23, 34, 11}, // erste Ebene { 54, 0, 21}, // zweite Ebene { 128, 76, 9} // dritte Ebene };
Die Datentypkonvertierung benötigt man oftmals in der Praxis, um beispielsweise seriell empfangene Daten in einen Integerwert umzuwandeln.
Die folgende Tabelle A.1 zeigt verschiedene Varianten der Typkonvertierung:
In einem Programm werden Werte, die zur Weiterbearbeitung gespeichert sind, mit einem Bezeichner benannt. Der Bezeichner, also der Variablenname , sollte so gewählt werden, dass er innerhalb des Programms gut lesbar und verständlich ist.
Der Wert einer Variablen kann sich laufend ändern oder durch das Programm verändert werden.
Eine Variable besitzt neben dem Variablennamen auch einen Datentyp, der den Wertebereich definiert.
Bei der Variablendeklaration, die am Anfang des Programms erfolgt, wird der Datentyp, der Variablenname und der Wert der Variablen gesetzt. Wird kein Wert angegeben, so wird der Variablenwert auf 0 gesetzt.
Datentyp Variablenname = Wert;
Beispiel:
int IRAbstand = 453; // Variable IRAbstand als Integer (ganzzahlig)
Verständliche Variablennamen wie AbstandSensor
oder EingabePin
verbessern die Lesbarkeit eines Programms.
// Ideale Variablennamen int AbstandSensor = 100; int EingabePin = 2; float TempWertAussen = 32.45; // Schlecht gewählte Variablennamen int x = 123; float valy = 34.45;
Konstanten sind spezielle Variablen, die ihren Wert während des gesamten Programmablaufs behalten. Sie werden verwendet, um fixe Werte einmalig zu deklarieren. Diese Konstanten können dann innerhalb des Arduino-Programms aufgerufen und verwendet werden.
Konstanten werden mit der Anweisung const
definiert und können im Programmablauf nicht überschrieben werden.
// Deklaration einer Konstanten const int GPSLED = 5; // LED für Anzeige von GPS-Empfang an Pin 5
Die Konstantendeklaration wird üblicherweise zur Definition von Portnummern, Werten von Konfigurationsparametern oder fixen Werten für Kommandos oder Zeiten verwendet.
Beispiele:
// Konstanten für Portnummer const int GPSLED = 5; // LED für Anzeige von GPS-Empfang an Pin 5 digitalWrite(GPSLED, LOW); // LED ist aus // Konstante für Zeitverzögerung const int zeitverzoegerung = 1000 // 1000 ms Verzögerung delay(zeitverzoegerung);
Neben den eigenen Definitionen von Konstanten kennt die Arduino-Syntax noch eine Anzahl von vordefinierten Konstanten.
Diese beiden booleschen Konstanten definieren logische Pegel. Die Konstante false
besitzt immer den Wert 0. Der Wert der Konstanten true
dagegen beträgt »alles außer 0«, meist wird jedoch 1 verwendet. Aber auch Werte von 2, 10 oder 100 werden als true
bewertet.
Beispiel:
// true/false if (EndSchalter1 == true) { digitalWrite(MotorPin, LOW); // Motor aus }
Mit diesen beiden Konstantenwerten werden die Zustände von Ein- und Ausgängen gelesen oder geschrieben. HIGH ist jeweils der Logiklevel 1, 5 V oder EIN. LOW entspricht 0 oder AUS.
Beispiel:
// Konstanten HIGH/LOW // digitale Ausgänge digitalWrite(9, LOW); // Ausgangsport 9 aus digitalWrite(8, HIGH); // Ausgangsport 8 ein // digitale Eingänge EingangSchalter = digitalRead(2) // Wert des digitalen Eingangs 2 lesen
Zu beachten ist, dass diese beiden Konstanten immer in GROSSBUCHSTABEN geschrieben werden müssen.
Kontrollstrukturen steuern den Programmfluss und werden bei Entscheidungen eingesetzt.
Eine if
-Kontrollstruktur wird für Entscheidungen verwendet und prüft, ob eine Bedingung erfüllt ist.
if (status == 1) { digitalWrite(8, HIGH); }
Man beachte die Schreibweise mit den doppelten Gleichheitszeichen. Ein einfaches Gleichheitszeichen bedeutet nämlich eine Zuordnung eines Wertes.
Status=1 // Variable Status bekommt Wert 1
Mit der zusätzlichen else
-Erweiterung ergibt sich eine Entweder/Oder
-Entscheidung.
if (statusPin1 == HIGH) { digitalWrite(8, HIGH); } else { digitalWrite(8, LOW); }
Die for
-Schleife erlaubt das definierte Wiederholen einer Liste von Anweisungen.
for (int i=0; i <= 255; i++) { analogWrite(PWMOut, i); delay(100); }
Das Beispiel zählt die Variable i
bis zum Wert 255 hoch und gibt jeweils den Wert als PWM-Signal (PWM = Pulsweitenmodulation) aus.
Wiederholtes Ausführen einer Schleife, bis eine Bedingung erfüllt ist.
wert = 0; while(wert < 200) { // Anweisung wert++; }
Die Schleife wird ausgeführt, bis Wert = 200 ist, wobei der Wert bei jedem Schleifendurchgang um 1 erhöht wird.
Ähnlich der while
-Schleife, aber bei der do ... while
-Schleife erfolgt die Prüfung der Bedingung erst am Ende.
do { temp = analogRead(tempPin); } while (temp < 40);
Das Ausführen der Schleife findet statt, solange der Wert von temp
kleiner als 40 ist.
Diese Kontrollstruktur vergleicht einen Wert mit einer Reihe von Werten. Entspricht der Wert einem Wert aus der Reihe, so wird dieser Fall (case
) ausgeführt.
switch (temp) { case 20: // Temp ist genau 20 digitalWrite(LEDgelb, HIGH); break; case 25: // Temp ist genau 25 digitalWrite(LEDgelb, HIGH); break; default: // Falls kein anderer Fall ausgeführt wird digitalWrite(LEDgruen, HIGH); }
Mit der Anweisung break
kann aus einer Schleife oder einem switch
-Fall ausgestiegen werden.
for (int i = 0; i < 255; i ++) { digitalWrite(PWMPin, i); // Stop-Eingang abfragen stopInp = digitalRead(stopPin); // falls Stop = HIGH, aussteigen if (stopInp == HIGH) { digitalWrite(PWMPin, 0); break; } delay(20); }
Die Schleife wird unterbrochen, falls stopInp
high
ist.
Unterbricht eine Schleife und springt wieder zur Prüfung der Bedingung.
for (int i = 0; i < 255; i++) { // falls i zwischen 50 und 100, i nicht ausgeben if (i > 50 && i < 100) { continue; } digitalWrite(PWMPin, i); delay(20); }
Beendet eine Funktion und gibt einen Wert zur Weiterverarbeitung im übergeordneten Programm zurück.
int getTemperatur(inPin) { int valTemp=0; valTemp=analogRead(inPin); return valTemp; }
Ermittelt das Minimum zweier Werte und gibt den kleineren Wert zurück.
wert = min(wert, 88);
Setzt die Variable wert
so, dass wert
nie größer als 88 werden kann.
Ermittelt das Maximum zweier Werte und gibt den höheren zurück.
wert = max(wert, 200);
Setzt die Variable wert
so, dass wert
nie kleiner als 200 werden kann.
Für das Begrenzen eines Wertes oder eines Bereichs kann auch die Funktion constrain()
verwendet werden.
Gibt den Absolutwert einer Zahl zurück.
int z =100; int x = abs(z); // x = 100 int a = -100 int b = abs(a); // b = 100
Mit dieser Funktion kann ein Wert so gesetzt werden, dass er immer in einem definierten Bereich liegt.
Die Funktion gibt folgende Werte zurück:
Zahl
, wenn Zahl
zwischen Minimal
und Maximal
liegt.
Minimal
, wenn Zahl
kleiner als Minimal
ist.
Maximal
, wenn Zahl
größer als Maximal
ist.
// Bereichsbegrenzung int xVal = 80; xVal = constrain(xVal, 100, 200); // xVal ist immer im Bereich von 100 bis 200
Diese Funktion kann einen Wertebereich (fromLow
, fromHigh
) in einen anderen Wertebereich (toLow
, toHigh
) konvertieren. Ein Temperaturwert von 20 bis 80 Grad kann auf diese Weise inso beispielsweise auf einen Bereich von 0 bis 100 Prozent abgebildet werden. Dabei wird eine Temperatur von 20 zu 0 und der Endwert von 80 zu 100. Eine weitere Möglichkeit ist die Invertierung des Bereichs, indem ein Wertebereich von 0 bis 100 zu 100 bis 0 konvertiert wird.
In der Praxis wird map()
oft eingesetzt, um einen eingelesenen Analogwert (10 Bit) in einen 8-Bit-Wert für die Ausgabe als PWM zu konvertieren.
// Temperatursensor einlesen, Wert 0-1023 int tempVal = analogRead(5); // Konvertierung 0-1023 zu 0-255 int tempValOut = map(tempVal, 0, 1023, 0, 255); // Ausgabe des aktuellen Wertes als PWM-Signal analogWrite(9, tempValOut);
Funktion zur Ausgabe der Potenz einer übergebenen Zahl.
float x = (analogRead(5)); y = pow(x,2.0);
Gibt das Quadrat einer Zahl zurück.
Berechnet die Wurzel einer übergebenen Zahl.
Berechnung des Sinus eines Winkels im Bogenmaß. Der Rückgabewert liegt zwischen -1 und +1.
Berechnung des Cosinus eines Winkels im Bogenmaß. Der Rückgabewert liegt zwischen -1 und +1.
Berechnung des Tangens eines Winkels im Bogenmaß.
Die Generierung einer Zufallszahl erfolgt mittels des Pseudo-random-number-generators (PRNG). Dieser Generator ermittelt die Zufallszahl algorithmisch. Eine echte Zufallszahl wird generiert, indem der prng mit einer Zahl initialisiert wird und dieser daraus eine Zufallszahl erstellt. Die echte Zufallszahl kann beispielsweise ein Analogwert sein.
Initialisieren des PRNGs mit einer Zahl.
Erstellen einer Pseudo-Zufallszahl.
// Initialisieren des PRNG randomSeed(analogRead(0)); // Zufallszahl zwischen 0 und 100 randZahl = random(100); // Zufallszahl 1 und 49 randZahl = random(1, 49);
Durchführen von Addition , Subtraktion , Multiplikation und Division .
y = y + 3; // Addition x = x - 7; // Subtraktion i = j * 6; // Multiplikation r = r / 5; // Division a = 6 % 4; // Modulo (Rest bei ganzzahliger Division)
Bei Vergleichsoperationen werden zwei Variablen oder Konstanten miteinander verglichen.
a == b // a ist gleich b a != b // a ist nicht gleich b a < y // a ist kleiner als b a > b // a ist größer als b a <= b // a ist kleiner oder gleich b a >= b // a ist größer oder gleich b
Gemischte Zuweisungen sind eine Art Kurzschreibweise für arithmetische Operationen und Variablenzuweisungen.
x++; // erhöht x um 1, entspricht x = x + 1 x--; // vermindert x um 1, entspricht x = x - 1
x += y; // entspricht x = x + y x -= y; // entspricht x = x - y x *= y; // entspricht x = x * y x /= y; // entspricht x = x / y
Mit den logischen Operatoren werden meist zwei Werte oder Ausdrücke miteinander verglichen. Das Resultat ist entweder true
oder false
. Die drei logischen Operatoren and
, or
und not
werden normalerweise in if
-Anweisungen verwendet.
Das Resultat ist true , wenn beide Werte true sind.
// Werte prüfen, true, wenn temp zwischen 15 und 25 liegt if (temp >= 15 && temp <= 25) { // Weitere Anweisungen } // Eingänge prüfen if (InpPin4 == HIGH && InpPin5 == HIGH) { // Weitere Anweisungen }
Das Resultat ist true , wenn einer der Werte true ist.
if (a > 10 || b > 10) { // Weitere Anweisungen }
Das Resultat wird true , wenn der Ausdruck false ist.
if (!x > 5) { // Weitere Anweisungen }
Konfiguriert einen digitalen Port als Ein- oder Ausgang.
pinMode (8, OUTPUT); // Port 8 als Ausgang pinMode (9, INPUT); // Port 9 als Eingang
Liest den Wert des digitalen Eingangs ein.
InpPin = digitalRead(4); // Einlesen von Port 4, Wert wird in der Variablen // InpPin abgelegt
Setzt den Ausgang auf 1 oder 0, entspricht high oder low .
digitalWrite(8, HIGH); // Ausgang 8 EIN digitalWrite(9, LOW); // Ausgang 9 AUS
Diese Funktion misst die Dauer eines am Eingang anliegenden Eingangspulses. Dabei kann die Pulszeit zwischen 10 Mikrosekunden und 3 Minuten liegen.
Der Wert high oder low gibt an, ob man einen high -Puls oder einen low -Puls messen möchte. Bei high wird darauf gewartet, dass das am Port anliegende Signal auf high wechselt, dann beginnt die Zeitmessung, die endet, wenn das Signal wieder low ist. Die gemessene Zeit wird in Mikrosekunden angegeben.
Die Angabe der Zeitüberschreitung (Timeout), also wie viele Mikrosekunden auf das Signal gewartet werden soll, ist optional. Als Standardwert ist 1 Sekunde definiert.
int PulsPin = 2; unsigned long zeitdauer; zeitdauer = pulseIn(PulsPin, HIGH);
Analoge Eingänge arbeiten mit einer Auflösung von 10 Bit, das entspricht einem Bereich von 0 bis 1023. Das Eingangssignal liegt dabei im Bereich von 0 bis 5 Volt.
tempIn = analogRead(0); // Analogport 0 einlesen, Wert
// in Variable tempIn ablegen
Als analoge Eingänge können folgende Ports verwendet werden:
(0–5): Arduino-Standardboards
(0–7): Arduino Mini und Nano
(0–15): Arduino Mega
Das Ausgeben einer Spannung an den analogen Ausgängen wird mittels Pulsweitenmodulation (PWM ) realisiert. Die Grundfrequenz liegt dabei bei ungefähr 490 Hertz. Der Wert liegt im Bereich von 0 bis 255, das entspricht 8 Bit.
analogWrite(10, 123); // Ausgeben eines analogen Wertes an Port 10
Zur analogen Ausgabe sind folgende Ports verwendbar:
(3, 5, 6, 9, 10, 11): Arduino-Standardboards
(2–13): Arduino Mega
Ausgabe eines Rechtecksignals mit einstellbarer Frequenz und einer Pulsweite von 50%. Das Ausgangssignal kann direkt an einem Piezo-Alarmgeber oder einem Lautsprecher angeschlossen werden.
Durch die Eingabe der Nummer des Ports, an dem ein Lautsprecher angeschlossen ist, und der Frequenz in Hertz kann ein Ton erzeugt werden. Wird zusätzlich noch die optionale Dauer in Millisekunden (ms) angegeben, stoppt die Tonausgabe nach der definierten Zeit.
Eine praktische Anwendung des Befehls und Ausgangspunkt für weitere Experimente ist das Beispiel aus dem Arduino Playground.
Stoppt die Ausgabe des Rechtecksignals, das mit tone()
gestartet wurde.
Bei einem Interrupt wird durch ein Ereignis, beispielsweise ein Signal von außen, das Hauptprogramm gestoppt und ein anderes Programm ausgeführt. Das externe Signal kann zum Beispiel ein kurzer Puls von einem sich drehenden Magneten sein. Damit jedes Signal des sich drehenden Magneten auch erfasst wird, kann damit ein Interrupt ausgelöst werden, der einen Zähler um 1 erhöht.
Diese Funktion löst mittels eines Signals an einem definierten digitalen Port einen Interrupt aus, der eine wählbare Programmfunktion aufruft.
Die Arduino-Standardboards können zwei Interrupts erfassen, die die Nummer 0 (angeschlossen an Pin 2) und die Nummer 1 (angeschlossen an Pin 3) besitzen.
Diese Funktion wird ausgeführt, wenn das Eingangssignal an den erwähnten Ports erkannt wird.
Dieser Parameter gibt an, bei welcher Signaländerung ein Interrupt ausgelöst werden soll.
Funktion wird bei ansteigendem Signal an Pin 2 ausgeführt.
attachInterrupt(0, alarm, RISING); void alarm() { // Anweisungen }
Schaltet den Interrupt aus.
InterruptNummer: 0 oder 1 (bei Arduino-Standardboards)
Bei den Zeitfunktionen unterscheidet man zwischen Funktionen, um Zeiten zu messen und Funktionen, um definierte Pausen (Verzögerungen) einzulegen.
Pausiert ein Programm für die in ms angegebene Zeit. Dabei ergibt die Eingabe von 1000 eine Verzögerung von einer Sekunde.
Dies wird oft in Blinkroutinen für die einzelnen Ein- und Aus-Phasen verwendet.
void loop() { // LED Ein digitalWrite(8, HIGH); // 1 Sekunde warten delay(1000); // LED Aus digitalWrite(8, LOW); // 1 Sekunde warten delay(1000); }
Pausiert ein Programm für eine angegebene Zeit in Mikrosekunden (us). Die Eingabe von 1000 Mikrosekunden ergibt eine Pause von einer Millisekunde.
Der maximale Wert für die Pause kann dabei 16.383 Mikrosekunden betragen, was einer Verzögerung von 16,383 Millisekunden entspricht.
Gibt die Zeit in Millisekunden (ms) seit dem Start des aktuellen Programms zurück. Der Rückgabewert ist eine Zahl des Datentyps unsigned long
.
unsigned long time; // Wert seit Start des Programms time = millis();
Gibt die Zeit in Mikrosekunden (us) seit dem Start des aktuellen Programms zurück. Der Rückgabewert ist eine Zahl vom Datentyp unsigned long
. Nach 70 Minuten erfolgt ein Überlauf (Overflow) und die Zeitmessung beginnt wieder bei 0.
Auf dem Arduino Duemilanove wird die serielle Schnittstelle einerseits zur Kommunikation mit dem angeschlossenen Rechner über den USB-Port und andererseits auf den Ports 0 (rx ) und 1 (tx ) zur externen Kommunikation verwendet.
Initialisieren der seriellen Schnittstelle und Definition der Übertragungsgeschwindigkeit . Über den seriellen Monitor in der Entwicklungsumgebung (IDE) können die übertragenen Daten sichtbar gemacht werden. Dabei muss im seriellen Monitor die entsprechende Übertragungsgeschwindigkeit eingestellt werden.
Übertragungsrate Wertebereich:
300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200
void setup() { // Konfiguration serielle Schnittstelle Serial.begin(9600); // Übertragungsgeschwindigkeit }
Wird die serielle Schnittstelle verwendet, so können die digitalen Ports D0 und D1 nicht für andere Aufgaben genutzt werden.
Der Arduino Mega hat drei zusätzliche serielle Schnittstellen, die an den folgenden Ports betrieben werden:
Serial1: Port 19 (rx ), Port 18 (tx )
Serial2: Port 17 (rx ), Port 16 (tx )
Serial3: Port 15 (rx ), Port 14 (tx )
Somit muss jede der vier seriellen Schnittstellen auf dem Arduino Mega einzeln initialisiert werden.
// Arduino Mega void setup() { // Konfiguration der seriellen Schnittstellen Serial.begin(9600); Serial1.begin(38400); Serial2.begin(115200); Serial3.begin(9600); }
Die Übertragungsgeschwindigkeiten der vier seriellen Schnittstellen können dabei unterschiedlich sein.
Beendet die serielle Funktion auf den Ports 0 und 1. Die beiden digitalen Ports können anschließend wieder für andere Anwendungen genutzt werden.
Diese Funktion prüft, ob im Empfangspuffer
der seriellen Schnittstelle Daten vorhanden sind. Der Rückgabewert ist die Anzahl der vorhandenen Bytes. Falls Daten vorhanden sind, können diese nun mit read()
aus dem Puffer gelesen werden. Zu beachten ist, dass der Empfangspuffer eine maximale Größe von 128 Bytes besitzt. Darum muss im Programm sichergestellt sein, dass die Daten des Empfangspuffers regelmäßig ausgelesen werden.
// prüfen, ob Daten empfangen wurden if (Serial.available() > 0) { // Daten aus Eingangspuffer einlesen empfangeneDaten = Serial.read(); // Ausgabe von Infomeldung Serial.print("Du hast Daten empfangen"); }
Diese Funktion liest das nächste Zeichen aus dem Empfangspuffer der seriellen Schnittstelle. Falls keine Daten empfangen wurden, gibt die Funktion den Wert -1 zurück.
empfangeneDaten = Serial.read();
Sendet Daten als ASCII-Zeichen zur Ausgabe an die serielle Schnittstelle. Dabei können verschiedene Ausgabeformate und -typen ausgegeben werden.
Beispiel von Ausgabemöglichkeiten :
Ausgabeanweisung |
Ausgabeformat |
---|---|
Serial.print(45) |
"45" |
Serial.print(1.23456) |
"1.23" |
Serial.print('A') |
"A" |
Serial.print("Hallo Arduino.") |
"Hallo Arduino." |
Mit einem optionalen zweiten Parameter kann das Ausgabeformat gesteuert werden.
Ausgabeanweisung |
Ausgabeformat |
---|---|
Serial.print(65, BIN) |
"1000001" |
Serial.print(65, OCT) |
"101" |
Serial.print(65, DEC) |
"65" |
Serial.print(78, HEX) |
"41" |
Serial.println(1.23456, 0) |
"1" |
Serial.println(1.23456, 2) |
"1.23" |
Serial.println(1.23456, 4) |
"1.2346" |
Sendet Daten mit einem anschließenden Zeilenumbruch (Carriage Return und Linefeed) an die serielle Schnittstelle. Das Carriage Return entspricht dem ASCII-Zeichen 13 oder »\r«, ein Linefeed entspricht dem ASCII-Zeichen 10 oder »\n«.
Formatangaben sind entsprechend den bei der print()
-Funktion aufgeführten Beispielen möglich.
Schreibt binäre Daten auf die serielle Schnittstelle. Dabei werden die Daten als einzelne Bytes oder in Form mehrerer Bytes in einem Puffer versendet.
Wert: Wert als Byte
String: String in Form von mehreren Bytes
Puffer: Array in Form von mehreren Bytes
Länge: Größe des Arrays
// Versenden von Byte mit Wert 45 Serial.write(45); //Versenden von String "Hallo", Rückgabewert ist die Länge des Strings int bytesSent = Serial.write("Hallo");