3.6 UART / RS232 (🚧)
Synopsis: [🔗 de.wikipedia.org/wiki/Universal_Asynchronous_Receiver_Transmitter] [🔗 https://reference.arduino.cc/reference/en/language/functions/communication/serial/]
Nochmal anschauen: 2.6 Schieberegister
Ist die älteste und schwierigste einfache Serielle Schnittstelle. Die Datenübertragung erfolgt asynchron (eine Baud-Rate muss eingestellt werden) und hat Start- und Stopp-Bits ausserdem kann ein Paritätsbit übertragen werden.
Es ist die elementare serielle Schnittstelle für Arduino, darüber werden viele der Boards programmiert und darüber wird mit dem Seriellen Monitor kommuniziert.
Datenübertragung analysieren
Nun wollte ich mit dem Oszilloskop die Kommunikation mitschneiden und steckte den Tastkopf in D1/TX auf dem L152RE-Board und fand nur Rauschen.
In der Doku fand ich raus, dass die Pins D1/PA2/TX und D0/PA3/RX einfach abgeklemmt sind und sich erst mit Lötbrücken wieder anklemmen lassen: [Doku UM1724] Seite 25 6.8 USART Communication. Auf der ST-LINK-Platine können die Signale aber abgegriffen werden, zur Kommunikation wird USART2 verwendet.
void setup() {
Serial.begin(9600); // USART2 PA2, PA3 mit ST-Link verbunden
}
void loop() {
Serial.println("ab");
delay(1000);
}
Übertragung mit Logic-Analyzer beobachten



Aufgaben
- Zeichnen Sie ein Blockdiagramm mit 2 Kommunikationsteilnehmern die mittels UARTs verbunden sind
- Erklären Sie die serielle Kommunikation
- Parity
- Ergänzen Sie jeweils zu gerader Parity (even)
- 01010101___
- 11001000___
- 00000001___
- Ergänzen Sie jeweils zu Ungerader Parity (odd)
- 01101010___
- 11100011___
- 00000000___
- Richtig oder fehlerhaft
- even 101011011
- odd 01011101
- even 111001111
- Erläutern sie, wie die Erkennung von Fehlern bei der Datenübertragung funktioniert
- Ergänzen Sie jeweils zu gerader Parity (even)
- Wie ist mit UARTs eine Vollduplex-Datenübertragung möglich?
- Wie kann bei der Datenübertragung mit UART auf eine Taktleitung verzichtet werden?
- Was versteht man unter einer asynchronen Datenübertragung?
- Welche Bytes wurden oben übertragen, verwenden Sie die ASCII-Tabelle
Lösung
- Siehe Forsa S. 17
- Die Datenbits werden seriell, d.h. nacheinander übertragen. Damit das funktioniert müssen Sender und Empfänger mit der gleichen Geschwindigkeit arbeiten.
- Parity
- Ergänzen Sie jeweils zu gerader Parity (even)
- 01010101__0_
- 11001000__1_
- 00000001__1_
- Ergänzen Sie jeweils zu Ungerader Parity (odd)
- 01101010__1_
- 11100011__0_
- 00000000__1_
- Richtig oder fehler
- even 101011011 ✅
- odd 01011101 ✅
- even 111001111 ❌
- Wenn bei der Übertragung ein Bit umkippt kann dies durch die Parität erkannt werden.
- Ergänzen Sie jeweils zu gerader Parity (even)
- Für Senden und Empfang ist jeweils eine Leitung vorhanden.
- Sender und Empfänger haben einen Taktgenerator, durch das Startbit kann der Empfänger sich auf den Sender synchronisieren.
- Sender und Empfänger arbeiten nicht mit dem selben Takt sondern der Empfänger hat eine eigene Taktbasis.
- ‚a‘,’b‘,CR (Carriage Return) und LF (Line Feet)
Vom Seriellen-Monitor Zeichen einlesen
Synopsis: [🔗 reference.arduino.cc/reference/en/language/functions/communication/serial/available/] [🔗 reference.arduino.cc/reference/en/language/functions/communication/stream/]
Die serielle Schnittstelle, mit der der µC programmiert wird kann auch zum Übertragen von Daten, Zeichen vom und zum µC verwendet werden. Dazu muss in setup() die Schnittstelle mit der gewünschten Baud-Rate (Übertragungsgeschwindigkeit) initialisiert werden.
Beim Empfangen von Zeichen (Bytes) werden in einen Puffer (64 Bytes) gespeichert. Die Operation Serial.available() gibt die Anzahl der empfangen Bytes zurück.
Mit der Operation Serial.read() kann ein Zeichen aus dem Puffer gelesen werden, das Zeichen wird dabei aus dem Puffer entfernt und die Anzahl der Zeichen im Puffer um 1 erniedrigt. Beispielcode:
Serial.available() gibt Anzahl der Bytes im Empfangspuffer zurück.
Serial.read() gibt ein Zeichen aus dem Empfangspuffer zurück und erniedrigt die Anzahl um eins.
void setup(){
Serial.begin(9600); // Serielle Schnittstelle mit 9600 Baud starten
}
void loop(){
char zeichen;
if(Serial.available()){ // wenn Zeichen gesendet wurden
zeichen = Serial.read(); // lese Zeichen
Serial.printf("Zeichen: %2c Hex: %#x\n",zeichen,zeichen); // gib Zeichen und Code aus
}
}
Die eingegebenen Zeichen werden aus der Eingabezeile des seriellen Monitors erst nach einem „Return“ zum µC gesendet, es kann eingestellt werden, ob am Ende noch weitere Bytes, Symbole gesendet werden sollen:
- Kein Zeilenende, es wird kein weiters Byte mehr gesendet.
- Neue Zeile, es wird das Symbol New Line, (NL, „\n“, 0xa, bzw. Line Feed, LF) gesendet, das eine neue Zeile bewirken soll, denke an eine Schreibmaschine bei der in eine neue Zeile gegangen wird. Ist der Normalfall.
- Zeilenumbruch, es wird das Symbol Carriage Return (CR, 0xd) gesendet, das einen Wagenrücklauf bewirken soll, also wieder zum Anfang der Zeile.
- Sowohl NL als auch CR, es werden beide Symbole gesendet.
Mehrere Zeichen in einen String einlesen
Es gibt auch Operationen, mit denen nicht nur einzelne Zeichen sondern das Empfangene auch gleich z.B. in einen String eingelesen wird.
void setup(){
Serial.begin(9600); // Serielle Schnittstelle mit 9600 Baud starten
}
void loop(){
String s;
if(Serial.available()){ // wenn Zeichen gesendet wurden
s = Serial.readString(); // lese Daten in String ein
Serial.print(s); // gib String aus
}
}
Beim Testen des Codes fällt auf, dass es 1 Sekunde dauert bis im Seriellen Monitor die Eingabe erscheint. Die readString()-Operation hat erst fertig wenn nach einem Timeout von 1 Sekunde kein weiteres Zeichen mehr empfangen wird, siehe [Stream.readString() 🔗].
Mit readStringUntil(‚\n‘) kann das Einlesen z.B. mit dem Terminal-Symbol ‚\n‘, New Line gestoppt werden, die Eingabe erscheint sofort, siehe [Stream.readStringUntil() 🔗].
Aufgabe: Von Seriell-Monitor einlesen und Töne ausgeben
Die Tasten a..l auf der Tastatur sollen die Töne ab c‘ ausgeben, dieser Code ist vorgegeben:
void setup(){
Serial.begin(9600); // Serielle Schnittstelle mit 9600 Baud starten
analogWriteResolution(16); // 16 Bit PWM-Auflösung
}
unsigned char buchstabe[]={'a','s','d','f','g','h','j','k','l'};
unsigned int frequenz[]={262,277,293,311,330,349,370,392,415,440,466,494}; // Frequenzen
void loop(){
String s;
if(Serial.available()){ // wenn Zeichen gesendet wurden
s = Serial.readStringUntil('\n'); // lese Daten in String ein
Serial.print(s); // gib String aus
for (int i=0; _____ ;i++){ // String durchgehen
//Serial.println(s[i]);
for (int k=0; _____ ;k++){ // finde Buchstaben sizeof(buchstabe) gibt die Länge des Arrays zurück
if( ____ ){ // wenn Buchstabe passt
analogWriteFrequency(frequenz[k]);
analogWrite(D11,32000); // PWM mit ca. 50%
delay(200); // Ton halten
analogWrite(D11,0); //Ton aus
delay(20); // bisschen Abstand
______ // etwas effizienter
}
}
}
}
}
Lösungsvorschlag
void setup(){
Serial.begin(9600); // Serielle Schnittstelle mit 9600 Baud starten
analogWriteResolution(16); // 16 Bit PWM-Auflösung
}
unsigned char buchstabe[]={'a','s','d','f','g','h','j','k','l'};
unsigned int frequenz[]={262,277,293,311,330,349,370,392,415,440,466,494}; // Frequenzen
void loop(){
String s;
if(Serial.available()){ // wenn Zeichen gesendet wurden
s = Serial.readStringUntil('\n'); // lese Daten in String ein
Serial.print(s); // gib String aus
for (int i=0;i<s.length();i++){
//Serial.println(s[i]);
for (int k=0;k<sizeof(buchstabe);k++){ // finde Buchstaben sizeof(buchstabe) gibt die Länge des Arrays zurück
if(s[i]==buchstabe[k]){ // wenn Buchstabe passt
analogWriteFrequency(frequenz[k]); // stelle PWM-Frequenz ein
analogWrite(D11,32000); // PWM mit ca. 50%
delay(200); // Ton halten
analogWrite(D11,0); //Ton aus
delay(20); // bisschen Abstand
break; // etwas effizienter
}
}
}
}
}
Verbindung zu weiterer Seriellen Schnittstelle
Die Standart-UART-Schnittstelle dient der Programmierung und Kommunikation mit dem seriellen Monitor. Wenn weitere UART-Module (z.B. Bluetooth) angeschlossen werden sollen wird eine zusätzliche Schnittstelle benötigt. Der µC hat dafür bereits die Hardware, die an bestimmten Ports betrieben werden kann siehe mezmedia.de/etc/hardware/stm32-nucleo-l152re/#Pinout. Beispiel zum Anschluss eines BT-Moduls am Sturm-Board:
HardwareSerial
Synopsis: [https://github.com/stm32duino/Arduino_Core_STM32/wiki/API#hardwareserial]
#define BTRX PB11 // USART3-RX
#define BTTX PB10 // USART3-TX
HardwareSerial SerialBT(BTRX,BTTX); //https://github.com/stm32duino/Arduino_Core_STM32/wiki/API#hardwareserial
SoftwareSerial
Synopsis: [SoftwareSerial Library]
Falls an den gewünschten Pins keine Hardware helfen kann, gibt es auch die Möglichkeit per Software die Schnittstelle an beliebigen Pins zu simulieren, Beispielcode:
#include <SoftwareSerial.h> // Wir verwenden Software Serial
#define BTRX PB11
#define BTTX PB10
SoftwareSerial SerialBT(BTRX, BTTX);
Ich will schnell 50 Werte nach Processing übertragen und dort auswerten!
Synopsis: https://processing.org/reference/libraries/serial/Serial.html🔗
Beim Versuch viele Messwerte über die serielle Schnittstelle nach Processing zu senden bin ich über ein Problem gestolpert.
Arduino Software zum Senden
#define ZAHLEN 500
#define P_USER PC13
void setup(){
pinMode(P_USER,INPUT);
Serial.begin(9600);
}
void loop(){
unsigned char byte_0; // left Byte;
unsigned char byte_1; // right Byte;
if(!digitalRead(P_USER)){ // Übertragung starten
for(int i=0;i<ZAHLEN;i+=10){ // lesen wie der Teufel und speichern
byte_0 = i & 0x00FF; // left Byte; // Übertragung nach Processing Test
byte_1 = (i & 0xFF00) >> 8; // right Byte;
Serial.write(byte_1);
Serial.write(byte_0);
}
delay(500);
}
}
Processing Software zum Empfangen
import processing.serial.*; //verwende die 'Serial' Library
Serial myPort; // Deklariere ein Object vom Typ 'Serial', mit dem Namen 'myPort'
//globale Variablen
int x = 0;
//stelle eine Verbindung zum Arduino/Mbed Controller her.
void setup() {
size(600,600); // Zeichenfläche
background(#FFFFFF); // Weisser Hintergrund
ellipseMode(CENTER); // Tentrum der Ellipse als Referenzpunkt
// Setup der Kommunikation über UART
// Um den Namen der Schnittstelle herauszufinden, lassen wir uns von der Serial-Klasse zunächst
// eine Liste der Namen der am PC vorhandenen Ports geben ('Serial.list()')
for(int i=0;i<Serial.list().length;i++){ // alle Ports ausgeben
println(Serial.list()[i]);
}
// Bei den meisten PCs ist der STM32/Arduino der letzte Port in der Liste. Wir versuchen den letzen in der Liste
String portName = Serial.list()[Serial.list().length-1]; // finde den Namen des letzten Serialports heraus.
int baudrate=9600; // Diese Baudrate muss mit der in eurem MBed/Arduino-Programm übereinstimmen.
// portName = "COM1"; // Feslegung auf Port COM1 sonst wird die erste Schnittstelle in der Liste verwendet.
myPort = new Serial(this, portName, baudrate); // Erzeuge ein Serial Objekt und stelle eine Verbindung her
}
void draw() { // draw wird regelmäßig aufgerufen: Lese Daten und gib sie in Processing aus
int int_16bit = 0;
while (myPort.available() > 0) { // wiederhole das folgende, solange (while) es neue Daten (myPort.available() > 0)gibt.
char hByte = myPort.readChar(); // lese Byte aus Puffer
char lByte = myPort.readChar();
int_16bit = (hByte<<8) + lByte;
if(int_16bit>4095){ // Übertragungsfehler entdeckt
println("Fehler 0x"+hex(int_16bit,4)+" "+int_16bit+" ");
break;
}
stroke(1);
fill(#EA0707);
ellipse(x,500-int_16bit/10,5,5);
x += 5;
if(x==width) {
x=0;
background(#FFFFFF);
}
}
}

Beim Empfang der Daten kam es immer wieder zu Aussetzern, es wurden Bytes verschluckt, die Ausgabe war fehlerhaft.
Irgendwann bin ich drauf gekommen, dass die Ursache an dem unregelmäßigen Aufruf von draw() liegt, bzw. dass Processing die gesendeten Daten nicht in seinen Puffer legt. Zwei Lösungen bieten sich dafür an:
- https://processing.org/reference/libraries/serial/Serial_bufferUntil_.html
- https://processing.org/reference/libraries/serial/Serial_buffer_.html
Weil alle Binärwerte potentiell gesendet werden können ist kein „Terminalsymbol“ für bufferUntil möglich.
Deshalb verwende ich einfach die Anzahl der gesendeten Bytes als Trigger für das Event.
Bislang wurden bei meinen Versuchen keine Fehler mehr angezeigt.
Allerdings würde ich mir schon ein sicheres Verfahren wünschen Binär-Daten, Objekte vom µC zu Processing zu übertragen, bei dem mit potentiellen Übertragungsfehlern sinnvoll umgegangen werden kann. Denke an Bluetooth..
import processing.serial.*; //verwende die 'Serial' Library
Serial myPort; // Deklariere ein Object vom Typ 'Serial', mit dem Namen 'myPort'
//globale Variablen
int x = 0;
int[] messwerte= new int[50]; // Platz für 50 Messwerte
boolean neueMessung=false; // Flag für neue Messwerte angekommen
//stelle eine Verbindung zum Arduino/Mbed Controller her.
void setup() {
size(600,600); // Zeichenfläche
background(#FFFFFF); // Weisser Hintergrund
ellipseMode(CENTER); // Tentrum der Ellipse als Referenzpunkt
// Setup der Kommunikation über UART
// Um den Namen der Schnittstelle herauszufinden, lassen wir uns von der Serial-Klasse zunächst
// eine Liste der Namen der am PC vorhandenen Ports geben ('Serial.list()')
for(int i=0;i<Serial.list().length;i++){ // alle Ports ausgeben
println(Serial.list()[i]);
}
// Bei den meisten PCs ist der STM32/Arduino der letzte Port in der Liste. Wir versuchen den letzen in der Liste
String portName = Serial.list()[Serial.list().length-1]; // finde den Namen des letzten Serialports heraus.
int baudrate=9600; // Diese Baudrate muss mit der in eurem MBed/Arduino-Programm übereinstimmen.
// portName = "COM1"; // Feslegung auf Port COM1 sonst wird die erste Schnittstelle in der Liste verwendet.
myPort = new Serial(this, portName, baudrate); // Erzeuge ein Serial Objekt und stelle eine Verbindung her
myPort.buffer(100); // 50 Messwerte lesen bis Event ausgelöst wird
}
void draw() { // draw wird regelmäßig aufgerufen: Lese Daten und gib sie in Processing aus
int int_16bit = 0;
if (neueMessung) { // wiederhole das folgende, solange (while) es neue Daten (myPort.available() > 0)gibt.
for (int i=0;i<50;i++){
stroke(1);
fill(#EA0707);
ellipse(x,500-messwerte[i]/10,5,5);
x += 5;
if(x==width) {
x=0;
background(#FFFFFF);
}
}
neueMessung=false;
}
}
void serialEvent(Serial myPort) { // Event für serielle Daten
int i = 0;
while (myPort.available() > 0){
messwerte[i]= (myPort.readChar()<<8) + myPort.readChar(); // Bytes aus Puffer lesen
//println(messwerte[i]);
i++;
}
if(i<50){
println("Fehler zu wenige Bytes: "+i);
}
else neueMessung = true;
}
😩 Übertragungsfehler mit Paritätsbit bei Arduino und Processing erkennen?
Eine Suche mit Perplexity ergab diese Aussagen:
- „Bei Arduino gibt es keine direkte Hardware-Unterstützung, um Paritätsfehler zu erkennen, da die Standard-
Serial
-Bibliothek diese Funktionalität nicht bietet.“ - „In Processing können Paritätsfehler bei der seriellen Kommunikation nicht direkt erkannt werden, da die
Serial
-Bibliothek von Processing keine eingebaute Unterstützung für Paritätsprüfung bietet.“
Das war wohl nix mit Parität in Hardware 😔. Es wird immer eine selbst gebaute Softwarelösung vor geschlagen…
ToDo: Lösung mit sinnvollem Protokoll bei fehlerhaften Übertragungen…
Exit-Room-Telefon
Synopsis: [mezdata.de/mez-entwicklung/090_exitroom-telefon/index.php] [wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299]
Info: Kommando 0x12 spielt aus MP3-Ordner ab…