Serielle Kommunikation

Programmieren

In diesem Tutorial werden wird die serielle Schnittstelle kennenlernen und lernen, wie man Nachrichten an den Computer sendet und Konfigurationswerte empfängt.

Was ist serielle Kommunikation?

Bei der seriellen Kommunikation werden Bits nacheinander über die Leitung übertragen. So können Daten mit nur wenigen Pins übertragen werden. Im Falle des Universal Serial Asynchronous Transmitter (UART) des Arduino Uno werden zwei Pins für die eigentliche Datenübertragung verwendet. Diese sind mit RX (Pin 0) und TX (Pin 1) gekennzeichnet. Die Kommunikation ist bidirektional. Der Arduino kann gleichzeitig Daten über den RX Pin empfangen und Daten über den TX Pin senden. Im Falle des Arduino Uno sind diese Pins auch mit dem USB-zu-Seriell-Konverter verbunden, wodurch uns eine direkte Kommunikation mit dem Computer ermöglicht wird.

Was sind mögliche Anwendungsfälle für die serielle Kommunikation?
Nun, man kann sie zur Übertragung von Messdaten und Statusmeldungen an den Computer verwenden. Das Senden von Statusmeldungen kann sehr praktisch sein, wenn man beispielsweise nach Fehler im Codesucht. Sende einfach eine Nachricht mit den aktuellen Werten und vergleichen sie mit den erwarteten Werten oder sende eine Nachricht, wenn eine bestimmte Stelle in deinem Code erreicht wurde. Du kannst jedoch auch Daten vom Computer empfangen. Dies ermöglicht es uns, unsere Arduino-Programme zu konfigurieren und sogar interaktive Menüs zu erstellen.

Halten wir es jedoch erst einmal einfach. In diesem Tutorial werden wir einen Countdown erstellen und lernen, wie man den Startwert vom Computer aus konfigurieren kann. Um dir einen Eindruck davon zu vermitteln, was alles möglich ist, werden wir am Ende dieses Tutorials unser Arduino in einen kleinen Taschenrechner verwandeln.

Erstellen eines Countdowns

Wie erstellt man einen Countdown? Um dieses Problem zu lösen, lass es uns zuerst in zwei Teile aufteilen: den eigentlichen Countdown und die serielle Kommunikation.

Für die serielle Kommunikation müssen wir Code zur setup-Prozedur hinzufügen, um die Kommunikation zu initialisieren. Für die loop-Prozedur brauchen wir Code, um die eigentlichen Nachrichten zu senden. Hier ist eine Liste der Prozeduren, die wir dafür benutzen werden:

  • Serial.begin(baudrate)
    • baudrate: Kommunikationsgeschwindigkeit für die serielle Schnittstelle (normalerweise 9600)
  • Serial.print(value)
    • value: Zu sendender Text, Zeichen, Ganzzahl oder Gleitkommazahl
  • Serial.println(value)
    • value: Zu sendender Text, Zeichen, Ganzzahl oder Gleitkommazahl

Was ist der Unterschied zwischen Serial.print und Serial.println? Der einzige Unterschied ist, dass Serial.println zusätzlich einen Zeilenumbruch nach dem gesendeten Wert verursacht. Die Funktion Serial.begin wird benutzt, um die Kommunikation zu initialisieren. Die Geschwindigkeit muss mit der auf dem Computer gewählten Geschwindigkeit übereinstimmen. Die Standardeinstellung in der Arduino IDE ist 9600 Symbole pro Sekunde. Damit es funktioniert, werden wir den gleichen Wert in unserem Initialisierungscode verwenden.

Nachdem wir nun die für die serielle Kommunikation erforderlichen Prozeduren kennengelernt haben, können wir uns nun auf den Countdown konzentrieren. Wir können eine Variable definieren, den Anfangswert z. B. auf zehn setzen und dann eine while-Schleife verwenden, in der wir den aktuellen Wert senden und dann immer den Wert Eins von der Variable subtrahieren. Das müssen wir tun, solange der Wert größer oder gleich null ist. Aber da eine solche Iteration über Zahlen eine ziemlich oft benötigte Sache ist, gibt es auch eine einfachere und sauberere Lösung dafür: Die for-Schleife.

for(initialization; condition; loop-statement) {
  statement1;
  statement2;
}

In der Initialisierungsphase der for-Schleife können wir eine einzige Anweisung verwenden, um unsere Zählvariable zu definieren und den Anfangswert zuzuweisen. Die Bedingung bestimmt, wann die Schleife endet. Analog zur while-Schleife endet die Schleife, sobald die Bedingung als false ausgewertet wird. Die Schleifenanweisung ermöglicht es uns, eine einzelne Anweisung anzugeben, um unsere Zählvariable nach jeder Ausführung der Schleife zu ändern. Für unseren Countdown sieht dies wie folgt aus:

for(int count = 10; count >= 0; count--) {
  // Hier können wir die serielle Kommunikation einfügen
}

Die Anweisung count-- ist dabei lediglich eine praktische Kurzschreibweise, um einen Wert um eins zu vermindern. Du kannst dasselbe tun, um eine Variable um eins zu inkrementieren, indem du count++ nutzt. Es gibt mehr als eine Alternative, um das gleiche Ziel zu erreichen: count = count - 1 oder count -= 1 funktioniert ebenfalls. Diese beiden Optionen funktionieren dabei auch für andere mathematische Operationen wie Division oder Multiplikation.

Kombiniert erhalten wir den folgenden Code für unseren Countdown:

void setup() {
  Serial.begin(9600);
}

void loop() {
  // Countdown von 10 bis 0
  for(int count = 10; count >= 0; count--) {
    Serial.print("Countdown: ");
    Serial.println(count);
    delay(1000);
  }
}

Schauen wir uns unser Ergebnis an. Dazu müssen wir den seriellen Monitor der Arduino IDE öffnen. Klicke dazu auf die Schaltfläche in der rechten Ecke der Kopfzeile, wie es im folgenden Screenshot gezeigt wird:

Öffnen des seriellen Monitors

Im seriellen Monitor können wir die vom Arduino Uno gesendete Nachricht ansehen. Wir können das Textfeld verwenden, um eine Nachricht zu schreiben und zu senden. Wir werden dies für die nächsten Schritte benötigen.

Serieller Monitor

Neustart auf Kommando

Etwas ärgerlich an unserer bisherigen Lösung ist, dass der Countdown nach Erreichen von null wieder von vorn beginnt. Wie können wir das beheben? Am einfachsten wäre es, am Ende des Programms eine Schleife hinzuzufügen, die ewig läuft:

while(true);

Auf diese Weise wird der Countdown nicht neu gestartet, außer wenn wir den Arduino Uno über den Reset-Taster zurücksetzen. Aber das wäre doch ziemlich langweilig, nicht wahr? Da wir auch Daten an den Arduino senden können, warum lassen wir ihn nicht auf eine Nutzereingabe warten, um den Countdown neu zu starten? Dazu können wir den folgenden Befehl verwenden:

  • Serial.available()
    • Gibt true zurück, wenn Daten vom Computer empfangen wurden

Wir können eine Schleife, die darauf wartet, dass diese Funktion true zurückgibt, an das Ende unseres Programmcodes anfügen. Wenn du dann im seriellen Monitor auf "Senden" klickst, wird der Countdown neu gestartet. Es wird jedoch folgendes Problem auftreten: Nachdem du einmal etwas gesendet hast, stoppt der Countdown nicht mehr. Das liegt daran, dass die in der Vergangenheit gesendeten Daten noch vorhanden sind. Wir müssen sie lesen, um den Eingabepuffer zu leeren. Dazu können wir die Funktion Serial.read verwenden:

  • Serial.read()
    • Gibt das gesendete Zeichen oder -1, wenn keine Daten mehr verfügbar sind, zurück

Um den Puffer zu löschen, können wir in einer Schleife so lange lesen, bis -1 zurückgegeben wird. Der angepasste Code sieht dann wie folgt aus:

void setup() {
  Serial.begin(9600);
}

void loop() {
  // Countdown von 10 bis 0
  for(int count = 10; count >= 0; count--) {
    Serial.print("Countdown: ");
    Serial.println(count);
    delay(1000);
  }

  while(Serial.available() == 0);
  while(Serial.read() != -1);
}

Konfigurieren des Startwerts

Warum nur die empfangenen Bytes lesen und dann verwerfen? Wir können die Eingabe vom Computer auch verwenden, um zu konfigurieren, bei welchem Wert der Countdown beginnt. Dabei wird sich die folgende Funktion als nützlich erweisen:

  • Serial.parseInt()
    • Liest und gibt eine ganze Zahl vom Typ long zurück

Wir können verschiedene Ganzzahl-Typen sowie Fließkommazahlen und Ganzzahlen direkt einander zuweisen können. Der Typ wird implizit konvertiert und der Wert wird abgeschnitten oder überlaufen, je nachdem, wie Sie ihn nennen möchten, wenn er nicht in den Zieltyp passt. Im nachfolgenden Beispielcode habe ich den Typ int verwendet, obwohl die Funktion Serial.parseInt eine Ganzzahl vom Typ long zurückgibt. Mithilfe dieser Funktion können wir den Startwert am Anfang der loop-Prozedur lesen. Die restlichen Bytes, den Zeilenumbruch, der nach der Zahl gesendet wird, müssen wir zusätzlich lesen und verwerfen. Ich speichere den Wert in einer Variablen, die später zum Setzen des Anfangswertes des Countdowns verwendet wird.

Hier ist der angepasste Code:

void setup() {
  Serial.begin(9600);
}

void loop() {
  while(Serial.available() == 0);
  int startValue = Serial.parseInt();
  while(Serial.read() != -1);

  // Count down from startValue to 0
  for(int count = startValue; count >= 0; count--) {
    Serial.print("Countdown: ");
    Serial.println(count);
    delay(1000);
  }
}

Ein einfacher Taschenrechner

Zu guter Letzt, der versprochene Beispielcode für einen einfachen Taschenrechner:

void setup() {
  Serial.begin(9600);
}

void loop() {
  while(Serial.available() == 0);
  
  int n1 = Serial.parseInt();
  char op = Serial.read();
  int n2 = Serial.parseInt();
  
  while(Serial.read() != -1);

  if(op == '+') {
    Serial.println(n1+n2);
  }
  else if(op == '-') {
    Serial.println(n1-n2);
  }
  else if(op == '*') {
    Serial.println(n1*n2);
  }
  else if(op == '/') {
    Serial.println(n1/n2);
  }
  else {
    Serial.println("Invalid input, try again!");
  }
}

Mit dem Wissen, das du in diesem Tutorial erworben hast, sollte es nicht schwer sein zu verstehen, wie der Code funktioniert. Er wartet auf Benutzereingaben und liest dann eine ganze Zahl, gefolgt von der Rechenoperation und einer zweiten Zahl ein. Die if-else-Konstruktion wird verwendet, um die gewählte Rechenoperation zu bestimmen, um das Ergebnis zu berechnen und es an den Benutzer zurückzusenden.

Um den Taschenrechner zu benutzen, gebe einfach die Rechenaufgabe (z.B. 1+1) in das Textfeld des seriellen Monitors ein und sende sie an den Arduino. Der Arduino wird mit dem Ergebnis deiner Rechenaufgabe antworten. Beachte, dass du falsche Ergebnisse erhältst, wenn du größere Zahlen verwendest. Dies ist auf den Integer-Überlauf zurückzuführen, über den ich im letzten Tutorial gesprochen habe. Um größere Zahlen zu ermöglichen, ersetze den Typ int durch long.

Vorheriger Beitrag Nächster Beitrag