Digitale Ausgänge

Programmieren

Das Blink-Beispiel ist zwar ganz nett, um das Arduino-Board zu testen, aber wie funktioniert es eigentlich und wie können wir auch ein komplexeres Programm damit erstellen? Heute geht es um die digitalen Ausgänge des Arduinos.

Im letzten Beispiel haben wir das Blink-Beispiel verwendet, um zu testen, ob unsere Arduino-IDE-Installation korrekt funktioniert. Aber wie funktioniert dieses einfache Programm und was können wir aus diesem Beispiel für unsere eigenen Projekte lernen?

Gucken wir uns zuerst einmal die Grundstruktur des Beispielcodes an. Wie jedes Arduino-Programm besteht er aus zwei Blöcken: Der setup- und der loop-Prozedur.

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Eine Prozedur ist eine Gruppe von Anweisungen, die unter einem menschenlesbaren Namen zusammengefasst wurden. Um die Anweisungen zu gruppieren, verwenden wir geschweifte Klammern { } und erzeugen so einen Codeblock. Der Name wird verwendet, um diesen Codeblock später aufzurufen und auszuführen. In der Welt von Arduino gibt es zwei Prozeduren, die automatisch aufgerufen werden: Die setup- und die loop-Prozedur. Was ist also der Zweck dieser beiden Prozeduren? Die Idee der Arduino-Ersteller ist, dass wir die setup-Prozedur verwenden, um alles zu konfigurieren, was für die Ausführung des eigentlichen Programms benötigt wird. Es ist die Initialisierungsroutine des Programms und sie wird nur einmal ausgeführt, wenn der Arduino durch Neuprogrammierung, Drücken des Resetknopfes oder durch Ein- und Ausschalten der Stromversorgung zurückgesetzt wird. Nachdem die setup-Prozedur fertig ist, führt der Arduino die loop-Prozedur in einer Endlosschleife aus - genau wie es der Name bereits sagt. Ich habe diesem Ablauf im unterstehenden Diagramm einmal aufgemalt.

[flow] st=>start: Start der Programmausführung setup=>operation: setup|success loop=>operation: loop|success

st->setup->loop loop->loop [/flow]

Warum diese Endlosschleife? Nun, die meisten eingebetteten Systeme werden ihre Aufgabe nie wirklich beenden. Sobald eine Sache erledigt ist, warten sie auf neue Benutzereingaben und fahren dann mit dem fort, was der Benutzer von ihnen verlangt. Die Ausführung endet nie – bis Sie den Strom abschalten. Das Schema mit der Endlosschleife passt hier also perfekt.

Übrigens hast du wahrscheinlich bemerkt, dass es im Code auch Erklärungseinschübe gibt, die eine kurze Version dessen enthalten, was diesen beiden Prozeduren machen. Sie werden auch Kommentare genannt und können einen beliebigen Text enthalten, der für den Compiler selbst absolut nichts bedeutet. Kommentare sind nur für andere Menschen gedacht, um ihnen zu helfen, den Code zu verstehen. Ein Kommentar wird durch zwei Schrägstriche eingeleitet: // Kommentartext hier.
Alles, was nach diesen beiden Schrägstrichen in dieser Zeile kommt, wird vom Compiler ignoriert. Es gibt einen alternativen Weg, Kommentare zu erstellen, die über mehrere Zeilen reichen. Dies sieht wie folgt aus:

/*
   Comment text here
 */

Wenn du dir den Beispielcode in der Arduino-IDE ansiehst, wird dieser Kommentartyp für den langen Textteil direkt am Anfang der Datei verwendet. Alles zwischen /* und */ wird vom Compiler ignoriert.

Du wiest jetzt bereits, dass es zwei Codeblöcke gibt, aber was steht darin und warum gibt es leere Klammern nach dem Namen und dieses seltsame void vor dem Namen. Dieses void wird als Rückgabetyp bezeichnet und bedeutet, dass von diesen beiden Prozeduren nichts zurückgegeben wird. Das scheint nicht sehr nützlich zu sein, denkst du dir vielleicht und was bedeutet es, etwas zurückzugeben. In der Mathematik kennen wir nur Funktionen. Eine Funktion nimmt einen oder mehrere Parameter wie x entgegen und erzeugt einen Wert, der in der Programmierung als Rückgabewert bezeichnet wird. Nehmen wir als Beispiel die einfache Funktion f(x) = 2x - wenn du 3 für x einsetzt, ist das Ergebnis 6. In der Programmierung kennen wir diese Funktionen auch, aber wir haben nicht nur Berechnungen, sondern auch Befehle, die sagen, dass dies oder jenes zu tun ist. Im Fall, dass nur Befehle ausgeführt werden, wird es kein Ergebnis geben, das man zurückgeben kann. Genau das ist bei unseren beiden Prozeduren setup und loop der Fall. Prozeduren sind Funktionen, die nichts zurückgeben und daher den Rückgabetyp void haben, was aus dem Englischen kommt und "komplett leer" bedeutet. Prozeduren haben einen leeren Rückgabewert. Genau wie unsere mathematische Funktion können auch Funktionen in der Programmierung Parameter haben. In unserem Fall ist dies nicht der Fall, und deshalb haben wir die leeren Klammern hinter dem Namen unserer Prozeduren. Wenn unsere Prozeduren Parameter benötigen, müssten diese zwischen diesen Klammern deklariert werden. Wir sollten uns hier jedoch nicht zu lange aufhalten. Auf Datentypen, Rückgabetypen und Funktionen werde ich in einem späteren Tutorial ohnehin noch einmal genauer eingehen.

Schauen wir uns nun den Inhalt unserer beiden Codeblöcke an. Da ist ein Haufen sogenannter Anweisungen drin. Es gibt verschiedene Arten von Anweisungen, die in einer Prozedur oder Funktion erlaubt sind. In diesem Beispiel haben wir nur einen Typ: Prozeduraufrufe. Genauso wie wir die beiden Prozeduren setup und loop im Beispielcode definiert haben, gibt es eine Menge anderer Prozeduren, die bereits vordefiniert sind.
Bevor wir darauf eingehen, was diese Prozeduren tun und wie der Beispielcode mit ihnen die LED zum Blinken bringt, möchte ich dich auf ein wichtiges Detail hinweisen: Jede Anweisung wird mit einem Semikolon abgeschlossen. Dies ist wirklich wichtig, da sich der Compiler nicht um die Zeilenumbrüche in unserem Code kümmert, sondern dieses Semikolon verwendet, um zu bestimmen, wo eine Anweisung endet. Wenn du mir nicht glaubst, versuche es einfach selbst und entfernen einen der Zeilenumbrüche. Der Code wird danach immer noch funktionieren. Falls nicht, hast du wahrscheinlich vergessen, auch den Kommentar zu entfernen. Denk daran, dass alles hinter den beiden Schrägstrichen vom Compiler vollständig ignoriert wird.

Welche vordefinierten Prozeduren werden im Blink-Beispiel verwendet? Nun, es gibt drei verschiedene Prozeduren pinMode, digitalWrite und delay, die vom Beispielcode aufgerufen werden. Beginnen wir mit der Prozedur delay, deren Bedeutung nicht schwer zu erraten sein sollte und die auch im Kommentar angegeben ist. Schauen wir uns den Prozeduraufruf an:

  delay(1000);

Eine Prozedur kann aufgerufen werden, indem man ihren Namen schreibt und dann die Parameterwerte in Klammern direkt hinter dem Namen angibt. Wenn eine Prozedur keine Parameter benötigt, setze einfach ein Paar leere Klammern hinter den Namen. Mehrere Parameter können durch ein Komma getrennt werden, wie wir in den anderen Prozeduraufrufen sehen. Die delay-Prozedur hat nur einen einzigen Parameter: Die Wartezeit in Millisekunden.

Die beiden anderen Prozeduren werden zur Steuerung der digitalen Ausgänge unseres Arduino Uno verwendet. Die pinMode-Prozedur wird verwendet, um dem Arduino Uno mitzuteilen, dass wir den mit der LED verbundenen Pin als Ausgang verwenden wollen. Als Parameterwerte werden LED_BUILTIN, was ein Platzhalter für Pin 13 ist, mit dem die interne LED verbunden ist, und OUTPUT, um den Pin-Modus auf digitalen Ausgang zu ändern, angegeben. Du kannst Pins auch als digitalen Eingang verwenden, worauf wir im nächsten Tutorial einen Blick werfen werden. Die Konfiguration der Pins muss nur einmal vorgenommen werden und wird daher in die setup-Prozedur durchgeführt.
Die zweite Prozedur heißt digitalWrite und ändert den Spannungspegel des Pins. Dieser kann entweder 5 V (HIGH) oder 0 V (LOW) betragen. Der Spannungspegel wird als zweiter Parameter der digitalWrite-Prozedur angegeben. Der Erste ist auch hier die Nummer des zu verwendenden Pins.

Was geschieht in der loop-Prozedur? Der erste Aufruf von digitalWrite schaltet die LED ein, indem sie über Pin 13 mit 5 V Spannung versorgt wird. Danach folgt ein Aufruf von delay und das Programm wartet 1 s. Danach erfolgt ein weiterer Aufruf von digitalWrite zum Ausschalten der LED und ein weiterer Aufruf von delay zum Warten von 1 s. Danach wird die gesamte Prozedur wiederholt und die LED wird kontinuierlich ein- und ausgeschaltet. So einfach ist es, wenn man die Syntax einmal verstanden hat.

Abschließend folgt eine kurze Zusammenfassung aller im Beispiel verwendeten Prozeduren:

  • pinMode(pin, mode)
    • pin: Nummer des digitalen Pins (0 bis 13) oder A1 bis A5 für analoge Pins
    • mode: OUTPUT, INPUT, INPUT_PULLUP
  • digitalWrite(pin, value)
    • pin: Nummer des digitalen Pins (0 bis 13) oder A1 bis A5 für analoge Pins
    • value: HIGH oder LOW
  • delay(ms)
    • ms: Zeit in Millisekunden

Ein komplexeres Beispiel

Nachdem ich dir nun das Blink-Beispiel erklärt habe, ist es an der Zeit, das gewonnene Wissen auf ein anderes Problem zu übertragen. Der Einfachheit halber sollten wir uns nicht zu sehr vom Blink-Beispiel entfernen legen und weiterhin LEDs verwenden. Natürlich kannst du die digitalen Ausgänge deines Arduino Uno auch für eine ganz andere Sache verwenden.

Für dieses Tutorial habe ich ein Beispiel ausgewählt, das wahrscheinlich jeder gut kennt. Verkehrsampeln. Sie haben drei Lampen in den Farben rot, grün und gelb, die dir sagen, ob du über eine Kreuzung fahren darfst oder nicht. Um unsere eigene Miniatur-Ampel zu bauen, müssen wir den Code an dieses neue Problem anpassen und eine Schaltung auf einem Breadboard erstellen, denn wir brauchen drei LEDs mit unterschiedlichen Farben statt nur einer LED, wie sie der Arduino Uno bietet.

Aufbau des Schaltkreises

Verkabelung der LEDs

Die für unsere einfache Ampel erforderliche Schaltung ist einfach: Drei LEDs in Reihe mit je einem Widerstand werden an die Pins 8 bis 10 des Arduino Uno angeschlossen. Ich habe 220 Ω Widerstände verwendet, um den durch jede LED fließenden Strom zu begrenzen. Diese Widerstände werden zum Schutz der LEDs benötigt. Ich werde in der kommenden Serie über Elektronikgrundlagen erklären, warum dies notwendig ist und wie du den Widerstandswert selbst berechnen kannst. Halte dich vorerst einfach an die 220 Ω oder wähle zumindest einen ähnlichen Wert. Du solltest mindestens 180 Ω wählen, um die LEDs zu schützen. Denke daran, dass LEDs mit der richtigen Polarität angeschlossen werden müssen. Im Aufbauplan werden die negativen Seiten bzw. Kathoden der LEDs mit den Widerständen und die positive Seite direkt mit den jeweiligen Arduino-Pins verbunden. Du kanst feststellen, welche Seite der LED welche ist, indem du nach der abgeflachten Seite suchst. Dies ist die negative Seite der LED.

Ich habe für die LEDs die folgende Pinbelegung gewählt:

LED Pin
Grüne LED 8
Gelbe LED 9
Rote LED 10

Unser Ziel

Bevor wir mit dem Schreiben des Codes für unsere Ampel beginnen, lass uns einen Moment darüber nachdenken, was wir erreichen wollen. Eine normale Ampel hat 4 Phasen. Wir wollen eine lange Phase haben, in der nur die grüne LED leuchtet und die Autos fahren dürfen. Die nächste Phase wird eine kürzere Phase sein, in der nur die gelbe LED leuchtet, in der die Autos Zeit zum Bremsen haben, bevor die Ampel schließlich rot wird. Dies sind die drei Phasen, an die jeder denkt, aber es gibt noch eine weitere Phase. Kurz bevor die Ampel grün wird, wird es einen wirklich kurzen Zeitraum geben, in dem sowohl das rote als auch das gelbe Licht aufleuchtet. Warum nicht eine weitere Gelb-Phase? Nun, dafür gibt es einen offensichtlichen Grund. Autofahrer sollten in der Lage sein, zwischen einer Ampel, die rot wird, und einer Ampel, die grün wird, zu unterscheiden, sodass sie wissen, ob sie bremsen müssen oder nicht.
Da wir nun wissen, was unsere Ampel tun sollte, können wir mit dem Schreiben des Codes beginnen.

Schreiben des Codes

Wie zu Beginn erläutert, beginnt die Programmausführung mit der setup-Prozedur. Was sind die Dinge, die wir nur einmal zu Beginn unseres Programms tun müssen? Wir müssen die Pins initialisieren, an die wir unsere LEDs angeschlossen haben:

void setup() {
  pinMode(8, OUTPUT);  // Green LED
  pinMode(9, OUTPUT);  // Yellow LED
  pinMode(10, OUTPUT); // Red LED
}

Im obigen Code stellen wir den korrekten Modus für jeden der von uns verwendeten Pins ein. Wir müssen unserem Arduino mitteilen, dass wir sie als Ausgabepins verwenden wollen. Standardmäßig sind alle LEDs ausgeschaltet, wenn das Arduino startet. Es gibt nichts mehr, was wir hier noch tun müssen, also fahren wir mit der loop-Prozedur fort.

Die loop-Prozedur wird immer und immer wieder wiederholt. Das bedeutet, dass wir nur einen vollständigen Zyklus beschreiben müssen, um eine voll funktionsfähige Ampel zu erhalten. Wir müssen die richtige Kombination der Funktionen digitalWrite und delay finden, um die vier Phasen einer echten Ampel nachzustellen.
Werfen wir einen Blick auf den erforderlichen Code:

void loop() {
  // Green
  digitalWrite(8, HIGH);
  delay(10000);
  digitalWrite(8, LOW);

  // Yellow
  digitalWrite(9, HIGH);
  delay(2000);
  digitalWrite(9, LOW);

  // Red
  digitalWrite(10, HIGH);
  delay(10000);

  // Red and Yellow
  digitalWrite(9, HIGH);
  delay(1000);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
}

Was habe ich hier getan? Sehen wir es uns Schritt für Schritt an.

Als Erstes habe ich die grüne LED eingeschaltet, indem ich mit der Funktion digitalWrite Pin 8 auf HIGH gesetzt habe. Danach benutzte ich die delay-Funktion, um für 10000 ms bzw. 10 s zu schlafen. Nach Ablauf der 10 s wird die Programmausführung fortgesetzt und die LED wieder ausgeschaltet, indem Pin 8 auf LOW gesetzt wird.

void loop() {
  // Green
  digitalWrite(8, HIGH);
  delay(10000);
  digitalWrite(8, LOW);

Genau wie bei der grünen LED schalte ich nun die gelbe LED an Pin 9 ein. Diesmal habe ich eine kleinere Verzögerung von 2 s gewählt.

  // Yellow
  digitalWrite(9, HIGH);
  delay(2000);
  digitalWrite(9, LOW);
 

Schließlich kommen wir zur roten LED an Pin 10. Auch für diese Phase habe ich 10 s Verzögerung verwendet, aber hier gibt es etwas Besonderes: Nach den 10 s wird die rote LED nicht ausgeschaltet, weil wir sie für die nächste Phase brauchen.

  // Red
  digitalWrite(10, HIGH);
  delay(10000);

In dieser nächsten Phase werden wir zusätzlich die gelbe LED an Pin 9 einschalten und nur 1 s warten und dann beide LEDs ausschalten, bevor dieser ganze Zyklus wieder mit der grünen LED beginnt.

  // Red and Yellow
  digitalWrite(9, HIGH);
  delay(1000);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
}

Endergebnis

Wenn du die Schritte richtig befolgt hast, erhältst du deine eigene Arduino-Ampel. Du kannst sie im untenstehenden Video in Aktion sehen. Spiele mit deiner Lösung herum und experimentiere zum Beispiel mit den Verzögerungszeiten, und wenn du willst, kannst du deine eigenen, völlig zufälligen Phasen erstellen und eine Disco-Ampel kreieren. Im nächsten Teil dieser Tutorialserie werden wir einen Taster zu unserer Ampel hinzufügen.

Vorheriger Beitrag Nächster Beitrag