Serielle Kommunikation
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
4.0 Einleitung
Die serielle Kommunikation ist eine einfache und flexible Möglichkeit für dein Arduino-Board, mit deinem Computer und anderen Geräten zu kommunizieren. In diesem Kapitel wird erklärt, wie du mit dieser Funktion Informationen senden und empfangen kannst.
In Kapitel 1 wurde beschrieben, wie du den seriellen USB-Anschluss des Arduino mit deinem Computer verbindest, um Sketches hochzuladen. Der Upload-Prozess sendet Daten von deinem Computer an den Arduino, und der Arduino sendet Statusmeldungen an den Computer zurück, um zu bestätigen, dass die Übertragung funktioniert. Die Rezepte hier zeigen, wie du dieselbe Kommunikationsverbindung nutzen kannst, um beliebige Informationen zwischen Arduino und deinem Computer oder einem anderen seriellen Gerät zu senden und zu empfangen.
Die serielle Kommunikation ist auch ein praktisches Werkzeug für die Fehlersuche. Du kannst Debug-Meldungen vom Arduino an den Computer senden und sie auf dem Bildschirm deines Computers anzeigen lassen oder sie an ein anderes Gerät wie einen Raspberry Pi oder einen anderen Arduino senden. Du kannst auch ein externes LCD-Display verwenden, um diese Meldungen anzuzeigen, aber höchstwahrscheinlich würdest du I2C oder SPI verwenden, um mit einem solchen Display zu kommunizieren (siehe Kapitel 13).
Die Arduino IDE (beschrieben in Rezept 1.3) bietet einen Serial Monitor (siehe Abbildung 4-1), um serielle Daten anzuzeigen, die von Arduino gesendet werden. Du kannst auch Daten vom Serial Monitor an Arduino senden, indem du Text in das Textfeld links neben der Schaltfläche Senden eingibst. Arduino enthält auch einen seriellen Plotter, der die vom Arduino gesendeten seriellen Daten grafisch darstellen kann (siehe Rezept 4.1).
Du kannst die Geschwindigkeit, mit der die Daten übertragen werden (die Baudrate, gemessen in Bits pro Sekunde), in der Dropdown-Box unten rechts einstellen. Achte darauf, dass du den Wert einstellst, den du mit Serial.begin()
verwendest. Die Standardrate von 9.600 Bits pro Sekunde ist für viele Fälle ausreichend, aber wenn du mit einem Gerät arbeitest, das eine höhere Geschwindigkeit benötigt, kannst du Serial.begin()
eine höhere Zahl als 9.600 übergeben.
Mit der Dropdown-Liste links neben der Baudrate kannst du festlegen, ob am Ende jeder Nachricht automatisch ein Zeilenumbruch (ASCII-Zeichen 10), Wagenrücklauf (ASCII-Zeichen 13), eine Kombination aus Zeilenumbruch und Wagenrücklauf ("Both NL & CR") oder kein Abschlusszeichen ("No line ending") gesendet werden soll.
Dein Arduino-Sketch kann die serielle Schnittstelle nutzen, um indirekt auf alle Ressourcen (Speicher, Bildschirm, Tastatur, Maus, Netzwerkverbindung usw.) deines Computers zuzugreifen (normalerweise über ein Proxy-Programm, das in einer Sprache wie Processing oder Python geschrieben wurde). Dein Computer kann die serielle Verbindung auch nutzen, um mit bestimmten Sensoren oder anderen Geräten zu kommunizieren, die an den Arduino angeschlossen sind. Wenn du mit mehreren Geräten über die serielle Schnittstelle kommunizieren willst, brauchst du entweder mehr als eine serielle Schnittstelle oder du musst eine serielle Software verwenden, um eine serielle Schnittstelle mit Arduino-Pins zu emulieren (siehe "Serielle Hardware mit digitalen Pins emulieren").
Hinweis
Viele Sensoren und Ausgabegeräte, die serielle Kommunikation unterstützen, unterstützen auch SPI oder I2C (siehe Kapitel 13). Obwohl die serielle Kommunikation gut verstanden wird und relativ universell ist, solltest du SPI oder I2C verwenden, wenn der Sensor oder das Ausgabegerät, das du anschließen möchtest, beides unterstützt. Beide Protokolle bieten mehr Flexibilität bei der Kommunikation mit mehreren Geräten.
Die Implementierung der seriellen Kommunikation umfasst Hardware und Software. Die Hardware sorgt für die elektrische Signalübertragung zwischen dem Arduino und dem Gerät, mit dem er kommuniziert. Die Software nutzt die Hardware, um Bytes oder Bits zu senden, die die angeschlossene Hardware versteht. Die seriellen Arduino-Bibliotheken halten dich von der Komplexität der Hardware fern, aber es ist hilfreich, wenn du die Grundlagen verstehst, vor allem, wenn du Probleme mit der seriellen Kommunikation in deinen Projekten beheben musst.
Serielle Hardware
Serielle Hardware sendet und empfängt Daten in Form von elektrischen Impulsen, die aufeinanderfolgende Bits darstellen. Die Nullen und Einsen, aus denen ein Byte besteht, können auf verschiedene Weise dargestellt werden. Das Schema, das der Arduino verwendet, ist 0 Volt, um einen Bitwert von 0 darzustellen, und 5 Volt (oder 3,3 Volt), um einen Bitwert von 1 darzustellen.
Hinweis
Es ist weit verbreitet, die niedrige Spannung eines Geräts (in der Regel 0 Volt) für 0 und die hohe Spannung (3,3 oder 5 Volt im Falle des Arduino) für 1 zu verwenden. Dies wird als TTL-Pegel bezeichnet, weil Signale in einer der ersten Implementierungen der digitalen Logik, der Transistor-Transistor-Logik (TTL), auf diese Weise dargestellt wurden. Bei den meisten Implementierungen kann eine 1 mit weniger als der Hochspannung des Geräts signalisiert werden, und 3,3 Volt sind in der Regel mehr als genug, um eine 1 zu signalisieren. Das bedeutet, dass du in den meisten Fällen von einer 3,3-V-Platine senden und das Signal auf einer 5-V-Platine empfangen kannst. Wenn du jedoch serielle Daten von einer 5V- zu einer 3,3V-Platine übertragen willst, musst du einen Pegelwandler oder einen Spannungsteiler verwenden, um die 3,3V-Platine nicht zu beschädigen. In den Rezepten 4.13 und 5.11 findest du Beispiele für Spannungsteiler.
Einige Boards, wie das Modern Device Bare Bones Board und das (inzwischen eingestellte) Adafruit Boarduino und Arduino Pro, Mini und Pro Mini, haben keine USB-Unterstützung und benötigen für den Anschluss an deinen Computer einen Adapter, der TTL in USB umwandelt. Die Adafruit CP2104 Friend (Adafruit Teilenummer 3309), Modern Device USB BUB Board (Modern Device Teil MD022X), und FTDI USB TTL Adapter funktionieren alle gut.
Einige serielle Geräte verwenden den RS-232-Standard für die serielle Verbindung. Diese Geräte haben in der Regel einen neunpoligen Anschluss, für den ein Adapter erforderlich ist, um sie mit dem Arduino zu verwenden. RS-232 verwendet Spannungspegel, die die digitalen Pins des Arduino beschädigen. Deshalb brauchst du einen RS-232-zu-TTL-Adapter, um ihn zu verwenden. Bei Arduino gibt es ein Arduino-RS-232-Tutorial, und auf der Website Serial Port Central findest du viele Informationen und Links.
Ein Arduino Uno hat einen einzigen seriellen Hardware-Anschluss, aber die serielle Kommunikation ist auch mit Hilfe von Software-Bibliotheken möglich, die zusätzliche Anschlüsse (Kommunikationskanäle) emulieren, um die Verbindung zu mehr als einem Gerät herzustellen. Die serielle Software erfordert viel Hilfe vom Arduino-Controller, um Daten zu senden und zu empfangen, und ist daher nicht so schnell und effizient wie die serielle Hardware.
Der Leonardo und viele 32-Bit-Boards (wie z.B. der Arduino Zero, Adafruit Metro M0, und SparkFun RedBoard Turbo) haben neben der USB-Schnittstelle eine zweite serielle Schnittstelle. Das Teensy 3-Board von PJRC hat drei serielle Schnittstellen zusätzlich zu USB-Seriell. Das Teensy 4.0 Board hat sieben serielle Anschlüsse (zusätzlich zu USB-Seriell).
Der Arduino Mega hat vier serielle Hardware-Anschlüsse, die mit bis zu vier verschiedenen seriellen Geräten kommunizieren können. Nur eine davon hat einen eingebauten USB-Adapter (du kannst einen USB-TTL-Adapter an eine der anderen seriellen Schnittstellen anschließen, wenn du mehr als einen USB-Anschluss brauchst).
Tabelle 4-1 zeigt die Pins, die für serielle Schnittstellen auf verschiedenen Arduino- und Arduino-kompatiblen Boards verwendet. Die angegebenen Pin-Nummern beziehen sich auf digitale und nicht auf analoge Pins.
Vorstand | Seriell RX/TX | Seriell1 RX/TX | Seriell2 RX/TX | Seriell3 RX/TX |
---|---|---|---|---|
Nur USB |
13/14 |
keine |
keine |
|
Nur USB |
0/1 |
Verbunden mit dem WiFi-Modul |
keine |
|
Nur USB |
0/1 |
keine |
keine |
|
Nur USB |
0/1 |
keine |
keine |
|
Arduino Uno Rev3 |
0/1 (auch USB) |
keine |
keine |
keine |
Nur USB |
0/1 |
keine |
keine |
|
Nur USBa |
0/1 |
keine |
keine |
|
Nur USB |
0/1 |
keine |
keine |
|
Nur USB |
0/1 |
9/10 |
7/8 |
|
PJRC Teensy 4.0 |
Nur USB |
0/1 |
7/8 |
15/14 |
0/1 (auch USB) |
19/18 |
17/16 |
15/14 |
|
0/1 (auch USB) |
19/18 |
17/16 |
15/14 |
|
Nur USB |
0/1 |
keine |
keine |
|
a Verwende |
Hinweis
Einige Teensy-Boards unterstützen mehr als drei serielle Hardware-Schnittstellen, und bei einigen kannst du einstellen, welche Pins für die serielle Kommunikation verwendet werden. Siehe PJRC für weitere Informationen.
Serielles Hardware-Verhalten
Die Anzahl der seriellen Schnittstellen ist nicht die einzige Variable zwischen den Boards. Es gibt auch einige grundlegende Unterschiede im Verhalten. Die meisten Boards, die auf den AVR ATmega-Chips basieren, wie z. B. der Uno, original Nano, und Mega, haben einen Chip, der die serielle Schnittstelle des Arduino-Chips in USB umwandelt, um eine Verbindung zur seriellen Schnittstelle herzustellen. Wenn du bei diesen Boards eine Verbindung zur seriellen Schnittstelle herstellst (z. B. indem du den seriellen Monitor öffnest oder von einem Programm, das auf einem Computer läuft, der über USB mit dem Board verbunden ist, auf die serielle Schnittstelle zugreifst), wird das Board automatisch zurückgesetzt, sodass der Sketch von vorne beginnt.
Bei einigen 8-Bit-Boards (Leonardo und kompatiblen Boards) und den meisten 32-Bit-Boards wird die serielle USB-Schnittstelle vom selben Prozessor bereitgestellt, auf dem du deine Sketche ausführst. Aufgrund ihrer Bauweise werden diese Boards nicht zurückgesetzt, wenn du die USB-Schnittstelle öffnest. Das hat zur Folge, dass dein Sketch schneller Daten an die serielle USB-Schnittstelle sendet, als du die Schnittstelle öffnen kannst. Das bedeutet, dass du, wenn du einen seriellen Ausgabebefehl (Serial.print
oder Serial.println
) in deiner setup()
Funktion hast, diesen nicht im Serial Monitor sehen wirst, weil du den Serial Monitor nicht schnell genug öffnen kannst. (Du könntest ein delay
in deine setup
Funktion einfügen, aber es gibt noch eine andere Möglichkeit.)
Außerdem haben der Leonardo und die kompatiblen Modelle ein weiteres Verhalten, das die Arbeit mit der seriellen Schnittstelle erschwert: Wenn du das Gerät zum ersten Mal einschaltest, blinkt eine LED mehrere Sekunden lang, um dir mitzuteilen, dass es sich in einem speziellen Modus befindet, in dem es dir erlaubt, einen Sketch über USB zu laden. Du kannst also die serielle Schnittstelle nicht öffnen, um Daten zu senden oder zu empfangen, bis die Wartezeit vorüber ist.
Bei Boards, die sich nicht automatisch zurücksetzen, wenn du die serielle Schnittstelle öffnest, kannst du den folgenden Code in deine setup
Funktion einfügen (direkt nach dem Aufruf von Serial.begin()
). Dadurch wird die Ausführung angehalten, bis die serielle Schnittstelle geöffnet wurde, damit du die serielle Ausgabe, die du in setup
sendest, sehen kannst:
while
(
!
Serial
)
{
;
// wait for serial port to connect
}
Du kannst die geschweiften Klammern weglassen und sie auf while(!Serial);
zusammenfassen, aber das kann für Programmieranfänger, die deinen Code lesen, verwirrend sein.
Da der Befehl while(!Serial);
die Ausführung des Sketches unterbricht, bis du die serielle Schnittstelle öffnest, sollte dieser Ansatz nicht in Umgebungen verwendet werden, in denen deine Arduino-basierte Lösung unabhängig laufen soll, z. B. wenn sie mit Batterien ohne USB-Anschluss läuft. Tabelle 4-2 zeigt das serielle USB-Verhalten für verschiedene Boards.
Vorstand | while(!Serial); benötigt? | Zurücksetzen bei seriellem Zugriff? |
---|---|---|
Ja |
Nein |
|
Nein |
Ja |
|
Nein; Erfordert eine |
Nein |
|
Ja |
Nein |
|
Arduino Uno Rev3 |
Nein |
Ja |
Ja |
Nein |
|
Ja |
Nein |
|
Ja |
Nein |
|
PJRC Teensy 4.0 |
Ja |
Nein |
Nein |
Ja |
|
Ja |
Nein |
Serielle Hardware mit digitalen Pins emulieren
Normalerweise verwendest du die integrierte Arduino Serial Library, um mit den seriellen Schnittstellen der Hardware zu kommunizieren. Serielle Bibliotheken vereinfachen die Nutzung der seriellen Schnittstellen, indem sie dich von der Komplexität der Hardware abschirmen.
Manchmal brauchst du mehr serielle Schnittstellen als die Anzahl der verfügbaren seriellen Hardware-Schnittstellen. In diesem Fall kannst du eine zusätzliche serielle Softwarebibliothek verwenden, die die serielle Hardware per Software emuliert. Die Rezepte 4.11 und 4.12 zeigen, wie du eine serielle Softwarebibliothek verwendest, um mit mehreren Geräten zu kommunizieren.
Nachrichtenprotokolle
Die seriellen Hardware- oder Softwarebibliotheken sorgen für das Senden und Empfangen von Informationen. Diese Informationen bestehen oft aus Gruppen von Variablen, die zusammen gesendet werden müssen. Damit die Informationen richtig interpretiert werden können, muss die empfangende Seite erkennen, wo jede Nachricht beginnt und endet. Eine sinnvolle serielle Kommunikation oder jede Art von Maschine-zu-Maschine-Kommunikation ist nur dann möglich, wenn sich die sendende und die empfangende Seite vollständig darüber einig sind, wie die Informationen in der Nachricht organisiert sind. Die formale Organisation der Informationen in einer Nachricht und die Bandbreite der angemessenen Antworten auf Anfragen wird als Kommunikationsprotokoll bezeichnet. Du kannst ein Protokoll über jedes zugrunde liegende Datenübertragungssystem erstellen, z. B. über serielle Kommunikation, aber dieselben Prinzipien gelten auch für andere Arten der Datenübertragung, z. B. für Ethernet oder WiFi-Netzwerke.
Nachrichten können ein oder mehrere Sonderzeichen enthalten, die den Anfang der Nachricht kennzeichnen - dies wird als Kopfzeile bezeichnet. Ein oder mehrere Zeichen können auch das Ende einer Nachricht kennzeichnen - dies wird als Fußzeile bezeichnet. Die Rezepte in diesem Kapitel zeigen Beispiele für Nachrichten, bei denen die Werte, aus denen der Nachrichtentext besteht, entweder im Text- oder im Binärformat gesendet werden können.
Beim Senden und Empfangen von Nachrichten im Textformat werden Befehle und Zahlenwerte als für Menschen lesbare Buchstaben und Wörter gesendet. Zahlen werden als Ziffernfolge gesendet, die den Wert darstellt. Wenn der Wert zum Beispiel 1234 lautet, werden die Zeichen 1, 2, 3 und 4 als einzelne Zeichen gesendet.
Binäre Nachrichten bestehen aus den Bytes, die der Computer verwendet, um Werte darzustellen. Binäre Daten sind in der Regel effizienter (es müssen weniger Bytes gesendet werden), aber die Daten sind nicht so gut lesbar wie Text, was die Fehlersuche erschwert. Arduino stellt zum Beispiel 1234 als die Bytes 4 und 210 dar (4 * 256 + 210 = 1234). Wenn du dir diese Zeichen im Serial Monitor ansehen würdest, wären sie nicht lesbar, weil das ASCII-Zeichen 4 ein Steuerzeichen ist und das ASCII-Zeichen 210 im erweiterten Bereich der ASCII-Zeichen liegt, so dass je nach Konfiguration wahrscheinlich ein Akzentzeichen oder etwas anderes angezeigt wird. Wenn das Gerät, mit dem du dich verbindest, nur binäre Daten sendet oder empfängt, musst du diese verwenden.
Es gibt viele Möglichkeiten, Softwareprobleme anzugehen, und einige der Rezepte in diesem Kapitel zeigen zwei oder drei verschiedene Wege, um ein ähnliches Ergebnis zu erzielen. Die Unterschiede (z. B. das Senden von Text anstelle von binären Rohdaten) können ein Gleichgewicht zwischen Einfachheit und Effizienz bieten. Wenn du die Wahl hast, wähle die Lösung, die du am leichtesten verstehst und anpassen kannst - das wird wahrscheinlich die erste Lösung sein. Alternativen sind vielleicht etwas effizienter oder eignen sich besser für ein bestimmtes Protokoll, mit dem du eine Verbindung herstellen willst, aber der "richtige Weg" ist der, den du für dein Projekt am einfachsten findest.
Arduino Serielle Hinweise
Hier sind ein paar Dinge, die du bei der Arbeit mit seriellen Daten in Arduino beachten solltest :
-
Serial.flush
wartet darauf, dass alle ausgehenden Daten gesendet werden, anstatt die empfangenen Daten zu verwerfen (wie es in älteren Versionen von Arduino der Fall war). Du kannst die folgende Anweisung verwenden, um alle Daten im Empfangspuffer zu verwerfen: .while(Serial.read() >= 0) ; // flush the receive buffer
-
Serial.write
und werden nicht blockiert. Ältere Versionen von Arduino warteten, bis alle Zeichen gesendet waren, bevor sie zurückkehrten. Stattdessen werden Zeichen, die du mit oder (und ) sendest, im Hintergrund (von einem Interrupt-Handler) übertragen, so dass dein Sketchcode sofort weiterverarbeitet werden kann. Das ist normalerweise eine gute Sache (es kann den Sketch reaktionsschneller machen), aber manchmal möchtest du warten, bis alle Zeichen gesendet wurden. Das kannst du erreichen, indem du unmittelbar nach oder / aufrufst.Serial.print
Serial.write
Serial.print
println
Serial.flush()
Serial.write()
Serial.print()
println()
-
Serielle Druckfunktionen geben die Anzahl der gedruckten Zeichen zurück. Dies ist nützlich, wenn die Textausgabe ausgerichtet werden muss oder für Anwendungen, die Daten senden, die die Gesamtzahl der gesendeten Zeichen enthalten.
-
Es gibt eine eingebaute Parsing-Funktion für Streams wie Serial, mit der du ganz einfach Zahlen extrahieren und Text finden kannst. Im Abschnitt "Diskussion" in Rezept 4.5 erfährst du mehr über die Verwendung dieser Funktion mit Serial.
-
Die SoftwareSerial-Bibliothek, die mit Arduino geliefert wird, kann sehr hilfreich sein; siehe Rezepte 4.11 und 4.12.
-
Mit der Funktion
Serial.peek
kannst du einen "Blick" auf das nächste Zeichen im Empfangspuffer werfen. Anders als beiSerial.read
wird das Zeichen beiSerial.peek
nicht aus dem Puffer entfernt.
4.1 Senden von Informationen vom Arduino an deinen Computer
Lösung
Dieser Sketch gibt fortlaufende Nummern auf dem seriellen Monitor aus:
/*
* SerialOutput sketch
* Print numbers to the serial port
*/
void
setup
()
{
Serial
.
begin
(
9600
);
// send and receive at 9600 baud
}
int
number
=
0
;
void
loop
()
{
Serial
.
(
"The number is "
);
Serial
.
println
(
number
);
// print the number
delay
(
500
);
// delay half second between numbers
number
++
;
// to the next number
}
Verbinde den Arduino wie in Kapitel 1 mit deinem Computer und lade diesen Sketch hoch. Klicke auf das Symbol für den seriellen Monitor in der IDE und du solltest die folgende Ausgabe sehen:
The
number
is
0
The
number
is
1
The
number
is
2
Diskussion
Um Text und Zahlen aus deinem Sketch über eine serielle Verbindung auf einem Computer anzuzeigen, füge die Anweisung Serial.begin(9600)
in setup()
, ein und verwende dann die Anweisungen Serial.print()
, um den gewünschten Text und die Werte zu drucken. Du kannst dir die Ausgabe dann im Serial Monitor ansehen, wie in Abbildung 4-2 gezeigt.
Um eine grafische Darstellung der zurückgesendeten Zahlen zu erhalten, schließe das Fenster Serial Monitor und wähle Tools→SerialPlotter. Es öffnet sich ein Fenster, in dem die von der Karte empfangenen Werte grafisch dargestellt werden. Der Plotter kann die Zahlen vom Text trennen und mehrere Zahlen, die durch Alpha-Zeichen getrennt sind, erkennen und sie in verschiedenen Farben getrennt darstellen. Abbildung 4-3 zeigt den seriellen Plotter.
Dein Sketch muss die Funktion Serial.begin()
aufrufen, bevor er die serielle Eingabe oder Ausgabe verwenden kann. Die Funktion benötigt einen einzigen Parameter: die gewünschte Kommunikationsgeschwindigkeit. Du musst die gleiche Geschwindigkeit für die sendende und die empfangende Seite verwenden, sonst siehst du nur Geschwafel (oder gar nichts) auf dem Bildschirm. In diesem Beispiel und den meisten anderen in diesem Buch wird eine Geschwindigkeit von 9.600 Baud verwendet(Baud ist ein Maß für die Anzahl der pro Sekunde übertragenen Bits). Die Geschwindigkeit von 9.600 Baud entspricht etwa 1.000 Zeichen pro Sekunde. Du kannst auch mit niedrigeren oder höheren Raten senden (die Spanne reicht von 300 bis 115.200 oder höher, je nach den Fähigkeiten deines Boards), aber achte darauf, dass beide Seiten die gleiche Geschwindigkeit verwenden. Der Serial Monitor stellt die Geschwindigkeit über das Dropdown-Menü Baudrate ein (unten rechts im Fenster Serial Monitor in Abbildung 4-2). Wenn deine Ausgabe etwa so aussieht:
`3??f<ÌxÌ▯▯▯ü`³??f<
solltest du überprüfen, ob die ausgewählte Baudrate auf dem seriellen Monitor deines Computers mit der von Serial.begin()
in deinem Sketch eingestellten Rate übereinstimmt.
Tipp
Wenn deine seriellen Sende- und Empfangsgeschwindigkeiten richtig eingestellt sind, du aber immer noch unlesbaren Text bekommst, überprüfe, ob du im IDE-Menü Tools→Boarddas richtige Board ausgewählt hast. Wenn du also die falsche Geschwindigkeit ausgewählt hast, ändere sie in die richtige und lade die Karte erneut hoch.
Du kannst Text mit der Funktion Serial.print()
übermitteln. Zeichenketten (Text in Anführungszeichen) werden so gedruckt, wie sie sind (aber ohne die Anführungszeichen). Zum Beispiel der folgende Code:
Serial
.
(
"The number is "
);
druckt dies:
The
number
is
Die Werte (Zahlen), die du ausdruckst, hängen vom Typ der Variablen ab; siehe Rezept 4.2 für weitere Informationen dazu. Wenn du zum Beispiel eine ganze Zahl druckst, wird ihr numerischer Wert gedruckt. Wenn also die Variable number
1
ist, wird der folgende Code gedruckt:
Serial
.
println
(
number
);
wird der aktuelle Wert von number
gedruckt:
1
In der Beispielskizze ist die gedruckte Zahl 0
, wenn die Schleife beginnt, und wird bei jedem Durchlauf der Schleife um eins erhöht. Das ln
am Ende von println
bewirkt, dass die nächste Druckanweisung in einer neuen Zeile beginnt.
Hinweis
Beachte, dass es zwei verschiedene Verhaltensweisen für die serielle Schnittstelle gibt, die du bei Arduino und Arduino-kompatiblen Boards antriffst: Der Uno und die meisten anderen AVR ATmega-basierten Boards werden zurückgesetzt, wenn du die serielle Schnittstelle öffnest. Das bedeutet, dass der Zähler im seriellen Monitor oder Plotter immer bei Null beginnt. Der Arduino Leonardo, und die ARM-basierten Boards setzen sich nicht automatisch zurück, wenn du die serielle Schnittstelle öffnest. Das bedeutet, dass der Sketch mit dem Zählen beginnt, sobald er eingeschaltet wird. Daher hängt der Wert, den du beim ersten Öffnen des seriellen Monitors oder Plotters siehst, davon ab, wann du die serielle Verbindung zum Board geöffnet hast. Weitere Informationen findest du unter "Serielles Hardware-Verhalten".
Damit solltest du anfangen können, Text und den Dezimalwert von ganzen Zahlen zu drucken. In Rezept 4.2 findest du weitere Informationen zu den Druckformatierungsoptionen.
Du solltest ein Terminalprogramm eines Drittanbieters in Betracht ziehen, das mehr Funktionen als Serial Monitor hat. Die Anzeige von Daten im Text- oder Binärformat (oder beidem), die Anzeige von Steuerzeichen und die Protokollierung in eine Datei sind nur einige der zusätzlichen Funktionen, die die vielen Terminalprogramme von Drittanbietern bieten. Hier sind einige, die von Arduino-Benutzern empfohlen wurden:
- CoolTerm
-
Ein einfach zu bedienendes Freeware-Terminalprogramm für Windows, Mac und Linux
- CuteCom
-
Ein Open Source Terminalprogramm für Linux
- Bray Terminal
-
Eine kostenlose ausführbare Datei für den PC
- GNU-Bildschirm
-
Ein Open-Source-Programm zur Verwaltung virtueller Bildschirme, das die serielle Kommunikation unterstützt; in Linux und macOS enthalten
- moserial
-
Ein weiteres Open Source Terminalprogramm für Linux
- PuTTY
-
Ein quelloffenes SSH-Programm für Windows und Linux, das serielle Kommunikation unterstützt
- RealTerm
-
Ein Open Source Terminalprogramm für den PC
- ZTerm
-
Ein Shareware-Programm für den Mac
Du kannst eine Flüssigkristallanzeige (LCD) als serielles Ausgabegerät verwenden, allerdings ist die Funktionalität dann sehr eingeschränkt. Erkundige dich in der Dokumentation, wie dein Display mit Zeilenumbrüchen umgeht, denn manche Displays springen nach println
Anweisungen nicht automatisch in eine neue Zeile. Wenn du ein LCD-Display verwendest, musst du es über die seriellen TTL-Pins (digital 0 und 1) und nicht über eine USB-Verbindung anschließen. Auf den meisten AVR ATmega Boards wie dem Uno, entsprechen diese Pins dem Serial
Objekt, sodass du den in der Lösung gezeigten Code unverändert verwenden kannst. Auf dem Leonardo oder bestimmten ARM-basierten Boards (z. B. SAMD-basierten Boards) entsprechen die Pins 0 und 1 jedoch dem Objekt Serial1
. Daher musst du Serial
in Serial1
ändern, damit der Code auf diesen Boards funktioniert. In Tabelle 4-1 findest du eine Liste der Pin-Konfigurationen des Serial-Objekts für eine Reihe von Boards.
Siehe auch
Die Arduino LiquidCrystal-Bibliothek für Text-LCDs verwendet eine ähnliche Druckfunktionalität wie die Serial-Bibliothek, sodass du viele der in diesem Kapitel behandelten Vorschläge auch mit dieser Bibliothek verwenden kannst (siehe Kapitel 11).
4.2 Senden von formatiertem Text und numerischen Daten vom Arduino
Lösung
Du kannst Daten in vielen verschiedenen Formaten über die serielle Schnittstelle ausgeben; hier ist eine Skizze, die alle Formatoptionen zeigt, die mit den seriellen Druckfunktionen print()
und println
verfügbar sind:
/*
SerialFormatting
Print values in various formats to the serial port
*/
char
chrValue
=
65
;
// these are the starting values to print
byte
byteValue
=
65
;
int
intValue
=
65
;
float
floatValue
=
65.0
;
void
setup
()
{
while
(
!
Serial
);
// Wait until serial port's open on Leonardo and SAMD boards
Serial
.
begin
(
9600
);
}
void
loop
()
{
Serial
.
(
"chrValue: "
);
Serial
.
(
chrValue
);
Serial
.
(
" "
);
Serial
.
write
(
chrValue
);
Serial
.
(
" "
);
Serial
.
(
chrValue
,
DEC
);
Serial
.
println
();
Serial
.
(
"byteValue: "
);
Serial
.
(
byteValue
);
Serial
.
(
" "
);
Serial
.
write
(
byteValue
);
Serial
.
(
" "
);
Serial
.
(
byteValue
,
DEC
);
Serial
.
println
();
Serial
.
(
"intValue: "
);
Serial
.
(
intValue
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
DEC
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
HEX
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
OCT
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
BIN
);
Serial
.
println
();
Serial
.
(
"floatValue: "
);
Serial
.
println
(
floatValue
);
Serial
.
println
();
delay
(
1000
);
// delay a second between numbers
chrValue
++
;
// to the next value
byteValue
++
;
intValue
++
;
floatValue
+=
1
;
}
Die Ausgabe sieht folgendermaßen aus:
chrValue
:
A
A
65
byteValue
:
65
A
65
intValue
:
65
65
41
101
1000001
floatValue
:
65.00
chrValue
:
B
B
66
byteValue
:
66
B
66
intValue
:
66
66
42
102
1000010
floatValue
:
66.00
...
Diskussion
Einen Textstring zu drucken ist ganz einfach: Serial.print("hello world");
sendet den Textstring "hello world" an ein Gerät am anderen Ende der seriellen Schnittstelle. Wenn du möchtest, dass nach der Ausgabe ein Zeilenumbruch gedruckt wird, verwende Serial.println()
anstelle von Serial.print()
.
Das Drucken numerischer Werte kann komplizierter sein. Die Art und Weise, wie Byte- und Integer-Werte gedruckt werden, hängt vom Typ der Variablen und einem optionalen Formatierungsparameter ab. Die Arduino-Sprache ist sehr flexibel, wenn es darum geht, wie du dich auf den Wert verschiedener Datentypen beziehen kannst (siehe Rezept 2.2 für mehr über Datentypen). Aber diese Flexibilität kann verwirrend sein, denn selbst wenn die numerischen Werte ähnlich sind, betrachtet der Compiler sie als getrennte Typen mit unterschiedlichen Verhaltensweisen. Wenn du zum Beispiel char
, byte
und int
mit demselben Wert ausgibst, wird nicht unbedingt dieselbe Ausgabe erzeugt.
Hier sind einige konkrete Beispiele; alle erzeugen Variablen, die ähnliche Werte haben:
char
asciiValue
=
'A'
;
// ASCII A has a value of 65
char
chrValue
=
65
;
// an 8-bit signed character, this also is ASCII 'A'
byte
byteValue
=
65
;
// an 8-bit unsigned character, this also is ASCII 'A'
int
intValue
=
65
;
// a 16-bit signed integer set to a value of 65
float
floatValue
=
65.0
;
// float with a value of 65
Tabelle 4-3 zeigt, was du siehst, wenn du Variablen mit Arduino-Routinen ausdruckst.
Datentyp | drucken (val) | drucken (val,DEC) | schreiben (val) | drucken (val,HEX) | drucken (val,OCT) | drucken (val,BIN) |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Das Format von |
||||||
|
|
|||||
|
|
Hinweis
Der Ausdruck Serial.print(val,BYTE);
wird in Arduino-Versionen ab 1.0 nicht mehr unterstützt.
Wenn dein Code erwartet, dass sich Byte-Variablen genauso verhalten wie Char-Variablen (d.h. dass sie als ASCII gedruckt werden), musst du dies in Serial.write(val);
ändern.
Die Skizze in diesem Rezept verwendet für jede Druckanweisung eine eigene Zeile Quellcode. Das kann komplexe Druckanweisungen unhandlich machen. Um zum Beispiel die folgende Zeile zu drucken:
At
5
seconds
:
speed
=
17
,
distance
=
120
müsstest du es normalerweise so codieren:
Serial
.
(
"At "
);
Serial
.
(
t
);
Serial
.
(
" seconds: speed = "
);
Serial
.
(
s
);
Serial
.
(
", distance = "
);
Serial
.
println
(
d
);
Das sind eine Menge Codezeilen für eine einzige Ausgabezeile. Du könntest sie wie folgt kombinieren:
Serial
.
(
"At "
);
Serial
.
(
t
);
Serial
.
(
" seconds, speed = "
);
Serial
.
(
s
);
Serial
.
(
", distance = "
);
Serial
.
println
(
d
);
Oder du könntest die Einfüge-Fähigkeit des von Arduino verwendeten Compilers nutzen, um deine Druckanweisungen zu formatieren. Du kannst einige fortgeschrittene C++-Fähigkeiten (Streaming Insertion Syntax und Templates) nutzen, die du verwenden kannst, wenn du eine Streaming-Vorlage in deinem Sketch deklarierst. Das geht am einfachsten, indem du die von Mikal Hart entwickelte Streaming-Bibliothek einbindest. Du kannst mehr über diese Bibliothek auf Mikals Website lesen und sie mit dem Arduino Library Manager installieren (siehe Rezept 16.2).
Wenn du die Streaming-Bibliothek verwendest, erhältst du die gleiche Ausgabe wie die zuvor gezeigten Zeilen:
Serial
<<
"At "
<<
t
<<
" seconds, speed="
<<
s
<<
", distance="
<<
d
<<
endl
;
Wenn du ein erfahrener Programmierer bist, fragst du dich vielleicht, warum Arduino printf
nicht unterstützt. Das liegt zum Teil daran, dass printf
dynamischen Speicher verwendet und der Arbeitsspeicher auf den 8-Bit-Boards knapp bemessen ist. Neuere 32-Bit-Boards haben zwar viel Speicher, aber das Arduino-Team hat gezögert, die knappe und leicht zu missbrauchende Syntax in die serielle Ausgabe der Arduino-Sprache aufzunehmen.
Obwohl Arduino keine Unterstützung für printf
bietet, kannst du seinen Bruder sprintf
verwenden, um formatierten Text in einem Zeichenpuffer zu speichern und diesen Puffer dann mit Serial.print
/println
zu drucken:
char
buf
[
100
];
sprintf
(
buf
,
"At %d seconds, speed = %d, distance = %d"
,
t
,
s
,
d
);
Serial
.
println
(
buf
);
Aber sprintf
kann gefährlich sein. Wenn die Zeichenkette, die du schreibst, größer ist als dein Puffer, wird er überlaufen. Man weiß nicht, wohin die überlaufenden Zeichen geschrieben werden, aber mit ziemlicher Sicherheit führen sie dazu, dass dein Sketch abstürzt oder sich anderweitig falsch verhält. Mit der Funktion snprintf
kannst du ein Argument übergeben, das die maximale Anzahl von Zeichen angibt (einschließlich des Null-Zeichens, das alle Zeichenketten abschließt). Du kannst die gleiche Länge angeben, mit der du das Array deklarierst (in diesem Fall 100), aber dann musst du dich daran erinnern, das Längenargument zu ändern, wenn du die Pufferlänge änderst. Stattdessen kannst du den sizeof
Operator verwenden, um die Länge des Puffers zu berechnen. Obwohl char
in jedem erdenklichen Fall 1 Byte ist, ist es die bewährte Methode, die Größe des Arrays durch die Größe des enthaltenen Datentyps zu teilen. sizeof (buf) / sizeof (buf[0])
liefert dir also die Länge des Arrays:
snprintf
(
buf
,
sizeof
(
buf
)
/
sizeof
(
buf
[
0
]),
"At %d seconds, speed = %d, distance = %d"
,
t
,
s
,
d
);
Serial
.
println
(
buf
);
Hinweis
Auch wenn du weißt, dass sizeof (buf[0])
garantiert 1 ist, ist es eine gute Angewohnheit, sich das anzugewöhnen. Betrachte den folgenden Code, der 400 ausgibt:
long
buf2
[
100
];
Serial
.
println
(
sizeof
(
buf2
));
Du kannst das richtige Ergebnis mit sizeof (buf2) / sizeof (buf2[0])
erhalten.
Die Verwendung von sprintf
oder snprintf
ist mit Kosten verbunden. Erstens hast du den Overhead des Puffers, in diesem Fall 100 Byte. Hinzu kommt der Aufwand für die Kompilierung der Funktionalität in deinem Sketch. Auf einem Arduino Uno erhöht sich der Speicherbedarf durch das Hinzufügen dieses Codes um 1.648 Byte, das sind 5 % des Speichers des Uno.
Siehe auch
Kapitel 2 enthält weitere Informationen über die von Arduino verwendeten Datentypen. Die Arduino-Webreferenz behandelt die seriellen Befehle und die Streaming-Ausgabe (Einfügung).
4.3 Empfang von seriellen Daten im Arduino
Lösung
Dieser Sketch empfängt eine Ziffer (einzelne Zeichen von 0 bis 9) und lässt die Onboard-LED in einem Rhythmus blinken, der dem Wert der empfangenen Ziffer entspricht:
/*
* SerialReceive sketch
* Blink the LED at a rate proportional to the received digit value
*/
int
blinkDelay
=
0
;
// blink delay stored in this variable
void
setup
()
{
Serial
.
begin
(
9600
);
// Initialize serial port to send and receive at 9600 baud
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// set this pin as output
}
void
loop
()
{
if
(
Serial
.
available
())
// Check to see if at least one character is available
{
char
ch
=
(
char
)
Serial
.
read
();
if
(
isDigit
(
ch
)
)
// is this an ASCII digit between 0 and 9?
{
blinkDelay
=
(
ch
-
'0'
);
// ASCII value converted to numeric value
blinkDelay
=
blinkDelay
*
100
;
// actual delay is 100 ms *" received digit
}
}
blink
();
}
// blink the LED with the on and off times determined by blinkDelay
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
blinkDelay
);
// delay depends on blinkDelay value
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
blinkDelay
);
}
Lade den Sketch hoch und sende Nachrichten mit dem Serial Monitor. Öffne den seriellen Monitor, indem du auf das Monitor-Symbol klickst (siehe Rezept 4.1) und gib eine Ziffer in das Textfeld oben im Fenster des seriellen Monitors ein. Wenn du auf die Schaltfläche Senden klickst, wird das in das Textfeld eingegebene Zeichen gesendet; wenn du eine Ziffer eingibst, solltest du sehen, wie sich die Blinkrate ändert.
Diskussion
Die Funktion Serial.read
gibt einen int
Wert zurück, also solltest du ihn für die folgenden Vergleiche in einen char
umwandeln. Die Umwandlung der empfangenen ASCII-Zeichen in numerische Werte ist vielleicht nicht offensichtlich, wenn du nicht mit der Art und Weise vertraut bist, wie ASCII Zeichen darstellt. Im Folgenden wird das Zeichen ch
in seinen numerischen Wert umgewandelt:
blinkDelay
=
(
ch
-
'0'
);
// ASCII value converted to numeric value
Die ASCII-Zeichen '0'
bis '9'
haben einen Wert von 48 bis 57 (siehe Anhang G). Die Umwandlung von '1'
in den numerischen Wert 1 erfolgt durch Subtraktion von '0'
, denn '1'
hat einen ASCII-Wert von 49, also muss 48 (ASCII '0'
) subtrahiert werden, um es in die Zahl 1 umzuwandeln. Wenn ch
zum Beispiel das Zeichen 1 darstellt, ist sein ASCII-Wert 49. Der Ausdruck 49- '0'
ist dasselbe wie 49-48
. Dies ist gleich 1, also der numerische Wert des Zeichens 1.
Mit anderen Worten: Der Ausdruck (ch - '0')
ist dasselbe wie (ch - 48)
; damit wird der ASCII-Wert der Variablen ch
in einen numerischen Wert umgewandelt.
Du kannst Zahlen mit mehr als einer Ziffer erhalten, indem du die Methoden parseInt
und parseFloat
verwendest, die das Extrahieren von numerischen Werten aus Serial.
vereinfachen (Das funktioniert auch mit Ethernet
und anderen Objekten, die von der Klasse Stream
abgeleitet sind; mehr über Stream-Parsing mit den Netzwerkobjekten findest du in der Einleitung zu Kapitel 15 ).
Serial.parseInt()
und Serial.parseFloat()
lesen Serial
Zeichen und geben deren numerische Darstellung zurück. Nicht-numerische Zeichen vor der Zahl werden ignoriert und die Zahl endet mit dem ersten Zeichen, das keine numerische Ziffer ist (oder ".
", wenn du parseFloat
verwendest). Wenn die Eingabe keine numerischen Zeichen enthält, geben die Funktionen 0 zurück. Du solltest also auf Nullwerte achten und sie entsprechend behandeln. Wenn du den Serial Monitor so konfiguriert hast, dass er einen Zeilenumbruch oder einen Wagenrücklauf (oder beides) sendet, wenn du auf Senden klickst, werden parseInt
oder parseFloat
versuchen (und fehlschlagen), den Zeilenumbruch oder Wagenrücklauf als Zahl zu interpretieren und eine Null zurückgeben. Das würde dazu führen, dass blinkDelay
sofort auf Null gesetzt wird, nachdem du es auf den gewünschten Wert gesetzt hast, was dazu führt, dass es nicht blinkt:
/*
* SerialParsing sketch
* Blink the LED at a rate proportional to the received digit value
*/
int
blinkDelay
=
0
;
void
setup
()
{
Serial
.
begin
(
9600
);
// Initialize serial port to send and receive at 9600 baud
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// set this pin as output
}
void
loop
()
{
if
(
Serial
.
available
())
// Check to see if at least one character is available
{
int
i
=
Serial
.
parseInt
();
if
(
i
!=
0
)
{
blinkDelay
=
i
;
}
}
blink
();
}
// blink the LED with the on and off times determined by blinkDelay
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
blinkDelay
);
// delay depends on blinkDelay value
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
blinkDelay
);
}
In der Diskussion zu Rezept 4.5 findest du ein weiteres Beispiel, in dem parseInt
verwendet wird, um Zahlen aus seriellen Daten zu finden und zu extrahieren.
Eine andere Möglichkeit, Textstrings in Zahlen umzuwandeln, ist die Konvertierungsfunktion der Sprache C namens atoi
(für int
Variablen) oder atol
(für long
Variablen). Diese Funktionen mit obskuren Namen konvertieren eine Zeichenkette in Ganzzahlen oder lange Ganzzahlen. Sie bieten mehr Möglichkeiten, die eingehenden Daten zu manipulieren, allerdings auf Kosten einer höheren Komplexität des Codes. Um diese Funktionen zu verwenden, musst du die gesamte Zeichenkette in einem Zeichenarray empfangen und speichern, bevor du die Umwandlungsfunktion aufrufen kannst.
Dieses Codefragment beendet die eingehenden Ziffern bei jedem Zeichen, das keine Ziffer ist (oder wenn der Puffer voll ist):
const
int
maxChars
=
5
;
// an int string contains up to 5 digits and
// is terminated by a 0 to indicate end of string
char
strValue
[
maxChars
+
1
];
// must be big enough for digits and terminating null
int
idx
=
0
;
// index into the array storing the received digits
void
loop
()
{
if
(
Serial
.
available
())
{
char
ch
=
(
char
)
Serial
.
read
();
if
(
idx
<
maxChars
&&
isDigit
(
ch
)
){
strValue
[
idx
++
]
=
ch
;
// add the ASCII character to the string;
}
else
{
// here when buffer full or on the first nondigit
strValue
[
idx
]
=
0
;
// terminate the string with a 0
blinkDelay
=
atoi
(
strValue
);
// use atoi to convert the string to an int
idx
=
0
;
}
}
blink
();
}
strValue
ist eine numerische Zeichenfolge, die aus den über die serielle Schnittstelle empfangenen Zeichen gebildet wird.
Tipp
Siehe Rezept 2.6 für Informationen über Zeichenketten.
atoi
(kurz für ASCII to integer) ist eine Funktion, die eine Zeichenkette in eine ganze Zahl konvertiert (atol
konvertiert in eine long
ganze Zahl).
Arduino unterstützt auch die Funktion serialEvent
, die du verwenden kannst, um eingehende serielle Zeichen zu verarbeiten. Wenn du in deinem Sketch Code in einer serialEvent
Funktion hast, wird diese jedes Mal von der loop
Funktion aufgerufen. Der folgende Sketch führt die gleiche Funktion aus wie der erste Sketch in diesem Rezept, verwendet aber serialEvent
, um die eingehenden Zeichen zu verarbeiten:
/*
* SerialEvent Receive sketch
* Blink the LED at a rate proportional to the received digit value
*/
int
blinkDelay
=
0
;
// blink delay stored in this variable
void
setup
()
{
Serial
.
begin
(
9600
);
// Initialize serial port to send and receive at 9600 baud
pinMode
(
LED_BUILTIN
,
OUTPUT
);
// set this pin as output
}
void
loop
()
{
blink
();
}
void
serialEvent
()
{
while
(
Serial
.
available
())
{
char
ch
=
(
char
)
Serial
.
read
();
Serial
.
write
(
ch
);
if
(
isDigit
(
ch
)
)
// is this an ASCII digit between 0 and 9?
{
blinkDelay
=
(
ch
-
'0'
);
// ASCII value converted to numeric value
blinkDelay
=
blinkDelay
*
100
;
// actual delay is 100 ms times digit
}
}
}
// blink the LED with the on and off times determined by blinkDelay
void
blink
()
{
digitalWrite
(
LED_BUILTIN
,
HIGH
);
delay
(
blinkDelay
);
// delay depends on blinkDelay value
digitalWrite
(
LED_BUILTIN
,
LOW
);
delay
(
blinkDelay
);
}
Siehe auch
Eine Websuche nach "atoi" oder "atol" liefert viele Hinweise auf diese Funktionen (siehe den Wikipedia-Verweis hier).
4.4 Mehrere Textfelder vom Arduino in einer einzigen Nachricht senden
Problem
Du möchtest eine Nachricht senden, die mehr als ein Feld an Informationen pro Nachricht enthält. Deine Nachricht kann zum Beispiel Werte von zwei oder mehr Sensoren enthalten. Du möchtest diese Werte in einem Programm wie Processing verwenden, das auf einem Computer oder einem Gerät wie einem Raspberry Pi läuft.
Lösung
Das geht ganz einfach, indem du einen Textstring sendest, in dem alle Felder durch ein Trennzeichen (z. B. ein Komma) getrennt sind:
// CommaDelimitedOutput sketch
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
int
value1
=
10
;
// some hardcoded values to send
int
value2
=
100
;
int
value3
=
1000
;
Serial
.
(
'H'
);
// unique header to identify start of message
Serial
.
(
","
);
Serial
.
(
value1
,
DEC
);
Serial
.
(
","
);
Serial
.
(
value2
,
DEC
);
Serial
.
(
","
);
Serial
.
(
value3
,
DEC
);
Serial
.
println
();
// send a carriage return and line feed
delay
(
100
);
}
Hier ist der Processing-Sketch, der diese Daten von der seriellen Schnittstelle liest:
// Processing Sketch to read comma delimited serial
// expects format: H,1,2,3
import
processing
.
serial
.
*
;
Serial
myPort
;
// Create object from Serial class
char
HEADER
=
'H'
;
// character to identify the start of a message
short
LF
=
10
;
// ASCII linefeed
// WARNING!
// If necessary change the definition below to the correct port
short
portIndex
=
0
;
// select the com port, 0 is the first port
void
setup
()
{
size
(
200
,
200
);
println
(
(
Object
[])
Serial
.
list
());
println
(
" Connecting to -> "
+
Serial
.
list
()[
portIndex
]);
myPort
=
new
Serial
(
this
,
Serial
.
list
()[
portIndex
],
9600
);
}
void
draw
()
{
if
(
myPort
.
available
()
>
0
)
{
String
message
=
myPort
.
readStringUntil
(
LF
);
// read serial data
if
(
message
!=
null
)
{
message
=
message
.
trim
();
// Remove whitespace from start/end of string
println
(
message
);
String
[]
data
=
message
.
split
(
","
);
// Split the comma-separated message
if
(
data
[
0
].
charAt
(
0
)
==
HEADER
&&
data
.
length
==
4
)
// check validity
{
for
(
int
i
=
1
;
i
<
data
.
length
;
i
++
)
// skip header (start at 1, not 0)
{
println
(
"Value "
+
i
+
" = "
+
data
[
i
]);
// Print the field values
}
println
();
}
}
}
}
Diskussion
Der Arduino-Code in der Lösung dieses Rezepts sendet die folgende Zeichenkette an die serielle Schnittstelle (\r
steht für einen Wagenrücklauf und \n
für einen Zeilenvorschub):
H
,
10
,
100
,
1000
\
r
\
n
Du musst ein Trennzeichen wählen, das nie in den eigentlichen Daten vorkommt; wenn deine Daten zum Beispiel nur aus numerischen Werten bestehen, ist ein Komma eine gute Wahl für ein Trennzeichen. Du möchtest vielleicht auch sicherstellen, dass die empfangende Seite den Anfang einer Nachricht bestimmen kann, um sicherzustellen, dass sie alle Daten für alle Felder hat. Dazu sendest du ein Header-Zeichen, das den Beginn der Nachricht angibt. Das Header-Zeichen muss außerdem eindeutig sein; es darf in keinem der Datenfelder vorkommen und muss sich auch vom Trennzeichen unterscheiden. In diesem Beispiel wird ein großes H verwendet, um den Beginn der Nachricht zu kennzeichnen. Die Nachricht besteht aus der Kopfzeile, drei durch Komma getrennten numerischen Werten als ASCII-Zeichenfolge und einem Wagenrücklauf und Zeilenvorschub.
Die Wagenrücklauf- und Zeilenvorschubzeichen werden immer dann gesendet, wenn der Arduino mit der Funktion println()
druckt, damit die empfangende Seite weiß, dass der gesamte Nachrichtenstring empfangen wurde. Da der Verarbeitungscode myPort.readStringUntil(LF)
den Wagenrücklauf ('\r'
) enthält, der vor dem Zeilenvorschub erscheint, verwendet dieser Sketch trim()
, um alle führenden oder nachfolgenden Leerzeichen zu entfernen, also auch Leerzeichen, Tabulatoren, Wagenrückläufe und Zeilenvorschübe.
Der Verarbeitungscode liest die Nachricht als String und verwendet die Java-Methode split()
, um ein Array aus den kommagetrennten Feldern zu erstellen.
Hinweis
In den meisten Fällen ist die erste serielle Schnittstelle diejenige, die du brauchst, wenn du einen Mac (oder einen PC ohne physischen RS-232-Anschluss) verwendest, und die letzte serielle Schnittstelle ist diejenige, die du brauchst, wenn du einen PC mit einem physischen RS-232-Anschluss verwendest. Der Processing-Sketch enthält Code, der die verfügbaren Ports und den aktuell ausgewählten anzeigt. Du musst also überprüfen, ob dies der Port ist, der mit dem Arduino verbunden ist. Es kann sein, dass du den Sketch einmal ausführen musst, eine Fehlermeldung erhältst und die Liste der seriellen Schnittstellen in der Processing-Konsole am unteren Rand des Bildschirms überprüfen musst, um festzustellen, welchen Wert du für portIndex
verwenden solltest.
Die Verwendung von Processing zur Anzeige von Sensorwerten kann dir bei der Fehlersuche viel Zeit ersparen, da du die Daten visualisieren kannst. Während CSV ein gängiges und nützliches Format ist, ist JSON (JavaScript Object Notation) aussagekräftiger und auch für Menschen lesbar. JSON ist ein gängiges Datenaustauschformat, das für den Austausch von Nachrichten über ein Netzwerk verwendet wird. Der folgende Sketch liest den Beschleunigungssensor aus dem Arduino WiFi Rev 2 oder Arduino Nano 33 BLE Sense (entkommentiere die entsprechende Zeile include
) und sendet ihn mit JSON an die serielle Schnittstelle (zum Beispiel: {'x': 0.66, 'y': 0.59, 'z': -0.49, }
):
/*
AccelerometerToJSON. Sends JSON-formatted representation of
accelerometer readings.
*/
#include <Arduino_LSM6DS3.h>
// Arduino WiFi R2
//#include <Arduino_LSM9DS1.h> // Arduino BLE Sense
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
if
(
!
IMU
.
begin
())
{
while
(
1
)
{
Serial
.
println
(
"Error: Failed to initialize IMU"
);
delay
(
3000
);
}
}
}
void
loop
()
{
float
x
,
y
,
z
;
if
(
IMU
.
accelerationAvailable
())
{
IMU
.
readAcceleration
(
x
,
y
,
z
);
Serial
.
(
"{"
);
Serial
.
(
"'x': "
);
Serial
.
(
x
);
Serial
.
(
", "
);
Serial
.
(
"'y': "
);
Serial
.
(
y
);
Serial
.
(
", "
);
Serial
.
(
"'z': "
);
Serial
.
(
z
);
Serial
.
(
", "
);
Serial
.
println
(
"}"
);
delay
(
200
);
}
}
Der folgende Processing-Sketch ermöglicht die visuelle Anzeige von bis zu 12 Werten in Echtzeit, die vom Arduino gesendet werden. Er zeigt Fließkommawerte in einem Bereich von -5 bis +5 an:
/*
* ShowSensorData.
*
* Displays bar graph of JSON sensor data ranging from -127 to 127
* expects format as: "{'label1': value, 'label2': value,}\n"
* for example:
* {'x': 1.0, 'y': -1.0, 'z': 2.1,}
*/
import
processing
.
serial
.
*
;
import
java
.
util
.
Set
;
Serial
myPort
;
// Create object from Serial class
PFont
fontA
;
// font to display text
int
fontSize
=
12
;
short
LF
=
10
;
// ASCII linefeed
int
rectMargin
=
40
;
int
windowWidth
=
600
;
int
maxLabelCount
=
12
;
// Increase this if you need to support more labels
int
windowHeight
=
rectMargin
+
(
maxLabelCount
+
1
)
*
(
fontSize
*
2
);
int
rectWidth
=
windowWidth
-
rectMargin
*
2
;
int
rectHeight
=
windowHeight
-
rectMargin
;
int
rectCenter
=
rectMargin
+
rectWidth
/
2
;
int
origin
=
rectCenter
;
int
minValue
=
-
5
;
int
maxValue
=
5
;
float
scale
=
float
(
rectWidth
)
/
(
maxValue
-
minValue
);
// WARNING!
// If necessary change the definition below to the correct port
short
portIndex
=
0
;
// select the com port, 0 is the first port
void
settings
()
{
size
(
windowWidth
,
windowHeight
);
}
void
setup
()
{
println
(
(
Object
[])
Serial
.
list
());
println
(
" Connecting to -> "
+
Serial
.
list
()[
portIndex
]);
myPort
=
new
Serial
(
this
,
Serial
.
list
()[
portIndex
],
9600
);
fontA
=
createFont
(
"Arial.normal"
,
fontSize
);
textFont
(
fontA
);
}
void
draw
()
{
if
(
myPort
.
available
()
>
0
)
{
String
message
=
myPort
.
readStringUntil
(
LF
);
if
(
message
!=
null
)
{
// Load the JSON data from the message
JSONObject
json
=
new
JSONObject
();
try
{
json
=
parseJSONObject
(
message
);
}
catch
(
Exception
e
)
{
println
(
"Could not parse ["
+
message
+
"]"
);
}
// Copy the JSON labels and values into separate arrays.
ArrayList
<
String
>
labels
=
new
ArrayList
<
String
>
();
ArrayList
<
Float
>
values
=
new
ArrayList
<
Float
>
();
for
(
String
key
:
(
Set
<
String
>
)
json
.
keys
())
{
labels
.
add
(
key
);
values
.
add
(
json
.
getFloat
(
key
));
}
// Draw the grid and chart the values
background
(
255
);
drawGrid
(
labels
);
fill
(
204
);
for
(
int
i
=
0
;
i
<
values
.
size
();
i
++
)
{
drawBar
(
i
,
values
.
get
(
i
));
}
}
}
}
// Draw a bar to represent the current sensor reading
void
drawBar
(
int
yIndex
,
float
value
)
{
rect
(
origin
,
yPos
(
yIndex
)
-
fontSize
,
value
*
scale
,
fontSize
);
}
void
drawGrid
(
ArrayList
<
String
>
sensorLabels
)
{
fill
(
0
);
// Draw the minimum value label and a line for it
text
(
minValue
,
xPos
(
minValue
),
rectMargin
-
fontSize
);
line
(
xPos
(
minValue
),
rectMargin
,
xPos
(
minValue
),
rectHeight
+
fontSize
);
// Draw the center value label and a line for it
text
((
minValue
+
maxValue
)
/
2
,
rectCenter
,
rectMargin
-
fontSize
);
line
(
rectCenter
,
rectMargin
,
rectCenter
,
rectHeight
+
fontSize
);
// Draw the maximum value label and a line for it
text
(
maxValue
,
xPos
(
maxValue
),
rectMargin
-
fontSize
);
line
(
xPos
(
maxValue
),
rectMargin
,
xPos
(
maxValue
),
rectHeight
+
fontSize
);
// Print each sensor label
for
(
int
i
=
0
;
i
<
sensorLabels
.
size
();
i
++
)
{
text
(
sensorLabels
.
get
(
i
),
fontSize
,
yPos
(
i
));
text
(
sensorLabels
.
get
(
i
),
xPos
(
maxValue
)
+
fontSize
,
yPos
(
i
));
}
}
// Calculate a y position, taking into account margins and font sizes
int
yPos
(
int
index
)
{
return
rectMargin
+
fontSize
+
(
index
*
fontSize
*
2
);
}
// Calculate a y position, taking into account the scale and origin
int
xPos
(
int
value
)
{
return
origin
+
int
(
scale
*
value
);
}
Abbildung 4-4 zeigt, wie die Werte des Beschleunigungsmessers (x, y, z
) angezeigt werden. Die Balken werden angezeigt, wenn du das Gerät schwenkst.
Der Wertebereich und der Ursprung des Diagramms können bei Bedarf leicht geändert werden. Wenn du zum Beispiel Balken mit Werten von 0 bis 1.024 auf der linken Achse anzeigen möchtest, kannst du Folgendes tun:
int
origin
=
rectMargin
;
// rectMargin is the left edge of the graphing area
int
minValue
=
0
;
int
maxValue
=
1024
;
Wenn du keinen Beschleunigungssensor hast, kannst du mit dem folgenden einfachen Sketch Werte erzeugen, die analoge Eingangswerte anzeigen. Wenn du keine Sensoren zum Anschließen hast, kannst du mit deinen Fingern über die Unterseite der analogen Pins streichen, um Werte zu erzeugen, die in der Processing-Skizze angezeigt werden können. Die Werte reichen von 0 bis 1.023, also ändere den Ursprung sowie die Minimal- und Maximalwerte in der Processing-Skizze, wie im vorherigen Abschnitt beschrieben:
/*
AnalogToJSON. Sends JSON-formatted representation of
analog readings.
*/
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
}
void
loop
()
{
Serial
.
(
"{"
);
Serial
.
(
"'A0': "
);
Serial
.
(
analogRead
(
A0
));
Serial
.
(
", "
);
Serial
.
(
"'A1': "
);
Serial
.
(
analogRead
(
A1
));
Serial
.
(
", "
);
Serial
.
(
"'A2': "
);
Serial
.
(
analogRead
(
A2
));
Serial
.
(
", "
);
Serial
.
(
"'A3': "
);
Serial
.
(
analogRead
(
A3
));
Serial
.
(
", "
);
Serial
.
(
"'A4': "
);
Serial
.
(
analogRead
(
A4
));
Serial
.
(
", "
);
Serial
.
(
"'A5': "
);
Serial
.
(
analogRead
(
A5
));
Serial
.
(
", "
);
Serial
.
println
(
"}"
);
}
Siehe auch
Auf der Processing-Website findest du weitere Informationen zur Installation und Verwendung dieser Programmierumgebung.
Es gibt auch eine Reihe von Büchern über Processing:
-
Getting Started with Processing, Second Edition, von Casey Reas und Ben Fry (Make)
-
Processing: A Programming Handbook for Visual Designers and Artists von Casey Reas und Ben Fry (MIT Press)
-
Visualisierung von Daten von Ben Fry (O'Reilly)
-
Processing: Creative Coding and Computational Art von Ira Greenberg (Apress)
-
Making Things Talk von Tom Igoe (Make Community) (Dieses Buch behandelt Processing und Arduino und enthält viele Beispiele für Kommunikationscode).
4.5 Empfangen mehrerer Textfelder in einer einzigen Nachricht in Arduino
Lösung
Arduino verfügt nicht über die Funktion split()
, die im Processing-Code in Rezept 4.4 verwendet wird, aber eine ähnliche Funktionalität kann wie in diesem Rezept gezeigt implementiert werden. Der folgende Code empfängt eine Nachricht mit einem einzelnen Zeichen H
als Kopfzeile, gefolgt von drei numerischen Feldern, die durch Kommas getrennt sind und mit einem Zeilenumbruch abgeschlossen werden:
/*
* SerialReceiveMultipleFields sketch
* This code expects a message in the format: H,12,345,678
* This code requires a newline character to indicate the end of the data
* Set the serial monitor to send newline characters
*/
const
int
NUMBER_OF_FIELDS
=
3
;
// how many comma separated fields we expect
int
values
[
NUMBER_OF_FIELDS
];
// array holding values for all the fields
void
setup
()
{
Serial
.
begin
(
9600
);
// Initialize serial port to send and receive at 9600 baud
}
void
loop
()
{
if
(
Serial
.
available
())
{
if
(
Serial
.
read
()
==
'H'
)
{
// Read the values
for
(
int
i
=
0
;
i
<
NUMBER_OF_FIELDS
;
i
++
)
{
values
[
i
]
=
Serial
.
parseInt
();
}
// Display the values in comma-separated format
Serial
.
(
values
[
0
]);
// First value
// Print the rest of the values with a leading comma
for
(
int
i
=
1
;
i
<
NUMBER_OF_FIELDS
;
i
++
)
{
Serial
.
(
","
);
Serial
.
(
values
[
i
]);
}
Serial
.
println
();
}
}
}
Diskussion
Dieser Sketch verwendet die Methode parseInt
, mit der sich numerische Werte aus seriellen und Web-Streams leicht extrahieren lassen. Dies ist ein Beispiel dafür, wie du diese Funktion nutzen kannst(in Kapitel 15 findest du weitere Beispiele für das Parsen von Streams). Du kannst diese Skizze testen, indem du den seriellen Monitor öffnest und eine kommagetrennte Nachricht wie H1,2,3
sendest. parseInt
ignoriert alles außer einem Minuszeichen und einer Ziffer, muss also nicht kommagetrennt sein. Du kannst auch ein anderes Trennzeichen wie H1/2/3
verwenden. Der Sketch speichert die Zahlen in einem Array und gibt sie dann, durch Kommas getrennt, aus.
Hinweis
Dieser Sketch zeigt eine kommagetrennte Liste auf eine Weise an, die zunächst ungewöhnlich erscheinen mag. Er gibt die erste empfangene Zahl aus (z. B. 1
) und druckt dann die restlichen Zahlen, jeweils mit einem Komma davor (z. B. ,2
und dann ,3
). Du könntest auch weniger Codezeilen verwenden und nach jeder Zahl ein Komma ausgeben, aber dann hättest du am Ende 1,2,3,
statt 1,2,3
. Viele andere Programmiersprachen, darunter auch Processing, bieten eine Funktion join
an, mit der ein Array zu einer abgegrenzten Zeichenkette kombiniert werden kann, aber Arduino nicht.
Die Stream-Parsing-Funktionen nehmen eine Zeitüberschreitung beim Warten auf ein Zeichen in Kauf; der Standardwert ist eine Sekunde. Wenn keine Ziffern empfangen wurden und parseInt
eine Zeitüberschreitung hat, wird 0 zurückgegeben. Du kannst die Zeitüberschreitung ändern, indem du Stream.setTimeout(timeoutPeriod)
aufrufst. Der Timeout-Parameter ist eine long
ganze Zahl, die die Anzahl der Millisekunden angibt. Der Timeout-Bereich reicht also von 1 ms bis 2.147.483.647 ms.
Stream.setTimeout(2147483647);
wird das Timeout-Intervall auf knapp 25 Tage geändert.
Im Folgenden findest du eine Übersicht über die von Arduino unterstützten Stream-Parsing-Methoden (nicht alle werden im vorangegangenen Beispiel verwendet):
bool find(char *target);
-
Liest aus dem Stream, bis das angegebene Ziel gefunden wird. Sie gibt
true
zurück, wenn der Zielstring gefunden wurde. Ein Rückgabewert vonfalse
bedeutet, dass die Daten nirgendwo im Stream gefunden wurden und dass keine weiteren Daten verfügbar sind. Beachte, dassStream
den Stream nur einmal durchläuft; es gibt keine Möglichkeit, zurückzugehen, um etwas anderes zu finden oder zu erhalten (siehe die MethodefindUntil
). bool findUntil(char *target, char *terminate);
-
Ähnlich wie die Methode
find
, aber die Suche wird beendet, wenn der Stringterminate
gefunden wird. Gibttrue
nur zurück, wenn das Ziel gefunden wird. Dies ist nützlich, um eine Suche nach einem Schlüsselwort oder einem Terminator zu beenden. Zum Beispiel:finder
.
findUntil
(
"target"
,
"
\n
"
);
versucht, die Zeichenkette
"value"
zu finden, hält aber bei einem Zeilenumbruch an, damit dein Sketch etwas anderes tun kann, wenn das Ziel nicht gefunden wird. long parseInt();
-
Gibt den ersten gültigen (
long
) ganzzahligen Wert zurück. Führende Zeichen, die keine Ziffern oder ein Minuszeichen sind, werden übersprungen. Die Ganzzahl wird durch das erste nicht-zifferige Zeichen nach der Zahl abgeschlossen. Wenn keine Ziffern gefunden werden, gibt die Funktion0
zurück. long parseInt(char skipChar);
-
Wie
parseInt
, aber die AngabeskipChar
innerhalb des numerischen Wertes wird ignoriert. Dies kann hilfreich sein, wenn ein einzelner numerischer Wert geparst wird, der ein Komma zwischen Ziffernblöcken in großen Zahlen verwendet, aber bedenke, dass mit Kommas formatierte Textwerte nicht als kommagetrennte Zeichenkette geparst werden können (zum Beispiel würde 32.767 als 32767 geparst werden). float parseFloat();
-
Die
float
Version vonparseInt
. Alle Zeichen außer Ziffern, einem Dezimalpunkt oder einem führenden Minuszeichen werden übersprungen. size_t readBytes(char *buffer, size_t length);
-
Legt die eingehenden Zeichen in den angegebenen Puffer, bis eine Zeitüberschreitung eintritt oder die Länge der Zeichen gelesen wurde. Gibt die Anzahl der Zeichen zurück, die im Puffer abgelegt wurden.
size_t readBytesUntil(char terminator, char *buf, size_t length);
-
Fügt die eingehenden Zeichen in den angegebenen Puffer ein, bis das Zeichen
terminator
erkannt wird. Zeichenketten, die länger als die angegebenelength
sind, werden abgeschnitten, damit sie passen. Die Funktion gibt die Anzahl der in den Puffer eingefügten Zeichen zurück.
Siehe auch
In Kapitel 15 findest du weitere Beispiele für Stream-Parsing, um Daten aus einem Stream zu finden und zu extrahieren. Mit der ArduinoJson-Bibliothek kannst du JSON-formatierte Nachrichten (siehe Rezept 4.4) in Arduino parsen.
4.6 Senden von Binärdaten vom Arduino
Lösung
Dieser Sketch sendet einen Header gefolgt von zwei Integer-Werten (zwei Byte) als Binärdaten. Der Sketch verwendet short
, weil es zwei Bytes sind, egal ob du ein 8-Bit- oder 32-Bit-Board hast (siehe Rezept 2.2). Die Werte werden mit der Arduino random
Funktion erzeugt (siehe Rezept 3.11). Obwohl random
einen long
Wert zurückgibt, bedeutet das Argument von 599, dass es nie einen Wert über dieser Zahl zurückgibt, die klein genug ist, um in ein short
zu passen:
/*
* SendBinary sketch
* Sends a header followed by two random integer values as binary data.
*/
short
intValue
;
// short integer value (two bytes on all boards)
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
Serial
.
(
'H'
);
// send a header character
// send a random integer
intValue
=
random
(
599
);
// generate a random number between 0 and 599
// send the two bytes that comprise that integer
Serial
.
write
(
lowByte
(
intValue
));
// send the low byte
Serial
.
write
(
highByte
(
intValue
));
// send the high byte
// send another random integer
intValue
=
random
(
599
);
// generate a random number between 0 and 599
// send the two bytes that comprise that integer
Serial
.
write
(
lowByte
(
intValue
));
// send the low byte
Serial
.
write
(
highByte
(
intValue
));
// send the high byte
delay
(
1000
);
}
Diskussion
Das Versenden von Binärdaten erfordert eine sorgfältige Planung, denn du wirst Kauderwelsch erhalten, wenn die sendende und die empfangende Seite nicht genau verstehen und vereinbaren, wie die Daten gesendet werden sollen. Anders als bei Textdaten, bei denen das Ende einer Nachricht durch das Vorhandensein des abschließenden Wagenrücklaufs (oder eines anderen eindeutigen Zeichens, das du auswählst) bestimmt werden kann, ist es unter Umständen nicht möglich, den Beginn oder das Ende einer Binärnachricht allein anhand der Daten zu bestimmen - Daten, die jeden Wert haben können, können daher den Wert eines Headers oder eines Abschlusszeichens haben.
Dies lässt sich umgehen, indem du deine Nachrichten so gestaltest, dass die sendende und die empfangende Seite genau wissen, wie viele Bytes erwartet werden. Das Ende einer Nachricht wird durch die Anzahl der gesendeten Bytes bestimmt und nicht durch die Erkennung eines bestimmten Zeichens. Dies kann durch das Senden eines Anfangswertes erreicht werden, der angibt, wie viele Bytes folgen werden. Oder du kannst die Größe der Nachricht so festlegen, dass sie groß genug ist, um die zu sendenden Daten aufzunehmen. Beides ist nicht immer einfach, da verschiedene Plattformen und Sprachen unterschiedliche Größen für die binären Datentypen verwenden können - sowohl die Anzahl der Bytes als auch ihre Reihenfolge können sich von Arduino unterscheiden. Zum Beispiel definiert Arduino einen int
als zwei Bytes (16 Bits) auf 8-Bit-Plattformen, vier Bytes (32 Bits) auf einer 32-Bit-Plattform, und Processing (Java) definiert einen int
als vier Bytes (short
ist der Java-Typ für eine Zwei-Byte-Ganzzahl). Das Senden eines int
Wertes als Text (wie in früheren Textrezepten) vereinfacht dieses Problem, da jede einzelne Ziffer als fortlaufende Ziffer gesendet wird (genau wie die Zahl geschrieben wird). Die empfangende Seite erkennt, wenn der Wert vollständig empfangen wurde, an einem Wagenrücklauf oder einem anderen nicht-zifferigen Trennzeichen. Binäre Überweisungen können die Zusammensetzung einer Nachricht nur kennen, wenn sie im Voraus definiert oder in der Nachricht angegeben ist .
Die Lösung dieses Rezepts erfordert ein Verständnis der Datentypen auf den Sende- und Empfangsplattformen und eine sorgfältige Planung. Rezept 4.7 zeigt einen Beispielcode, der die Sprache Processing verwendet, um diese Nachrichten zu empfangen.
Einzelne Bytes zu senden ist einfach; benutze Serial.write(byteVal)
. Um eine Ganzzahl von Arduino aus zu senden, musst du die Low- und High-Bytes senden, aus denen die Ganzzahl besteht (siehe Rezept 2.2 für weitere Informationen zu Datentypen). Dazu verwendest du die Funktionen lowByte
und highByte
(siehe Rezept 3.14):
Serial
.
write
(
lowByte
(
intValue
));
Serial
.
write
(
highByte
(
intValue
));
Um eine long
Ganzzahl zu senden, werden die vier Bytes, aus denen eine long
besteht, in zwei Schritten zerlegt. Die long
wird zunächst in zwei 16-Bit-Ganzzahlen zerlegt; jede wird dann mit der oben beschriebenen Methode für das Senden von Ganzzahlen gesendet:
long
longValue
=
2147483648
;
int
intValue
;
Zuerst sendest du den unteren 16-Bit-Ganzzahlwert:
intValue
=
longValue
&
0xFFFF
;
// get the value of the lower 16 bits
Serial
.
write
(
lowByte
(
intValue
));
Serial
.
write
(
highByte
(
intValue
));
Dann sendest du den höheren 16-Bit-Ganzzahlwert:
intValue
=
longValue
>>
16
;
// get the value of the higher 16 bits
Serial
.
write
(
lowByte
(
intValue
));
Serial
.
write
(
highByte
(
intValue
));
Vielleicht findest du es praktisch, Funktionen zum Senden der Daten zu erstellen. Hier ist eine Funktion, die den oben gezeigten Code verwendet, um eine 16-Bit-Ganzzahl an die serielle Schnittstelle zu senden:
// function to send the given integer value to the serial port
void
sendBinary
(
int
value
)
{
// send the two bytes that comprise a two-byte (16-bit) integer
Serial
.
write
(
lowByte
(
value
));
// send the low byte
Serial
.
write
(
highByte
(
value
));
// send the high byte
}
Die folgende Funktion sendet den Wert einer long
(Vier-Byte)-Ganzzahl, indem sie zuerst die beiden Low-Bytes (ganz rechts) sendet, gefolgt von den High-Bytes (ganz links):
// function to send the given long integer value to the serial port
void
sendBinary
(
long
value
)
{
// first send the low 16-bit integer value
int
temp
=
value
&
0xFFFF
;
// get the value of the lower 16 bits
sendBinary
(
temp
);
// then send the higher 16-bit integer value:
temp
=
value
>>
16
;
// get the value of the higher 16 bits
sendBinary
(
temp
);
}
Diese Funktionen zum Senden von binären int
und long
Werten haben den gleichen Namen: sendBinary
. Der Compiler unterscheidet sie anhand der Art des Wertes, den du für den Parameter verwendest. Wenn dein Code sendBinary
mit einem Zwei-Byte-Wert aufruft, wird die als void sendBinary(int value)
deklarierte Version aufgerufen. Wenn der Parameter ein long
Wert ist, wird die als void sendBinary(long value)
deklarierte Version aufgerufen. Dieses Verhalten wird als Funktionsüberladung bezeichnet. Rezept 4.2 bietet eine weitere Veranschaulichung dafür; die unterschiedliche Funktionalität, die du in Serial.print
gesehen hast, ist darauf zurückzuführen, dass der Compiler die verschiedenen verwendeten Variablentypen unterscheidet.
Du kannst binäre Daten auch mit Strukturen senden. Strukturen sind ein Mechanismus zum Organisieren von Daten. Wenn du mit ihrer Verwendung noch nicht vertraut bist, solltest du dich lieber an die oben beschriebenen Lösungen halten. Wer mit dem Konzept der Strukturzeiger vertraut ist, kann die Bytes innerhalb einer Struktur als Binärdaten an die serielle Schnittstelle senden. Sie schließt das Header-Zeichen in die Struktur ein, sodass sie die gleichen Nachrichten wie die Lösung sendet:
/*
SendBinaryStruct sketch
Sends a struct as binary data.
*/
typedef
struct
{
char
padding
;
// ensure same alignment on 8-bit and 32-bit
char
header
;
short
intValue1
;
short
intValue2
;
}
shortMsg
;
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
()
{
shortMsg
myStruct
=
{
0
,
'H'
,
(
short
)
random
(
599
),
(
short
)
random
(
599
)
};
sendStructure
((
byte
*
)
&
myStruct
,
sizeof
(
myStruct
));
delay
(
1000
);
}
void
sendStructure
(
byte
*
structurePointer
,
int
structureLength
)
{
int
i
;
for
(
i
=
0
;
i
<
structureLength
;
i
++
)
{
Serial
.
write
(
structurePointer
[
i
]);
}
}
Hinweis
Wenn du die Struktur shortMsg
ohne das Member padding
deklarieren würdest, könntest du feststellen, dass die Länge der Struktur auf einem Board fünf Byte und auf einem anderen Board sechs Byte beträgt. Das liegt daran, dass der Compiler für eine Architektur eine Fünf-Byte-Struktur zulässt, während er bei einer anderen ein oder mehrere zusätzliche Bytes einfügt, um sicherzustellen, dass die Größe der Struktur ein Vielfaches der natürlichen Datengröße des Boards ist. Indem du das Padding vorne einfügst, stellst du sicher, dass char
an einer geraden Grenze erscheint (das zweite von zwei Bytes), so dass es unwahrscheinlich ist, dass der Compiler Padding zwischen den Werten char
und short
einfügt. Dieser Trick funktioniert aber nicht immer, also musst du eventuell experimentieren. Ein weiterer Vorteil des Auffüllens vor dem Kopfzeichen ist, dass der Code in Rezept 4.7 Eingaben ignoriert, bis er ein H
Zeichen sieht.
Das Versenden von Daten als Binärbytes ist effizienter als das Versenden von Daten als Text, aber es funktioniert nur dann zuverlässig, wenn sich die sendende und die empfangende Seite genau über die Zusammensetzung der Daten einig sind. Hier ist eine Zusammenfassung der wichtigen Dinge, die du beim Schreiben deines Codes überprüfen musst:
- Variable Größe
-
Achte darauf, dass die Größe der gesendeten Daten auf beiden Seiten gleich ist. Ein Integer ist auf dem Arduino Uno und anderen 8-Bit-Boards zwei Byte groß, auf 32-Bit-Boards und den meisten anderen Plattformen vier Byte. Schau immer in der Dokumentation deiner Programmiersprache nach, um sicherzustellen, dass die Datentypen übereinstimmen. Es ist kein Problem, eine zwei Byte große Arduino-Ganzzahl als vier Byte große Ganzzahl in Processing zu empfangen, solange Processing nur zwei Bytes erwartet. Achte aber darauf, dass die sendende Seite keine Werte verwendet, die den von der empfangenden Seite verwendeten Typ überlaufen lassen.
- Byte-Reihenfolge
-
Vergewissere dich, dass die Bytes innerhalb einer
int
oderlong
in der Reihenfolge gesendet werden, die von der Empfangsseite erwartet wird. Die Lösung verwendet die gleiche Byte-Reihenfolge, die auch die Arduino-Boards intern verwenden, nämlich Little Endian. Dies bezieht sich auf die Reihenfolge der Bytes, bei der das niedrigstwertige Byte zuerst erscheint. Technisch gesehen sind ARM-basierte Arduino-kompatible Boards bi-endian, d.h. sie können so konfiguriert werden, dass sie sowohl den Big-Endian- als auch den Little-Endian-Modus verwenden, aber in der Praxis ist es unwahrscheinlich, dass du ein Arduino-Board findest, das nicht Little-Endian ist. Wenn dulowByte
undhighByte
verwendest, um eine Ganzzahl zu zerlegen, hast du die Kontrolle über die Reihenfolge, in der die Bytes gesendet werden. Wenn du jedoch einestruct
im Binärformat sendest, wird die interne Darstellung der struct verwendet, die von der Endianness deines Boards beeinflusst wird. Wenn du also den struct-Code mit dem Processing-Code aus Rezept 4.7 ausführst und nicht den gewünschten Wert (16.384) siehst, kann es sein, dass dein struct vertauscht wurde. - Synchronisation
-
Stelle sicher, dass deine Empfangsseite den Anfang und das Ende einer Nachricht erkennen kann. Wenn du mitten in einem Übertragungsstrom anfängst zu lauschen, bekommst du keine gültigen Daten. Das kannst du erreichen, indem du eine Folge von Bytes sendest, die im Hauptteil der Nachricht nicht vorkommt. Wenn du zum Beispiel Binärwerte von
analogRead
sendest, können diese nur im Bereich von 0 bis 1.023 liegen, also muss das höchstwertige Byte kleiner als 4 sein (derint
Wert von 1.023 wird als die Bytes 3 und 255 gespeichert); daher wird es nie Daten mit zwei aufeinanderfolgenden Bytes größer als 3 geben. Das Senden von zwei Bytes mit dem Wert 4 (oder einem anderen Wert größer als 3) kann also keine gültigen Daten darstellen und kann dazu verwendet werden, den Anfang oder das Ende einer Nachricht anzuzeigen. - Durchflusskontrolle
-
Wähle entweder eine Übertragungsgeschwindigkeit, die sicherstellt, dass die empfangende Seite mit der sendenden Seite mithalten kann, oder verwende eine Art Flusskontrolle. Die Flusskontrolle ist ein Handshake, der der sendenden Seite mitteilt, dass der Empfänger bereit ist, weitere Daten zu empfangen.
Siehe auch
In Kapitel 2 findest du weitere Informationen zu den Variablentypen, die in Arduino-Sketches verwendet werden.
In Rezept 3.15 erfährst du mehr über den Umgang mit High und Low Bytes. Schau auch in den Arduino-Referenzen für lowByte
und highByte
.
Weitere Informationen zur Flusskontrolle findest du in der Wikipedia-Referenz.
4.7 Empfangen von Binärdaten vom Arduino auf einem Computer
Problem
Du möchtest in einer Programmiersprache wie Processing auf Binärdaten reagieren, die von Arduino gesendet werden. Du möchtest zum Beispiel auf Arduino-Nachrichten reagieren, die in Rezept 4.6 gesendet werden.
Lösung
Die Lösung für dieses Rezept hängt von der Programmierumgebung ab, die du auf deinem PC oder Mac verwendest. Wenn du noch keine bevorzugte Programmierumgebung hast und eine suchst, die leicht zu erlernen ist und gut mit Arduino funktioniert, ist Processing eine gute Wahl.
Hier sind die beiden Codezeilen für das Lesen eines Bytes aus dem Processing-Beispiel SimpleRead
(siehe Einleitung zu diesem Kapitel):
if
(
myPort
.
available
()
>
0
)
{
// If data is available,
val
=
myPort
.
read
();
// read it and store it in val
Wie du siehst, ist dieser Code dem Arduino-Code, den du in früheren Rezepten gesehen hast, sehr ähnlich.
Im Folgenden findest du einen Processing-Sketch, der die Größe eines Rechtecks proportional zu den Integer-Werten einstellt, die du vom Arduino-Sketch in Rezept 4.6 erhalten hast:
/*
* ReceiveBinaryData_P
*
* portIndex must be set to the port connected to the Arduino
*/
import
processing
.
serial
.
*
;
Serial
myPort
;
// Create object from Serial class
// WARNING!
// If necessary change the definition below to the correct port
short
portIndex
=
0
;
// select the com port, 0 is the first port
char
HEADER
=
'H'
;
int
value1
,
value2
;
// Data received from the serial port
void
setup
()
{
size
(
600
,
600
);
// Open whatever serial port is connected to Arduino.
String
portName
=
Serial
.
list
()[
portIndex
];
println
((
Object
[])
Serial
.
list
());
println
(
" Connecting to -> "
+
portName
);
myPort
=
new
Serial
(
this
,
portName
,
9600
);
}
void
draw
()
{
// read the header and two binary *(16-bit) integers:
if
(
myPort
.
available
()
>=
5
)
// If at least 5 bytes are available,
{
if
(
myPort
.
read
()
==
HEADER
)
// is this the header
{
value1
=
myPort
.
read
();
// read the least significant byte
value1
=
myPort
.
read
()
*
256
+
value1
;
// add the most significant byte
value2
=
myPort
.
read
();
// read the least significant byte
value2
=
myPort
.
read
()
*
256
+
value2
;
// add the most significant byte
println
(
"Message received: "
+
value1
+
","
+
value2
);
}
}
background
(
255
);
// Set background to white
fill
(
0
);
// set fill to black
// draw rectangle with coordinates based on the integers received from Arduino
rect
(
0
,
0
,
value1
,
value2
);
}
Tipp
Achte darauf, dass du portIndex
so einstellst, dass es der seriellen Schnittstelle entspricht, an die der Arduino angeschlossen ist. Möglicherweise musst du den Sketch einmal ausführen, einen Fehler erhalten und die Liste der seriellen Ports in der Processing-Konsole am unteren Rand des Bildschirms überprüfen, um festzustellen, welchen Wert du für portIndex
verwenden solltest.
Diskussion
Die Sprache Processing hat Arduino beeinflusst, und die beiden sind sich absichtlich ähnlich. Die Funktion setup
in Processing wird für die einmalige Initialisierung verwendet, genau wie bei Arduino. In Processing gibt es ein Anzeigefenster und setup
stellt dessen Größe mit dem Aufruf von size(600,600)
auf 600 × 600 Pixel ein.
Die Zeile String portName = Serial.list()[portIndex];
wählt die serielle Schnittstelle aus - in Processing sind alle verfügbaren seriellen Schnittstellen im Objekt Serial.list
enthalten, und dieses Beispiel verwendet den Wert einer Variablen namens portIndex
. println((Object[]) Serial.list())
gibt alle verfügbaren Schnittstellen aus, und die Zeile myPort = new Serial(this, portName, 9600);
öffnet die als portName
ausgewählte Schnittstelle. Achte darauf, dass du portIndex
auf die serielle Schnittstelle setzt, die mit deinem Arduino verbunden ist. Auf einem Mac ist der Arduino normalerweise der erste Anschluss; auf einem PC ist normalerweise der letzte Anschluss, wenn der PC einen physischen RS-232-Anschluss hat, andernfalls kann es der erste Anschluss sein. Du kannst dir auch die Liste der Ports in der Arduino IDE ansehen, die die seriellen Ports in der gleichen Reihenfolge anzeigt, in der Processing sie auflistet.
Die Funktion draw
in Processing funktioniert wie loop
in Arduino; sie wird wiederholt aufgerufen. Der Code in draw
prüft, ob Daten an der seriellen Schnittstelle verfügbar sind; wenn ja, werden Bytes gelesen und in den durch die Bytes dargestellten Ganzzahlwert umgewandelt. Basierend auf den empfangenen Integer-Werten wird ein Rechteck gezeichnet.
Siehe auch
Du kannst mehr über Processing auf der Processing-Website lesen.
4.8 Senden von Binärwerten von Processing an Arduino
Lösung
Verwende diesen Code:
// Processing Sketch
/* SendingBinaryToArduino
* Language: Processing
*/
import
processing
.
serial
.
*
;
Serial
myPort
;
// Create object from Serial class
// WARNING!
// If necessary change the definition below to the correct port
short
portIndex
=
0
;
// select the com port, 0 is the first port
public
static
final
char
HEADER
=
'H'
;
public
static
final
char
MOUSE_TAG
=
'M'
;
void
setup
()
{
size
(
512
,
512
);
String
portName
=
Serial
.
list
()[
portIndex
];
println
((
Object
[])
Serial
.
list
());
myPort
=
new
Serial
(
this
,
portName
,
9600
);
}
void
draw
(){
}
void
serialEvent
(
Serial
p
)
{
// handle incoming serial data
String
inString
=
myPort
.
readStringUntil
(
'\n'
);
if
(
inString
!=
null
)
{
(
inString
);
// print text string from Arduino
}
}
void
mousePressed
()
{
sendMessage
(
MOUSE_TAG
,
mouseX
,
mouseY
);
}
void
sendMessage
(
char
tag
,
int
x
,
int
y
){
// send the given index and value to the serial port
myPort
.
write
(
HEADER
);
myPort
.
write
(
tag
);
myPort
.
write
((
char
)(
x
/
256
));
// msb
myPort
.
write
(
x
&
0xff
);
//lsb
myPort
.
write
((
char
)(
y
/
256
));
// msb
myPort
.
write
(
y
&
0xff
);
//lsb
}
Tipp
Achte darauf, dass du portIndex
so einstellst, dass es der seriellen Schnittstelle entspricht, an die der Arduino angeschlossen ist. Möglicherweise musst du den Sketch einmal ausführen, einen Fehler erhalten und die Liste der seriellen Ports in der Processing-Konsole am unteren Rand des Bildschirms überprüfen, um festzustellen, welchen Wert du für portIndex
verwenden solltest.
Wenn die Maus im Verarbeitungsfenster angeklickt wird, wird sendMessage
mit dem 8-Bit-Tag aufgerufen, der anzeigt, dass es sich um eine Mausnachricht handelt, sowie mit den beiden 16-Bit-Mauskoordinaten x
und y
. Die Funktion sendMessage
sendet die 16-Bit-Werte x
und y
als zwei Bytes, wobei das höchstwertige Byte zuerst gesendet wird.
Hier ist der Arduino-Code, um diese Nachrichten zu empfangen und die Ergebnisse zurück an Processing zu senden:
// BinaryDataFromProcessing
// These defines must mirror the sending program:
const
char
HEADER
=
'H'
;
const
char
MOUSE_TAG
=
'M'
;
const
int
TOTAL_BYTES
=
6
;
// the total bytes in a message
void
setup
()
{
Serial
.
begin
(
9600
);
}
void
loop
(){
if
(
Serial
.
available
()
>=
TOTAL_BYTES
)
{
if
(
Serial
.
read
()
==
HEADER
)
{
char
tag
=
Serial
.
read
();
if
(
tag
==
MOUSE_TAG
)
{
int
x
=
Serial
.
read
()
*
256
;
x
=
x
+
Serial
.
read
();
int
y
=
Serial
.
read
()
*
256
;
y
=
y
+
Serial
.
read
();
Serial
.
println
(
"Got mouse msg:"
);
Serial
.
(
"x="
);
Serial
.
(
x
);
Serial
.
(
", y="
);
Serial
.
println
(
y
);
}
else
{
Serial
.
(
"Unknown tag: "
);
Serial
.
write
(
tag
);
Serial
.
println
();
}
}
}
}
Diskussion
Der Verarbeitungscode sendet ein Header-Byte, um anzuzeigen, dass eine gültige Nachricht folgt. Dies ist notwendig, damit sich der Arduino synchronisieren kann, wenn er mitten in einer Nachricht gestartet wird oder wenn die serielle Verbindung Daten verlieren kann, wie z. B. bei einer drahtlosen Verbindung. Das Tag bietet eine zusätzliche Überprüfung der Gültigkeit der Nachricht und ermöglicht es, alle anderen Nachrichtentypen, die du senden möchtest, individuell zu behandeln. In diesem Beispiel wird die Funktion mit drei Parametern aufgerufen: einem Tag und den 16-Bit-Mauskoordinaten x
und y
.
Der Arduino-Code prüft, ob mindestens TOTAL_BYTES
empfangen wurde, und stellt sicher, dass die Nachricht erst dann verarbeitet wird, wenn alle erforderlichen Daten vorhanden sind. Nachdem der Header und das Tag überprüft wurden, werden die 16-Bit-Werte als zwei Bytes gelesen, wobei das erste mit 256 multipliziert wird, um das höchstwertige Byte auf seinen ursprünglichen Wert zurückzusetzen.
Wenn du die serielle Ausgabe des Arduino an ein anderes Gerät, wie z. B. ein LCD serielles Zeichendisplay, senden möchtest, kannst du einen SoftwareSerial-Anschluss oder einen der zusätzlichen seriellen Anschlüsse deines Boards verwenden, wie in Rezept 4.11 gezeigt. Du müsstest die serielle Schnittstelle in setup
initialisieren und alle Serial.write
und Serial.print
/println
Anweisungen so ändern, dass sie diese serielle Schnittstelle verwenden. Die folgenden Änderungen würden zum Beispiel serielle Daten an den Serial1
TX Pin 1 des Arduino WiFi Rev 2, des Leonardo und der meisten ARM-basierten Arduino-Kompatiblen senden. Zuerst fügst du dies zu setup
hinzu:
Serial1
.
begin
(
9600
);
Und ändere den print
/println
/write
Code am Ende von loop
wie gezeigt:
Serial1
.
println
();
Serial1
.
println
(
"Got mouse msg:"
);
Serial1
.
(
"x="
);
Serial1
.
(
x
);
Serial1
.
(
", y="
);
Serial1
.
(
y
);
und:
Serial1
.
println
();
Serial1
.
(
"Unknown tag: "
);
Serial1
.
write
(
tag
);
Serial1
.
println
();
Warnung
Die sendende und die empfangende Seite müssen die gleiche Nachrichtengröße verwenden, damit binäre Nachrichten korrekt verarbeitet werden. Wenn du die Anzahl der zu sendenden Bytes erhöhen oder verringern möchtest, ändere TOTAL_BYTES
im Arduino-Code entsprechend.
4.9 Senden der Werte mehrerer Arduino-Pins
Lösung
Dieses Rezept sendet einen Header, gefolgt von einer Ganzzahl mit den Bitwerten der digitalen Pins 2 bis 13. Danach folgen sechs Ganzzahlen mit den Werten der Analogpins 0 bis 5. In Kapitel 5 findest du viele Rezepte, die Werte an den analogen und digitalen Pins setzen, die du zum Testen dieses Sketches verwenden kannst:
/*
* SendBinaryFields
* Sends digital and analog pin values as binary data
*/
const
char
HEADER
=
'H'
;
// a single character header to indicate
// the start of a message
void
setup
()
{
Serial
.
begin
(
9600
);
for
(
int
i
=
2
;
i
<=
13
;
i
++
)
{
pinMode
(
i
,
INPUT_PULLUP
);
// set pins 2 through 13 to inputs (with pullups)
}
}
void
loop
()
{
Serial
.
write
(
HEADER
);
// send the header
// put the bit values of the pins into an integer
int
values
=
0
;
int
bit
=
0
;
for
(
int
i
=
2
;
i
<=
13
;
i
++
)
{
bitWrite
(
values
,
bit
,
digitalRead
(
i
));
// set the bit to 0 or 1 depending
// on value of the given pin
bit
=
bit
+
1
;
// increment to the next bit
}
sendBinary
(
values
);
// send the integer
for
(
int
i
=
0
;
i
<
6
;
i
++
)
{
values
=
analogRead
(
i
);
sendBinary
(
values
);
// send the integer
}
delay
(
1000
);
//send every second
}
// function to send the given integer value to the serial port
void
sendBinary
(
int
value
)
{
// send the two bytes that comprise an integer
Serial
.
write
(
lowByte
(
value
));
// send the low byte
Serial
.
write
(
highByte
(
value
));
// send the high byte
}
Diskussion
Der Code sendet einen Header (das Zeichen H
), gefolgt von einer Ganzzahl mit den digitalen Pin-Werten, wobei die Funktion bitRead
verwendet wird, um ein einzelnes Bit in der Ganzzahl so zu setzen, dass es dem Wert des Pins entspricht (siehe Kapitel 3). Anschließend werden sechs Integerwerte gesendet, die die Werte der sechs analogen Anschlüsse enthalten (weitere Informationen in Kapitel 5 ). Alle Integer-Werte werden mit sendBinary
gesendet, das in Rezept 4.6 vorgestellt wurde. Die Nachricht ist 15 Bytes lang - ein Byte für den Header, zwei Bytes für die digitalen Pin-Werte und 12 Bytes für die sechs analogen Ganzzahlen. Der Code für die digitalen und analogen Eingänge wird in Kapitel 5 erklärt.
Angenommen, die analogen Pins haben die Werte 0 an Pin 0, 100 an Pin 1 und 200 an Pin 2 bis 500 an Pin 5, und die digitalen Pins 2 bis 7 sind high und 8 bis 13 sind low, dann ist dies der Dezimalwert jedes Bytes, das gesendet wird:
72
// the character 'H' - this is the header
// two bytes in low high order containing bits representing pins 2-13
63
// binary 00111111 : this indicates that pins 2-7 are high
0
// this indicates that 8-13 are low
// two bytes for each pin representing the analog value
0
// pin 0's analog value is 0 and this is sent as two bytes
0
100
// pin 1 has a value of 100, sent as a byte of 100 and a byte of 0
0
...
// pin 5 has a value of 500
244
// the remainder when dividing 500 by 256
1
// the number of times 500 can be divided by 256
Dieser Processing-Code liest die Nachricht und gibt die Werte auf der Processing-Konsole aus :
// Processing Sketch
/*
* ReceiveMultipleFieldsBinary_P
*
* portIndex must be set to the port connected to the Arduino
*/
import
processing
.
serial
.
*
;
Serial
myPort
;
// Create object from Serial class
short
portIndex
=
0
;
// select the com port, 0 is the first port
char
HEADER
=
'H'
;
void
setup
()
{
size
(
200
,
200
);
// Open whatever serial port is connected to Arduino.
String
portName
=
Serial
.
list
()[
portIndex
];
println
((
Object
[])
Serial
.
list
());
println
(
" Connecting to -> "
+
portName
);
myPort
=
new
Serial
(
this
,
portName
,
9600
);
}
void
draw
()
{
int
val
;
if
(
myPort
.
available
()
>=
15
)
// wait for the entire message to arrive
{
if
(
myPort
.
read
()
==
HEADER
)
// is this the header
{
println
(
"Message received:"
);
// header found
// get the integer containing the bit values
val
=
readArduinoInt
();
// print the value of each bit
for
(
int
pin
=
2
,
bit
=
1
;
pin
<=
13
;
pin
++
){
(
"digital pin "
+
pin
+
" = "
);
int
isSet
=
(
val
&
bit
);
if
(
isSet
==
0
)
{
println
(
"0"
);
}
else
{
println
(
"1"
);
}
bit
=
bit
*
2
;
//shift the bit to the next higher binary place
}
println
();
// print the six analog values
for
(
int
i
=
0
;
i
<
6
;
i
++
){
val
=
readArduinoInt
();
println
(
"analog port "
+
i
+
"="
+
val
);
}
println
(
"----"
);
}
}
}
// return integer value from bytes received from serial port (in low,high order)
int
readArduinoInt
()
{
int
val
;
// Data received from the serial port
val
=
myPort
.
read
();
// read the least significant byte
val
=
myPort
.
read
()
*
256
+
val
;
// add the most significant byte
return
val
;
}
Tipp
Achte darauf, dass du portIndex
so einstellst, dass es der seriellen Schnittstelle entspricht, an die der Arduino angeschlossen ist. Möglicherweise musst du den Sketch einmal ausführen, einen Fehler erhalten und die Liste der seriellen Ports in der Processing-Konsole am unteren Rand des Bildschirms überprüfen, um festzustellen, welchen Wert du für portIndex
verwenden solltest.
Der Verarbeitungscode wartet, bis 15 Zeichen ankommen. Wenn das erste Zeichen die Kopfzeile ist, ruft er die Funktion readArduinoInt
auf, um zwei Bytes zu lesen und sie in eine Ganzzahl umzuwandeln, indem er die komplementäre mathematische Operation durchführt, die vom Arduino durchgeführt wurde, um die einzelnen Bits zu erhalten, die die digitalen Pins darstellen. Die sechs Ganzzahlen stellen dann die analogen Werte dar. Beachte, dass die digitalen Pins alle standardmäßig auf 1 stehen (HIGH
). Das liegt daran, dass die Pull-ups an ihnen mit INPUT_PULLUP
aktiviert wurden. Das bedeutet, dass ein Wert von 1 bedeutet, dass die Taste nicht gedrückt ist, während 0 bedeutet, dass sie gedrückt ist, wenn eine Taste angeschlossen ist. In Rezept 2.4 wird dieser Modus erläutert.
Siehe auch
Um Arduino-Werte zurück an den Computer zu senden oder die Pins vom Computer aus anzusteuern (ohne Entscheidungen auf dem Board zu treffen), kannst du Firmata verwenden. Die Firmata-Bibliothek und Beispielskizzen (File→Examples→Firmata) sind in der Arduino-Software enthalten, und es gibt eine Bibliothek zur Verwendung in Processing. Du lädst den Firmata-Code auf den Arduino, legst fest, ob die Pins Eingänge oder Ausgänge des Computers sind, und setzt oder liest diese Pins dann.
4.10 Arduino-Daten in einer Datei auf deinem Computer protokollieren
Lösung
Wir haben das Senden von Informationen vom Arduino an deinen Computer in früheren Rezepten behandelt. Diese Lösung verwendet denselben Arduino-Code, der in Rezept 4.9 erklärt wird. Der Processing-Sketch, der die Dateiprotokollierung übernimmt, basiert auf dem ebenfalls in diesem Rezept beschriebenen Processing-Sketch.
Dieser Processing-Sketch erstellt eine Datei (mit dem aktuellen Datum und der Uhrzeit als Dateinamen) im selben Verzeichnis wie der Processing-Sketch. Die vom Arduino empfangenen Nachrichten werden der Datei hinzugefügt. Drücke eine beliebige Taste, um die Datei zu speichern und das Programm zu beenden:
/*
* ReceiveMultipleFieldsBinaryToFile_P
*
* portIndex must be set to the port connected to the Arduino
* based on ReceiveMultipleFieldsBinary, this version saves data to file
* Press any key to stop logging and save file
*/
import
processing
.
serial
.
*
;
import
java
.
util
.
*
;
import
java
.
text
.
*
;
PrintWriter
output
;
DateFormat
fnameFormat
=
new
SimpleDateFormat
(
"yyMMdd_HHmm"
);
DateFormat
timeFormat
=
new
SimpleDateFormat
(
"hh:mm:ss"
);
String
fileName
;
Serial
myPort
;
// Create object from Serial class
short
portIndex
=
0
;
// select the com port, 0 is the first port
char
HEADER
=
'H'
;
void
setup
()
{
size
(
200
,
200
);
// Open whatever serial port is connected to Arduino.
String
portName
=
Serial
.
list
()[
portIndex
];
println
((
Object
[])
Serial
.
list
());
println
(
" Connecting to -> "
+
portName
);
myPort
=
new
Serial
(
this
,
portName
,
9600
);
Date
now
=
new
Date
();
fileName
=
fnameFormat
.
format
(
now
);
output
=
createWriter
(
fileName
+
".txt"
);
// save the file in the sketch folder
}
void
draw
()
{
int
val
;
if
(
myPort
.
available
()
>=
15
)
// wait for the entire message to arrive
{
if
(
myPort
.
read
()
==
HEADER
)
// is this the header
{
String
timeString
=
timeFormat
.
format
(
new
Date
());
println
(
"Message received at "
+
timeString
);
output
.
println
(
timeString
);
// get the integer containing the bit values
val
=
readArduinoInt
();
// print the value of each bit
for
(
int
pin
=
2
,
bit
=
1
;
pin
<=
13
;
pin
++
){
(
"digital pin "
+
pin
+
" = "
);
output
.
(
"digital pin "
+
pin
+
" = "
);
int
isSet
=
(
val
&
bit
);
if
(
isSet
==
0
){
println
(
"0"
);
output
.
println
(
"0"
);
}
else
{
println
(
"1"
);
output
.
println
(
"1"
);
}
bit
=
bit
*
2
;
// shift the bit
}
// print the six analog values
for
(
int
i
=
0
;
i
<
6
;
i
++
){
val
=
readArduinoInt
();
println
(
"analog port "
+
i
+
"="
+
val
);
output
.
println
(
"analog port "
+
i
+
"="
+
val
);
}
println
(
"----"
);
output
.
println
(
"----"
);
}
}
}
void
keyPressed
()
{
output
.
flush
();
// Writes the remaining data to the file
output
.
close
();
// Finishes the file
exit
();
// Stops the program
}
// return the integer value from bytes received on the serial port
// (in low,high order)
int
readArduinoInt
()
{
int
val
;
// Data received from the serial port
val
=
myPort
.
read
();
// read the least significant byte
val
=
myPort
.
read
()
*
256
+
val
;
// add the most significant byte
return
val
;
}
Vergiss nicht, dass du portIndex
auf die serielle Schnittstelle einstellen musst, die mit dem Arduino verbunden ist. Wenn du einen falschen Wert für portIndex
wählst, überprüfe die anfängliche Ausgabe des Processing-Sketches, in der die Liste der verfügbaren seriellen Schnittstellen ausgegeben wird, und wähle die richtige aus.
Diskussion
Der Basisname für die Logdatei wird mit der Funktion DateFormat
in Processing gebildet:
DateFormat
fnameFormat
=
new
SimpleDateFormat
(
"yyMMdd_HHmm"
);
Der vollständige Dateiname wird mit einem Code erstellt, der ein Verzeichnis und eine Dateierweiterung hinzufügt:
output
=
createWriter
(
fileName
+
".txt"
);
Um die Datei zu erstellen und die Skizze zu beenden, kannst du eine beliebige Taste drücken, während das Hauptfenster der Verarbeitungsskizze aktiv ist. Drücke nicht die Escape-Taste, denn dadurch wird die Skizze beendet, ohne die Datei zu speichern. Die Datei wird in demselben Verzeichnis wie der Processing-Sketch erstellt (der Sketch muss mindestens einmal gespeichert werden, um sicherzustellen, dass das Verzeichnis existiert). Um dieses Verzeichnis zu finden, wähle Sketch→ShowSketch Folder in Processing.
createWriter
ist die Processing-Funktion, die die Datei öffnet; sie erzeugt ein Objekt (eine Einheit der Laufzeitfunktionalität) namens , das die eigentliche Dateiausgabe übernimmt. Der Text, der in die Datei geschrieben wird, ist derselbe wie der, der in output
Rezept 4.9 auf der Konsole ausgegeben wird, aber du kannst den Inhalt der Datei nach Bedarf formatieren, indem du die Standard-String-Verarbeitungsfunktionen von Processing nutzt. Die folgende Abwandlung der Routine erzeugt zum Beispiel eine kommagetrennte Datei , die von einer Tabellenkalkulation oder einer Datenbank gelesen werden kann. Der Rest der Processing-Skizze kann identisch sein, auch wenn du die Dateierweiterung von draw
.txt in .csv ändern solltest:
void
draw
()
{
int
val
;
if
(
myPort
.
available
()
>=
15
)
// wait for the entire message to arrive
{
if
(
myPort
.
read
()
==
HEADER
)
// is this the header
{
String
timeString
=
timeFormat
.
format
(
new
Date
());
output
.
(
timeString
);
val
=
readArduinoInt
();
// print the value of each bit
for
(
int
pin
=
2
,
bit
=
1
;
pin
<=
13
;
pin
++
){
int
isSet
=
(
val
&
bit
);
if
(
isSet
==
0
){
output
.
(
",0"
);
}
else
{
output
.
(
",1"
);
}
bit
=
bit
*
2
;
// shift the bit
}
// output the six analog values delimited by a comma
for
(
int
i
=
0
;
i
<
6
;
i
++
){
val
=
readArduinoInt
();
output
.
(
","
+
val
);
}
output
.
println
();
}
}
}
Siehe auch
Mehr über createWriter
erfährst du auf der Seite Processing. Processing enthält auch das ObjektTable
zum Erstellen, Bearbeiten und Speichern von CSV-Dateien.
4.11 Daten an mehr als ein serielles Gerät senden
Lösung
Bei einem Board mit mehr als einer seriellen Schnittstelle (siehe Einleitung für einige Vorschläge) ist das kein Problem. Zuerst musst du das Board mit dem seriellen Gerät verkabeln, wie in Abbildung 4-5 gezeigt. Als Nächstes kannst du zwei serielle Schnittstellen initialisieren und Serial
für die Verbindung zu deinem Computer und die andere (normalerweise Serial1
) für das Gerät verwenden:
void
setup
()
{
// initialize two serial ports on a board that supports this
Serial
.
begin
(
9600
);
// primary serial port
Serial1
.
begin
(
9600
);
// Some boards have even more serial ports
}
Warnung
Wenn du ein Arduino-Board oder ein Arduino-kompatibles Board verwendest, das mit 3,3 V arbeitet (z. B. ein SAMD-basiertes Board), kannst du gefahrlos an ein Gerät übertragen, das 5 V verwendet. Wenn du jedoch ein Board mit 5 V (z. B. ein Uno) mit einem Gerät verwendest, das 3,3 V verwendet, wirst du das Gerät beschädigen, wenn du keinen Spannungsteiler in die Schaltung einbaust, um die Spannung zu senken. In den Rezepten 4.13 und 5.11 findest du Beispiele für Spannungsteiler.
Auf einem ATmega328-basierten Arduino-Board (oder ähnlichem) wie dem Uno, das nur eine serielle Hardware-Schnittstelle hat, musst du mit der SoftwareSerial-Bibliothek eine emulierte oder "weiche" serielle Schnittstelle erstellen.
Wähle zwei verfügbare digitale Pins, jeweils einen für Senden und Empfangen, und schließe dein serielles Gerät daran an. Verbinde die Sendeleitung des Geräts mit dem Empfangsanschluss und die Empfangsleitung mit dem Sendeanschluss. Wenn du nur Daten sendest, z. B. wenn du Zeichen auf einem seriellen LCD-Display anzeigst, musst du nur den Sende-Pin (TX) mit dem Empfangs-Pin (RX) des Geräts verbinden, wie in Abbildung 4-6 gezeigt.
Erstelle in deinem Sketch ein SoftwareSerial
Objekt und teile ihm mit, welche Pins du für deine emulierte serielle Schnittstelle ausgewählt hast. In diesem Beispiel erstellen wir ein Objekt namens serial_lcd
und weisen es an, die Pins 2 und 3 zu verwenden. Auch wenn wir keine Daten von dieser seriellen Verbindung empfangen werden, müssen wir einen Empfangspin angeben, also solltest du Pin 2 nicht für etwas anderes verwenden, wenn du den SoftwareSerial-Port benutzt:
/*
* SoftwareSerialOutput sketch
* Output data to a software serial port
*/
#include <SoftwareSerial.h>
const
int
rxpin
=
2
;
// pin used to receive (not used in this version)
const
int
txpin
=
3
;
// pin used to send to LCD
SoftwareSerial
serial_lcd
(
rxpin
,
txpin
);
// new serial port on pins 2 and 3
void
setup
()
{
Serial
.
begin
(
9600
);
// 9600 baud for the built-in serial port
serial_lcd
.
begin
(
9600
);
//initialize the software serial port also for 9600
}
int
number
=
0
;
void
loop
()
{
serial_lcd
.
(
"Number: "
);
// send text to the LCD
serial_lcd
.
println
(
number
);
// print the number on the LCD
Serial
.
(
"Number: "
);
Serial
.
println
(
number
);
// print the number on the PC console
delay
(
500
);
// delay half second between numbers
number
++
;
// to the next number
}
Um den Sketch mit einer eingebauten seriellen Schnittstelle zu verwenden, verbinde die Pins wie in Abbildung 4-5 gezeigt und entferne dann diese Leitungen:
#include <SoftwareSerial.h>
const
int
rxpin
=
2
;
const
int
txpin
=
3
;
SoftwareSerial
serial_lcd
(
rxpin
,
txpin
);
Zum Schluss fügst du diese Zeile an ihrer Stelle ein: #define serial_gps Serial1
(ändere Serial1
nach Bedarf, wenn du einen anderen Port verwendest).
Hinweis
Einige der Boards, die mehrere serielle Hardware-Ports unterstützen, wie z. B. Leonardo, Mega und Mega 2560, haben Einschränkungen, welche Pins du für den SoftwareSerial-Empfang (RX) verwenden kannst. Auch wenn wir die Empfangsfunktion hier nicht verwenden und auch wenn du auf diesen Boards höchstwahrscheinlich die seriellen Hardware-Pins für Serial1
verwenden würdest (siehe Tabelle 4-1), solltest du wissen, dass diese Boards die RX-Funktion an Pin 2 nicht unterstützen. In Rezept 4.12 werden RX-Pins verwendet, die auf einer Vielzahl von Boards funktionieren.
In diesem Sketch wird davon ausgegangen, dass ein serielles LCD an Pin 3 angeschlossen ist, wie in Abbildung 4-6 gezeigt, und dass eine serielle Konsole mit dem eingebauten Port verbunden ist. In der Schleife wird immer wieder dieselbe Nachricht angezeigt:
Number
:
0
Number
:
1
...
Diskussion
Der Arduino-Mikrocontroller verfügt über mindestens einen eingebauten seriellen Hardware-Anschluss. Beim Arduino Uno ist dieser Anschluss mit der seriellen USB-Verbindung verbunden und außerdem mit den Pins 0 (Empfangen) und 1 (Senden) verdrahtet, sodass du ein Gerät wie eine serielle LCD-Anzeige an den Arduino anschließen kannst. Die Zeichen, die du über das Objekt Serial
überträgst, werden auf dem LCD-Display angezeigt.
Hinweis
Obwohl du eine separate Stromversorgung für das serielle Gerät verwenden kannst, musst du den Masse-Pin des Arduino mit dem Pin des Geräts verbinden, damit der Arduino und das serielle Gerät eine gemeinsame Masse haben. In der Lösung haben wir das getan, aber wir haben auch den 5-V-Ausgang des Arduino für die Stromversorgung des Geräts verwendet.
Zusätzlich zur seriellen USB-Verbindung unterstützen einige Boards eine oder mehrere direkte serielle Verbindungen. Bei diesen Boards sind die Pins 0 und 1 in der Regel mit dem Objekt Serial1
verbunden, so dass du eine serielle USB-Verbindung zu deinem Computer aufrechterhalten kannst, während du über die Pins 0 und 1 Daten mit dem Gerät austauschst. Einige Boards unterstützen zusätzliche serielle Schnittstellen an einer anderen Anzahl von Pins (siehe Tabelle 4-1 für eine Tabelle der auf verschiedenen Boards verfügbaren seriellen Schnittstellen). Alle Pins, die die serielle Ein- und Ausgabe unterstützen, sind nicht nur Allzweck-Digitalpins, sondern werden auch von der universellen asynchronen Empfänger-Sender-Hardware (UART) unterstützt, die in den Chip eingebaut ist. Diese spezielle Hardware ist dafür verantwortlich, eine Reihe von genau getakteten Impulsen zu erzeugen, die das Partnergerät als Daten erkennt, und den entsprechenden Datenstrom zu interpretieren, den es im Gegenzug erhält.
Obwohl ARM SAMD-basierte Boards (M0- und M4-Boards) über zwei hardwareunterstützte serielle Schnittstellen verfügen und der Mega vier solcher Schnittstellen hat, haben der Arduino Uno und die meisten ähnlichen Boards auf Basis des ATmega328 nur eine. Für den Uno und ähnliche Boards brauchst du eine Softwarebibliothek, die die zusätzlichen Anschlüsse für Projekte emuliert, die Verbindungen zu zwei oder mehr seriellen Geräten benötigen. Eine "Software-Serial"-Bibliothek verwandelt ein beliebiges Paar digitaler I/O-Pins in eine neue serielle Schnittstelle.
Um deine serielle Softwareschnittstelle zu erstellen, wählst du zwei Pins aus, die als Sende- und Empfangsleitungen der Schnittstelle fungieren, ähnlich wie bei einer seriellen Hardwareschnittstelle die zugewiesenen Pins. In Abbildung 4-6 sind die Pins 3 und 2 dargestellt, aber es können alle verfügbaren digitalen Pins verwendet werden, mit einigen Ausnahmen für bestimmte Boards. Es ist ratsam, die Pins 0 und 1 nicht zu verwenden, da diese bereits von der eingebauten Schnittstelle angesteuert werden.
Die Syntax für das Schreiben an den Soft-Port ist identisch mit der für den Hardware-Port. Im Beispielsketch werden die Daten mit print()
und println()
sowohl an den "echten" als auch an den emulierten Port gesendet:
serial_lcd
.
(
"Number: "
);
// send text to the LCD
serial_lcd
.
println
(
number
);
// print the number on the LCD
Serial
.
(
"Number: "
);
Serial
.
println
(
number
);
// print the number on the PC console
Tipp
Wenn der kombinierte Text ("Number: "
) und die Zahl selbst länger sind als die Breite deiner LCD-Serienanzeige, wird die Ausgabe möglicherweise abgeschnitten oder läuft über das Display. Viele LCD-Zeichenanzeigen haben zwei Reihen mit je 20 Zeichen.
Siehe auch
Nick Gammon pflegt eine reine Sendeversion von SoftwareSerial, mit der du vermeiden kannst, dass du einen Pin für den Datenempfang zuweisen musst, wenn du ihn nicht brauchst.
4.12 Empfang von seriellen Daten von mehr als einem seriellen Gerät
Lösung
Auf einem Board mit mehr als einer seriellen Schnittstelle (siehe Einleitung für einige Vorschläge) ist das kein Problem. Zuerst musst du das Board mit dem seriellen Gerät verkabeln, wie in Abbildung 4-7 gezeigt. Als Nächstes kannst du zwei serielle Schnittstellen initialisieren und Serial
für die Verbindung zu deinem Computer und die andere (normalerweise Serial1
) für das Gerät verwenden:
void
setup
()
{
// initialize two serial ports on a board that supports this
Serial
.
begin
(
9600
);
// primary serial port
Serial1
.
begin
(
9600
);
// Some boards have even more serial ports
}
Warnung
Wenn du ein Arduino-Board oder ein Arduino-kompatibles Board verwendest, das mit 5 V arbeitet (z. B. ein Uno), kannst du gefahrlos Daten von einem Gerät empfangen, das 3,3 V verwendet. Wenn du jedoch ein Board mit 3,3 V (die meisten ARM-basierten Boards) mit einem Gerät verwendest, das 5 V Logikpegel verwendet, wirst du dein Board beschädigen, wenn du keinen Spannungsteiler in die Schaltung einbaust, um die Spannung zu senken. In den Rezepten 4.13 und 5.11 findest du Beispiele für Spannungsteiler.
Beim Arduino Uno und anderen Boards, die auf dem ATmega328 basieren, der nur eine serielle Hardware-Schnittstelle hat, musst du mit der SoftwareSerial-Bibliothek eine emulierte oder "weiche" serielle Schnittstelle erstellen. Dabei sind die Übertragungsgeschwindigkeiten langsamer als bei einer eingebauten seriellen Schnittstelle.
Dieses Problem ähnelt dem in Rezept 4.11, und die Lösung ist in der Tat ähnlich. Wenn die serielle Schnittstelle deines Arduinos mit der Konsole verbunden ist und du ein zweites serielles Gerät anschließen möchtest, musst du mit einer seriellen Softwarebibliothek eine emulierte Schnittstelle erstellen. In diesem Fall werden wir Daten von der emulierten Schnittstelle empfangen, anstatt auf sie zu schreiben, aber die grundlegende Lösung ist sehr ähnlich.
Wähle zwei Pins aus, die du als Sende- und Empfangsleitungen verwenden willst. Bei dieser Lösung werden die Pins 8 und 9 verwendet, weil einige Boards (wie der Arduino Leonardo) nur SoftwareSerial-Empfang an den Pins 8, 9, 10, 11 und 14 unterstützen. Dies sind auch die Pins, die der Arduino Mega und Mega 2560 für den SoftwareSerial-Empfang unterstützen. In der Praxis würdest du wahrscheinlich die Hardware-Serial an den Pins 0 und 1 (Serial1
) auf diesen Boards verwenden (die Mega-Boards haben Pins, die Hardware-Serial unterstützen, wie Serial2
und Serial3
). Wir haben jedoch SoftwareSerial-Pins gewählt, die auf möglichst vielen Boards funktionieren, falls du diesen Code auf einem dieser Boards testen möchtest.
Verbinde dein GPS wie in Abbildung 4-8 dargestellt.
Wie in Rezept 4.11 erstellst du ein SoftwareSerial
Objekt in deinem Sketch und sagst ihm, welche Pins es steuern soll. Im folgenden Beispiel definieren wir eine serielle Schnittstelle namens serial_gps
und verwenden die Pins 8 und 9 für den Empfang bzw. das Senden. Auch wenn wir keine Daten an dieses serielle Gerät senden werden, müssen wir einen Sende-Pin angeben, also solltest du Pin 9 für nichts anderes verwenden, wenn du die serielle Software-Schnittstelle benutzt:
Tipp
Um den folgenden Code mit einer eingebauten seriellen Hardware-Schnittstelle zu verwenden, verbinde die Pins wie in Abbildung 4-7 gezeigt und entferne dann diese Leitungen:
#include <SoftwareSerial.h>
const
int
rxpin
=
8
;
const
int
txpin
=
9
;
SoftwareSerial
serial_gps
(
rxpin
,
txpin
);
Zum Schluss fügst du diese Zeile an ihrer Stelle ein (ändere Serial1
, wenn du einen anderen Port verwendest):
#define serial_gps Serial1
/*
* SoftwareSerialInput sketch
* Read data from a software serial port
*/
#include <SoftwareSerial.h>
const
int
rxpin
=
8
;
// pin used to receive from GPS
const
int
txpin
=
9
;
// pin used to send to GPS
SoftwareSerial
serial_gps
(
rxpin
,
txpin
);
// new serial port on these pins
void
setup
()
{
Serial
.
begin
(
9600
);
// 9600 baud for the built-in serial port
serial_gps
.
begin
(
9600
);
// initialize the port, most GPS devices
// use 9600 bits per second
}
void
loop
()
{
if
(
serial_gps
.
available
()
>
0
)
// any character arrived yet?
{
char
c
=
serial_gps
.
read
();
// if so, read it from the GPS
Serial
.
write
(
c
);
// and echo it to the serial console
}
}
Dieser kurze Sketch leitet einfach alle eingehenden Daten vom GPS an den Arduino Serial Monitor weiter. Wenn das GPS funktioniert und deine Verkabelung korrekt ist, solltest du die GPS-Daten auf dem seriellen Monitor sehen.
Diskussion
Du initialisierst eine emulierte SoftwareSerial-Schnittstelle, indem du Pin-Nummern für Senden und Empfangen angibst. Der folgende Code richtet die Schnittstelle so ein, dass sie auf Pin 8 empfängt und auf Pin 9 sendet:
const
int
rxpin
=
8
;
// pin used to receive from GPS
const
int
txpin
=
9
;
// pin used to send to GPS
SoftwareSerial
serial_gps
(
rxpin
,
txpin
);
// new serial port on these pins
Die Syntax für das Lesen eines emulierten Ports ist sehr ähnlich wie die für das Lesen eines eingebauten Ports. Prüfe zuerst mit available()
, ob ein Zeichen vom GPS angekommen ist, und lese es dann mit read()
.
Es ist wichtig, sich daran zu erinnern, dass serielle Software-Schnittstellen Zeit und Ressourcen verbrauchen. Eine emulierte serielle Schnittstelle muss alles tun, was eine Hardware-Schnittstelle tut, und dabei denselben Prozessor verwenden, mit dem dein Sketch "echte Arbeit" zu leisten versucht. Jedes Mal, wenn ein neues Zeichen eintrifft, muss der Prozessor seine Arbeit unterbrechen, um es zu verarbeiten. Das kann sehr zeitaufwändig sein. Bei 4.800 Baud braucht der Arduino zum Beispiel etwa 2 ms, um ein einziges Zeichen zu verarbeiten. Auch wenn 2 ms nicht viel klingen, bedenke, dass dein angeschlossenes Gerät 200 bis 250 Zeichen pro Sekunde überträgt und dein Sketch 40 bis 50 % seiner Zeit damit verbringt, mit der seriellen Eingabe Schritt zu halten. Damit bleibt nur sehr wenig Zeit, um all diese Daten zu verarbeiten. Wenn du zwei serielle Geräte hast, solltest du, wenn möglich, das Gerät mit dem höheren Bandbreitenbedarf an den eingebauten (Hardware-)Anschluss anschließen. Wenn du ein Gerät mit hoher Bandbreite an eine serielle Softwareschnittstelle anschließen musst, achte darauf, dass der Rest der Schleife deines Sketches sehr effizient ist.
Empfangen von Daten von mehreren SoftwareSerial-Ports
Mit der SoftwareSerial-Bibliothek, die im Arduino enthalten ist, ist es möglich, mehrere "weiche" serielle Schnittstellen im selben Sketch zu erstellen. Das ist eine nützliche Methode, um z. B. mehrere XBee-Funkgeräte (siehe Rezept 14.2) oder serielle Displays im selben Projekt zu steuern. Der Nachteil ist, dass zu jedem Zeitpunkt nur eine dieser Schnittstellen aktiv Daten empfangen kann. Eine zuverlässige Kommunikation über eine Software-Schnittstelle erfordert die ungeteilte Aufmerksamkeit des Prozessors. Deshalb kann SoftwareSerial immer nur mit einer Schnittstelle aktiv kommunizieren.
Es ist möglich, im selben Sketch an zwei verschiedenen SoftwareSerial-Ports zu empfangen. Du musst nur darauf achten, dass du nicht versuchst, von beiden gleichzeitig zu empfangen. Es gibt viele erfolgreiche Entwürfe, die z. B. eine Zeit lang ein serielles GPS-Gerät überwachen und dann später Eingaben von einem XBee annehmen. Der Schlüssel dazu ist, langsam zwischen den beiden Geräten zu wechseln und erst dann auf das zweite Gerät umzuschalten, wenn die Übertragung vom ersten abgeschlossen ist.
In der folgenden Skizze ist zum Beispiel ein XBee-Modul mit dem Arduino verbunden. Das Modul empfängt Befehle von einem entfernten Gerät, das an ein anderes XBee-Modul angeschlossen ist. Der Sketch hört den Befehlsstrom über den "xbee"-Port ab, bis er das Signal erhält, Daten von einem GPS-Modul zu sammeln, das an einem zweiten SoftwareSerial-Port angeschlossen ist. Der Sketch überwacht dann das GPS für 10 Sekunden - hoffentlich lange genug, um einen "Fix" zu machen - bevor er zum XBee zurückkehrt.
In einem System mit mehreren "Soft"-Ports empfängt nur einer aktiv Daten. Standardmäßig ist der "aktive" Anschluss derjenige, für den begin()
zuletzt aufgerufen wurde. Du kannst jedoch ändern, welcher Port aktiv ist, indem du die Methode listen()
aufrufst. listen()
weist das SoftwareSerial-System an, den Datenempfang an einem Port zu beenden und auf einem anderen Port auf Daten zu warten.
Hinweis
Da das folgende Beispiel nur Daten empfängt, kannst du beliebige Pins für txpin1
und txpin2
wählen. Wenn du die Pins 9 und 11 für etwas anderes verwenden möchtest, kannst du txpin1
/2
auf einen anderen Pin ändern. Wir raten davon ab, sie auf eine nicht existierende Pin-Nummer zu ändern, da dies zu einem ungewöhnlichen Verhalten führen könnte.
Das folgende Codefragment veranschaulicht, wie du einen Sketch gestalten könntest, um zuerst von einem Port und dann von einem anderen zu lesen:
/*
* MultiRX sketch
* Receive data from two software serial ports
*/
#include <SoftwareSerial.h>
const
int
rxpin1
=
8
;
const
int
txpin1
=
9
;
const
int
rxpin2
=
10
;
const
int
txpin2
=
11
;
SoftwareSerial
gps
(
rxpin1
,
txpin1
);
// gps TX pin connected to Arduino pin 9
SoftwareSerial
xbee
(
rxpin2
,
txpin2
);
// xbee TX pin connected to Arduino pin 10
void
setup
()
{
Serial
.
begin
(
9600
);
xbee
.
begin
(
9600
);
gps
.
begin
(
9600
);
xbee
.
listen
();
// Set “xbee” to be the active device
}
void
loop
()
{
if
(
xbee
.
available
()
>
0
)
// xbee is active. Any characters available?
{
if
(
xbee
.
read
()
==
'y'
)
// if xbee received a 'y' character?
{
gps
.
listen
();
// now start listening to the gps device
unsigned
long
start
=
millis
();
// begin listening to the GPS
while
(
start
+
100000
>
millis
())
// listen for 10 seconds
{
if
(
gps
.
available
()
>
0
)
// now gps device is active
{
char
c
=
gps
.
read
();
Serial
.
write
(
c
);
// echo it to the serial console
}
}
xbee
.
listen
();
// After 10 seconds, go back to listening to the xbee
}
}
}
Dieser Sketch ist so konzipiert, dass das XBee-Funkgerät als aktiver Port behandelt wird, bis es ein y
Zeichen empfängt, woraufhin das GPS zum aktiven Abhörgerät wird. Nachdem die GPS-Daten 10 Sekunden lang verarbeitet wurden, nimmt der Sketch das Lauschen am XBee-Port wieder auf. Daten, die an einem inaktiven Port ankommen, werden einfach verworfen.
Beachte, dass die Einschränkung "aktiver Port" nur für mehrere Softports gilt. Wenn dein Design wirklich Daten von mehr als einem seriellen Gerät gleichzeitig empfangen muss, solltest du ein Board wie das Teensy verwenden, das mehrere serielle Schnittstellen unterstützt (das Teensy 4.0 unterstützt insgesamt sieben). Tabelle 4-1 zeigt die Pins, die auf verschiedenen Arduino- und Arduino-kompatiblen Boards für serielle Schnittstellen verwendet werden.
Siehe auch
Wenn du mit einem GPS weitergehen und die empfangenen Nachrichten parsen möchtest, siehe Rezept 6.14. Eine Alternative zu SoftwareSerial, die mehrere Geräte besser unterstützen kann, ist die AltSoftSerial-Bibliothek.
4.13 Verwendung von Arduino mit dem Raspberry Pi
Lösung
Der Arduino kann serielle Befehle vom Raspberry Pi überwachen und darauf reagieren. Der Code hier steuert die Arduino-LEDs über Python-Skripte, die auf dem Pi laufen.
Hinweis
Es ist auch möglich, Arduino mit einem Raspberry Pi zu verbinden, indem du einen der USB-Anschlüsse des Raspberry Pi benutzt. Du kannst sogar die Arduino IDE auf dem Raspberry Pi ausführen. Lade eine der ARM-Versionen herunter. Zum Zeitpunkt der Erstellung dieses Artikels lief das Raspberry Pi-Betriebssystem Raspbian im 32-Bit-Modus. Du solltest also die 32-Bit-Version wählen, es sei denn, du verwendest ein 64-Bit-Betriebssystem.
Verbinde den seriellen Empfangspin des Arduino (Pin 0 mit der Bezeichnung RX auf der Platine) mit Pin 8 an der Stiftleiste des Pi. Verbinde den TX-Pin 1 des Arduino mit dem GPIO-Pin 10 des Pi. Der Erdungsstift (GND) des Arduino wird mit einem der Erdungsstifte des Pi verbunden (Pin 14 wird in Abbildung 4-9 verwendet).
Hinweis
Das Folgende gilt für den Arduino Uno und alle Arduino-kompatiblen Boards, die eine einzige serielle Schnittstelle haben, die von der seriellen USB-Verbindung und den RX/TX-Pins gemeinsam genutzt wird. Wenn du ein Board mit einem zusätzlichen seriellen Hardwareanschluss verwendest, wie z. B. das Leonardo, WiFi Rev2, Nano Every oder ein ARM-basiertes Board, ändere #define mySerial Serial
in #define mySerial Serial1
und wenn dein Board nicht die Pins 0 und 1 für RX und TX verwendet, verwende die entsprechenden Pins für Serial1
(siehe Tabelle 4-1).
Hier ist ein Arduino-Sketch, der serielle Nachrichten vom Pi überwacht. Lade ihn auf das Arduino-Board hoch:
/*
* ArduinoPi sketch
* Pi control Arduino pins using serial messages
* format is: Pn=state
* where 'P' is header character, n is pin number, state is 0 or 1
* example: P13=1 turns on pin 13
*/
// Replace Serial with Serial1 on boards with an additional serial port
#define mySerial Serial
void
setup
()
{
mySerial
.
begin
(
9600
);
// Initialize serial port to send/receive at 9600 baud
}
void
loop
()
{
if
(
mySerial
.
available
())
// Check whether at least one character is available
{
char
ch
=
mySerial
.
read
();
int
pin
=
-
1
;
if
(
ch
==
'P'
)
// is this the beginning of a message to set a pin?
{
pin
=
mySerial
.
parseInt
();
// get the pin number
}
else
if
(
ch
==
'B'
)
// Message to set LED_BUILTIN
{
pin
=
LED_BUILTIN
;
}
if
(
pin
>
1
)
{
// 0 and 1 are usually serial pins (leave them alone)
int
state
=
mySerial
.
parseInt
();
// 0 is off, 1 is on
pinMode
(
pin
,
OUTPUT
);
digitalWrite
(
pin
,
state
);
}
}
}
Speichere das folgende Python-Skript als blinkArduino.py auf dem Pi und führe es mit python blinkArduino.py
aus. Das Skript lässt die LED auf dem Arduino-Board blinken. Bevor du es ausführst, musst du die python-serial Bibliothek installiert haben. Du kannst sie mit sudo apt-get install python-serial
auf dem Raspberry Pi installieren:
#!/usr/bin/env python
import
serial
from
time
import
sleep
ser
=
serial
.
Serial
(
'/dev/serial0'
,
9600
)
ser
.
write
(
'P13=1'
)
sleep
(
1
)
ser
.
write
(
'P13=0'
)
Wenn das Skript ausgeführt wird, sollte eine LED an Pin 13 für eine Sekunde aufleuchten und dann erlöschen.
Diskussion
Der Arduino-Sketch erkennt den Beginn einer Nachricht, wenn das Zeichen P empfangen wird. Die Funktion Arduino parseInt
wird verwendet, um die gewünschte Pin-Nummer und den Pin-Status zu ermitteln. Wenn du P13=1 sendest, schaltet sich die LED an Pin 13 ein. P13=0 schaltet die LED aus. Weitere Informationen zu seriellen Nachrichten und parseInt
findest du in Kapitel 4. Viele Arduino- und Arduino-kompatible Boards verwenden einen anderen Pin als 13. Um dir das Nachschlagen zu ersparen, verwendet die Arduino-Skizze die Konstante LED_BUILTIN
als Pin-Nummer, wenn du ihr eine Nachricht wie B=1 sendest (keine Pin-Nummer erforderlich).
Das Python-Skript sendet die entsprechenden Nachrichten, um die LED ein- und wieder auszuschalten.
Wenn die eingebaute LED deines Boards nicht an Pin 13 ist, verwende diese Version, die den Befehl B
verwendet, um die LED an LED_BUILTIN
umzuschalten:
#!/usr/bin/env python
import
serial
from
time
import
sleep
ser
=
serial
.
Serial
(
'/dev/serial0'
,
9600
)
ser
.
write
(
'B=1'
)
sleep
(
1
)
ser
.
write
(
'B=0'
)
Die Pins des Raspberry Pi sind nicht 5-Volt-tolerant. Wenn du also ein 5-Volt-kompatibles Arduino-Board an den Pi anschließt, musst du den im Diagramm gezeigten Spannungsteiler verwenden. Wenn du einen 3,3-V-Arduino verwendest, kannst du den Spannungsteiler in der Regel weglassen, aber der Spannungsteiler schadet ihm nicht. In Rezept 5.11 findest du weitere Informationen zu Spannungsteilern.
Die Nachrichten, die in diesem Rezept gesendet werden, sind sehr einfach, können aber so erweitert werden, dass der Pi fast jede Arduino-Funktion steuern kann und der Arduino Informationen an den Pi zurücksenden kann. In Rezept 4.0 erfährst du mehr darüber, wie du Arduino über die serielle Schnittstelle mit einem Computer verbinden kannst.
Details zu Python und dem Pi findest du online und in Büchern wie dem Raspberry Pi Cookbook, Third Edition, von Simon Monk.
Get Arduino Kochbuch, 3. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.