Der Arduino arbeitet mit einer plattformangepassten C-Sprache. Dieser Artikel führt daher in die Grundlagen der Programmierung in C ein und zeigt deren praktische Anwendung am Beispiel von I/O-Ports.
Grundlegendes Arduino-Software-Framework
Jedes Computerprogramm ist eine Sammlung von Befehlen. Ein spezieller Schaltkreis, der Befehlszähler, ruft die Befehle einen nach dem anderen auf. In der Sprache C werden alle Befehle, die wir ausführen wollen, in der Main-Funktion platziert (mehr über Funktionen später), die wie folgt aussieht:
int main() {
//Inhalt des Programms
}
Hinweis: Das musst du dir merken!
Die Symbole „//“ kennzeichnen Kommentare. Das sind Informationen, die in einer einzigen Zeile enthalten sind und dem Benutzer helfen, das Programm zu verstehen. Sie werden beim Kompilieren weggelassen. Wenn Sie einen längeren Kommentar einfügen wollen, sollten Sie ihn /* in solche Symbole */ einfügen.
Kommentare sind sehr wichtig! Denk daran, den Code zu kommentieren, auch wenn du nur Programme für deinen eigenen Gebrauch schreibst.
Beim Arduino sind bestimmte Dinge vereinfacht. Nun, in jedem Programm werden zunächst einige Befehle einmal ausgeführt und dann andere in einer Schleife.
void setup() {
//Befehle, die einmal ausgeführt werden
}
void loop() {
//Befehle, die in einer Schleife ausgeführt werden
}
In der zweiten Funktion platzieren wir den eigentlichen Anwendungscode, der ständig ausgeführt wird (in einer Schleife). Anhand der folgenden praktischen Beispiele lässt sich dies viel leichter nachvollziehen.
Funktionen - was bedeuten diese Einträge?
In der Sprache C gibt es das Konzept der Funktionen. Die Parallele zur Mathematik ist hier treffend. Nun, es stellt sich heraus, dass eine Funktion in Programmiersprachen ein Block (Liste) von bestimmten, aus dem Hauptcode extrahierten Befehlen ist, deren Ausführung ein bestimmtes Ergebnis liefert.
Für uns ist das praktisch, denn sobald eine Funktion definiert ist, können wir sie beliebig oft aufrufen, ohne mühsam dieselben Codezeilen zu duplizieren – wir brauchen nur den Funktionsnamen.
Konzentrieren wir uns auf die wichtigsten Funktionen in Programmen, die mit dem Arduino geschrieben wurden.
void setup() {
}
Die Setup-Prozedur ist so konzipiert, dass der in ihr enthaltene Befehlsblock einmal ausgeführt wird. Wie der Name schon sagt, ist sie hauptsächlich für die Einrichtung gedacht. In ihr wird der Prozessor initialisiert, die Peripheriegeräte werden konfiguriert usw.
void loop() {
}
Eine Loop-Funktion (Prozedur) ist eine Endlosschleife. Sie enthält Anweisungen, die in einer Schleife ausgeführt werden sollen (immer wieder). Kommen wir nun zu praktischen Beispielen.
Fertige Sets für Forbot-Kurse
Sie können jetzt ein Set von mehr als 70 Elementen, die für die Kursübungen notwendig sind, bei unseren Händlern kaufen!
Beliebtes Paket: Arduino Meister – Robotik Meister
ArduinoUNO-Anschlüsse
Wie im vorigen Teil erwähnt, können über spezielle Anschlüsse externe Komponenten wie Dioden und Taster an den Arduino angeschlossen werden. Bevor wir dazu kommen, müssen wir jedoch die Beschreibung der Pinbelegung kennen. Unten sehen Sie eine Zeichnung mit den wichtigsten Signalen, die aus dem Arduino UNO kommen:
Die analogen Eingänge (A0-A5) sind hellgrün hervorgehoben. Dies sind einzigartige Pins, die eine Spannungsmessung (im Bereich von 0-5V) ermöglichen. Wie man sehen kann, stimmt die Nummerierung dieser Eingänge mit den Universalpins (Nummern 14 bis 19) überein. Der analoge Betrieb ist eine zusätzliche Funktion dieser Pins.
Denke daran: Die analogen Pins A0-A5 können auch als normale I/O-Pins arbeiten.
Alternative Funktionen für einzelne Signale sind blau hervorgehoben. Das bedeutet, dass sie nicht nur eine Standardeingabe oder -ausgabe sein können, sondern auch komplexere Funktionen erfüllen können. Auf diese werden wir später eingehen, im Moment reicht eine grundlegende Erläuterung aus:
- SDA, SCL – Pins des I²C-Busses, die z.B. für die Kommunikation mit fortschrittlicheren Sensoren verwendet werden, die Pins sind dupliziert (sie befinden sich in der unteren linken und oberen rechten Ecke der Platine – es sind genau die gleichen Signale),
- TX, RX – UART-Schnittstelle, hauptsächlich für die Kommunikation mit dem Computer verwendet,
- PWM – Pins, an denen man ein rechteckiges Signal mit variabler Füllung erzeugen kann. Eine sehr nützliche Funktion z.B.: bei der Steuerung von Servomechanismen,
- LED – eine Leuchtdiode, die fest in den Arduino integriert ist und an Pin 13 angeschlossen wird.
Die in der obigen Grafik als ICSP bezeichneten Anschlüsse dienen der direkten Programmierung der beiden Mikrocontroller, die sich auf dem Arduino UNO-Board befinden. Diese Anschlüsse werden in sehr speziellen Fällen verwendet und es ist absolut nicht notwendig, sie in diesem Stadium zu behandeln.
Ausgänge in der Praxis - LED
Die Schaltung sollte wie unten gezeigt angeschlossen werden. Wir schließen die LED in Reihe mit einem Widerstand (330R) an. Das längere Bein der Diode (die Anode) wird dann an Pin 8 angeschlossen, das andere Bein über einen Widerstand an Masse, die sich im Stromanschluss befindet (als GND bezeichnet). Auf der Platine gibt es 3 Leitungen, die als GND bezeichnet sind. Wir können jede davon wählen.
ACHTUNG!
Denken Sie daran, die meisten Peripheriegeräte (LEDs, Buzzer) über Widerstände an den Arduino anzuschließen! Entsprechende Informationen finden Sie in unseren Übersichtszeichnungen.
Wenn ein Widerstand fehlt, könnte dies zu einer BESCHÄDIGUNG der angeschlossenen Komponenten und sogar des Arduinos führen!
Die Software-Aktivierung der Diode ist sehr einfach. Schließe den Arduino über ein USB-Kabel an den Computer an. Starte Arduino-IDE und schreibe den unten stehenden Code ab. Lade ihn dann auf das Board hoch. Eine Beschreibung dieses Schrittes findest du im ersten Teil des Kurses.
Versuche, die Codes abzuschreiben! Kopieren ist schneller, aber man lernt weniger!
void setup() {
pinMode(8, OUTPUT);
digitalWrite(8, HIGH);
}
void loop() {
}
Mit der Funktion pinMode (Pin, Modus) können Sie festlegen, ob ein Pin ein Eingang oder ein Ausgang ist. Der Pin kann eine ganze Zahl zwischen 0 und 13 sein, der Modus ist:
- INPUT,
- OUTPUT,
- INPUT_PULLUP.
Mit dieser Konfiguration können wir den logischen Zustand am Ausgang einstellen (und damit die Diode einschalten). Zu diesem Zweck wird die Funktion digitalWrite(Pin, Zustand) verwendet. Zustand ist ein logischer Zustand, der HIGH oder LOW sein kann.
In unserem Beispiel ist die LED bereits mit der Masse verbunden, so dass der Arduino sie in einen hohen Zustand bringen muss, also digitalWrite(8, HIGH);.
Beenden Sie jeden Befehl mit einem Strichpunkt (Semikolon)!
Sobald der Pin einmal auf „high“ gesetzt wurde, ändert sich sein Wert nicht mehr, bis wir ihn selbst auf einen anderen Wert setzen. Daher lässt ein Programm wie das obige die LED ständig leuchten.
Programmverzögerungen - LED blinkt
Dieses Mal werden wir die LED blinken lassen. Dazu brauchen wir eine neue Funktion, deren Aufgabe es sein wird, eine Verzögerung einzuführen – Delay. Der Schaltplan ist genau der gleiche wie im ersten Fall. Allerdings wird der Code wie folgt aussehen:
void setup() {
pinMode(8, OUTPUT); //Konfiguration von Pin 8 als Ausgang
}
void loop() {
digitalWrite(8, HIGH); //Diode einschalten
delay(1000); //1 Sekunde warten
digitalWrite(8, LOW); //Diode ausschalten
delay(1000); //1 Sekunde warten
}
Der Zustand des Ausgangs ändert sich hier ständig in einer Endlosschleife. Im Programm wurden mit Hilfe der Funktion delay(Zeit) Verzögerungen eingefügt (damit das Blinken sichtbar ist). Diese Funktion erhält als Argument die Anzahl der zu wartenden Millisekunden.
Ohne die eingefügten Verzögerungen würde die Schaltung den Zustand ihres Ausgangs so schnell ändern, dass es unmöglich wäre, die Änderungen mit dem bloßen Auge zu erkennen. Man kann ein solches Experiment durchführen, indem man die Verzögerung auf 0 ms setzt.
Hausaufgabe 1.1
Finde heraus, bei welchem kleinsten Verzögerungswert du das Blinken der Diode wahrnehmen kannst! Was passiert, wenn die Diode zu schnell blinkt?
Hausaufgabe 1.2
Wähle einen freien Pin und schließe daran eine zweite LED an. Schreibe ein Programm, das beide LEDs zum Leuchten bringt. Dann schreibe ein Programm, das beide LEDs abwechselnd blinken lässt.
Eingänge der Schaltung in der Praxis - bedingte Anweisung
Dies soll nach dem unten stehenden Beispiel geschehen. Eine Seite des Schalters ist mit der Masse (Minus) verbunden, die andere mit Anschluss Nr. 7.
Achtung!
Die Schalter, die den Sets beiliegen, haben 2 statt 4 Anschlüsse, damit sie besser auf die Kontaktplatte passen. Man muss sich keine Sorgen machen, dass der Schalter auf der Grafik 4 Ausgänge hat.
Kommen wir nun zur Umsetzung der Aufgabe. Wir erwarten, dass sich das Programm ständig in einem von zwei Zuständen befindet – LED an oder aus. Zunächst ist es notwendig, den logischen Zustand abzulesen, der an dem Eingang mit dem Schalter auftritt.
Hier triffst du zum ersten Mal auf die Eingangs-Konfiguration. Beachte den bereits erwähnten INPUT_PULLUP-Modus. Wir werden ihn jedes Mal verwenden, wenn wir einen Schalter an den Arduino anschließen.
Der erste Teil des Namens (input) bedeutet natürlich Eingang, während der zweite Teil (pullup) auf die Einschaltung eines internen Pull-up-Widerstands für den Eingang hinweist.
Daher die bedingte Anweisung (allgemein als if oder condition bezeichnet). Diese Anweisung ist sehr beliebt. Sie ermöglicht es uns, einen bestimmten Teil des Codes auszuführen, wenn bestimmte Umstände eintreten. Zum Beispiel: Wenn eine Taste gedrückt wird.
[...]
void loop()
{
//Code, der bei jedem Schleifendurchlauf ausgeführt wird
if ( BEDINGUNG ) {
/* Code, der nur ausgeführt wird, wenn in einer Schleife die BEDINGUNG erfüllt ist */
}
//Code, der bei jedem Schleifendurchlauf ausgeführt wird
}
Diese Anweisung kann sehr einfach um einen Codeteil erweitert werden, die nur ausgeführt wird, wenn die Bedingung nicht erfüllt ist. Hierfür wird die else-Anweisung verwendet.
[...]
void loop()
{
//Code, der in jedem Schleifendurchlauf ausgeführt wird
if ( BEDINGUNG ) {
/* Code, der nur ausgeführt wird, wenn in einem Schleifendurchlauf eine BEDINGUNG erfüllt ist */
} else {
/* Code, der nur ausgeführt wird, wenn eine Bedingung in einem Schleifendurchlauf NICHT erfüllt ist*/
}
//Code, der in jedem Schleifendurchlauf ausgeführt
}
Durch die Kombination der gewonnenen Erkenntnisse können wir ein Programm erstellen, das unsere Aufgabe erfüllt. Analysiert es und ladet es dann auf den Arduino hoch.
void setup() {
pinMode(8, OUTPUT); //Diode als Ausgang
pinMode(7, INPUT_PULLUP); //Taste als Eingang
digitalWrite(8, LOW); //Diode ausschalten
}
void loop()
{
if (digitalRead(7) == LOW) { //Wenn Taste gedrückt
digitalWrite(8, HIGH); //Diode einschalten
} else { //Wenn Bedingung nicht erfüllt ist (Taste nicht gedrückt)
digitalWrite(8, LOW); //Diode ausschalten
}
}
Ein solches Programm ist jedoch wenig nützlich. Wozu brauchen wir einen Lichtschalter, der nur funktioniert, wenn wir ihn mit dem Finger gedrückt halten? Wäre es nicht besser, wenn bei Betätigung des Schalters die LED für eine bestimmte Zeit aufleuchtet?
Beispiel - Lichtschalter mit "Timer"
Kannst du ein solches Programm schon selbst schreiben? Ich hoffe es! Wenn du irgendwelche Probleme hast, kannst du dir meinen Code ansehen:
Beispiel - Verkehrsampeln
Unsere nächste Schaltung wird eine geschaltete Verkehrsampel sein. Unser Hauptziel ist es, ein Programm zu schreiben, das beim Drücken einer Taste die nächste korrekte Ampelsequenz darstellt. Gehen wir von einem solchen Ampelzyklus aus:
void setup() {
pinMode(8, OUTPUT); //Diode als Ausgang
pinMode(7, INPUT_PULLUP); //Taste als Eingang
digitalWrite(8, LOW); //Diode ausschalten
}
void loop()
{
if (digitalRead(7) == LOW) { //Wenn Taste gedrückt
digitalWrite(8, HIGH); //Diode einschalten
delay(10000); //10 Sekunden warten
digitalWrite(8, LOW); //Diode ausschalten
}
}
[…] -> Grün -> Orange -> Rot -> Rot + Orange[…].
Wenn wir die Taste drücken, sollte das System die Lichter in die nächste Sequenz schalten. Wir werden dies in mehreren Schritten erreichen. Zuerst verbinden wir die 3 LEDs und den Schalter wie unten gezeigt.
Bereiten wir die Grundstruktur des Programms vor, mit dem wir arbeiten werden. Seine Aufgabe besteht lediglich darin, die Ein- und Ausgänge zu konfigurieren.
void setup() {
pinMode(10, OUTPUT); //Rote Diode
pinMode(9, OUTPUT); //Gelbe Diode
pinMode(8, OUTPUT); //Grüne Diode
pinMode(7, INPUT_PULLUP); //Taste
digitalWrite(10, LOW); //Diode ausschalten
digitalWrite(9, LOW);
digitalWrite(8, LOW);
}
void loop()
{
//Hier wird unser Programm sein
}
Vergessen wir nun den Schalter und schreiben wir ein Programm, das die Lichter automatisch jede 1 Sekunde wechselt. Es sollte wie das folgende aussehen:
void setup() {
pinMode(10, OUTPUT); //Rote Diode
pinMode(9, OUTPUT); //Gelbe Diode
pinMode(8, OUTPUT); //Grüne Diode
pinMode(7, INPUT_PULLUP); //Taste
digitalWrite(10, LOW); //Diode ausschalten
digitalWrite(9, LOW);
digitalWrite(8, LOW);
}
void loop()
{
digitalWrite(10, LOW); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, HIGH); //Grün
delay(1000); //1 Sekunde warten
digitalWrite(10, LOW); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //1 Sekunde warten
digitalWrite(10, HIGH); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //1 Sekunde warten
digitalWrite(10, HIGH); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //1 Sekunde warten
}
Lade das Programm auf den Arduino hoch und prüfe, ob es funktioniert. Wir müssen sicherstellen, dass alles richtig angeschlossen ist, bevor wir weitermachen.
While-Schleife
Bisher haben wir nur die notwendige „Schleife“ im Arduino loop()-Code verwendet. Jetzt ist es an der Zeit, etwas über Schleifen zu lernen, die wir innerhalb unserer Programme verwenden können.
Wir werden eine Reihe von verschiedenen Schleifen zur Verfügung haben. Wir werden uns in späteren Teilen des Kurses mit ihnen befassen.
Wir werden nun die while()-Schleife besprechen, die so lange läuft, bis eine Bedingung erfüllt ist. Ihre Funktionsweise ist im folgenden Code dargestellt:
[...]
void loop()
{
//Code, der bei jedem Druchlauf der Haupt Loop-Schleife ausgeführt wird
while ( BEDINGUNG ) {
/* Code der in einer Schleife ausgeführt wird, bis die BEDINGUNG nicht mehr erfüllt ist */
}
//Code, der jedes Mal ausgeführt wird,wenn die Haupt Loop-Schleife ausgeführt wird
Nehmen wir die gerade zusammengebaute Schaltung mit der Ampel und schreiben wir ein Programm, das eine LED nur dann blinken lässt, wenn wir den Knopf gedrückt haben.
Wahrscheinlich hast du hier an die bedingte if-Anweisung gedacht. Aber wie willst du das Blinken der LED realisieren? Dies ist, trotz des Anscheins, eine schwierigere Aufgabe als das Dauerlicht, das wir vorhin geschrieben haben.Der beste Weg, dieses Programm zu implementieren, ist die Verwendung einer while()-Schleife, die wie folgt aussieht:
void setup() {
pinMode(10, OUTPUT); //Rote Diode
pinMode(9, OUTPUT); //Gelbe Diode
pinMode(8, OUTPUT); //Grüne Diode
pinMode(7, INPUT_PULLUP); //Taste
digitalWrite(10, LOW); //Dioden ausschalten
digitalWrite(9, LOW);
digitalWrite(8, LOW);
}
void loop() {
while (digitalRead(7) == LOW) { // Wenn die Taste gedrückt ist
digitalWrite(10, LOW); //Rot aus
delay(1000);
digitalWrite(10, HIGH); //Rot an
delay(1000);
}
}
Diesmal sollen die Sequenzen so lange angezeigt werden, bis wir den Schalter drücken (dann soll der Wechsel erfolgen). Dabei gehen wir davon aus, dass wir die Taste sehr schnell drücken und wieder loslassen. Das fertige Programm sollte so aussehen wie das untenstehende:
void setup() {
pinMode(10, OUTPUT); //Rote Diode
pinMode(9, OUTPUT); //Gelbe Diode
pinMode(8, OUTPUT); //Grüne Diode
pinMode(7, INPUT_PULLUP); //Taste
digitalWrite(10, LOW); //Ausschalten der Dioden
digitalWrite(9, LOW);
digitalWrite(8, LOW);
}
void loop()
{
digitalWrite(10, LOW); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, HIGH); //Grün
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, LOW); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, HIGH); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, LOW); //Grün
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, HIGH); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
}
In diesem Fall wurde die Schleife auf eine recht seltsame Weise verwendet. Wie man sieht, befindet sich nichts in den Klammern! Warum funktioniert das Programm dennoch? Weil das Programm Schleifen benutzt, um zu stoppen.
Wie funktioniert das?
Wir starten die LED-Leuchten nach einer bestimmten Sequenz,
wir gehen in die while()-Schleife, die sich direkt darunter befindet,
die Schleife ist leer, also läuft das Programm im Kreis, und…. tut nichts,
erst wenn die Taste gedrückt wird (Bedingung nicht erfüllt), verlässt das Programm die Schleife,
eine weitere Sequenz wird eingeschaltet und die Situation wiederholt sich.
Schauen wir uns an, wie das Programm in der Praxis funktioniert!
Was passiert da eigentlich? Funktioniert alles so, wie es soll? Nein! Auch wenn wir die Taste nur ganz kurz drücken, funktioniert das Programm manchmal richtig und manchmal springt es ein paar Positionen weiter. Warum passiert das?
Wie du dich aus dem ersten Teil erinnern solltest, führt der Prozessor, um es einfach auszudrücken, etwa 16 Millionen Operationen pro Sekunde aus. Folglich schafft er es, alle Zustände unserer Signalisierung zu durchlaufen (und das nicht nur einmal…), wenn die Taste gedrückt wird. Die verschiedenen Effekte beim Loslassen des Knopfes sind nur das Ergebnis einer zufälligen „Aktion“ in einer bestimmten Sequenz.
Wie lässt sich dieses Problem lösen? Ganz einfach. Es genügt, das Programm so zu überarbeiten, dass der Wechsel der Lichter nicht öfter als z.B. jede Sekunde stattfindet. Wir können dafür die bereits bekannte Funktion delay() verwenden.
void setup() {
pinMode(10, OUTPUT); //Rote Diode
pinMode(9, OUTPUT); //Gelbe Diode
pinMode(8, OUTPUT); //Grüne Diode
pinMode(7, INPUT_PULLUP); //Taste
digitalWrite(10, LOW); //Ausschalten der Dioden
digitalWrite(9, LOW);
digitalWrite(8, LOW);
}
void loop()
{
digitalWrite(10, LOW); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, HIGH); //Grün
delay(1000); //Anhalten des Programms vor Eintritt in die Schleife für 1 Sekunde
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, LOW); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //Anhalten des Programms vor Eintritt in die Schleife für 1 Sekunde
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, HIGH); //Rot
digitalWrite(9, LOW); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //Zatrzymujemy program przed wejsciem do pętli na 1 sekunde
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
digitalWrite(10, HIGH); //Rot
digitalWrite(9, HIGH); //Orange
digitalWrite(8, LOW); //Grün
delay(1000); //Anhalten des Programms vor Eintritt in die Schleife für 1 Sekunde
while (digitalRead(7) == HIGH) {} //Warten auf Tastendruck
}
Jetzt sollte alles richtig funktionieren!
Denkt daran, dass das Set von Komponenten, das für alle Übungen benötigt wird, bei Botland erhältlich ist. Mit dem Kauf der Sets unterstützt ihr zukünftige Veröffentlichungen zu Forbot!
Zusammenfassung
Im nächsten Teil geht es um die Kommunikation mit dem Computer über USB. Dies wird es einfacher machen, spätere, erweiterte Programme zu testen.