3.6 🚧 UART / RS232

Synopsis: [de.wikipedia.org/wiki/Universal_Asynchronous_Receiver_Transmitter 🔗] [www.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

Logic Analyzer verbinden
Logic Analyzer verbinden
Logic2 einstellen
Logic2 einstellen
Oszillogramm für Datenübertragung

Aufgaben

  1. Zeichnen Sie ein Blockdiagramm mit 2 Kommunikationsteilnehmern die mittels UARTs verbunden sind
  2. Erklären Sie die serielle Kommunikation
  3. Parity
    1. Ergänzen Sie jeweils zu gerader Parity (even)
      1. 01010101___
      2. 11001000___
      3. 00000001___
    2. Ergänzen Sie jeweils zu Ungerader Parity (odd)
      1. 01101010___
      2. 11100011___
      3. 00000000___
    3. Richtig oder fehlerhaft
      1. even 101011011
      2. odd 01011101
      3. even 111001111
    4. Erläutern sie, wie die Erkennung von Fehlern bei der Datenübertragung funktioniert
  4. Wie ist mit UARTs eine Vollduplex-Datenübertragung möglich?
  5. Wie kann bei der Datenübertragung mit UART auf eine Taktleitung verzichtet werden?
  6. Was versteht man unter einer asynchronen Datenübertragung?
Lösung
  1. Siehe Forsa S. 17
  2. Die Datenbits werden seriell, d.h. nacheinander übertragen. Damit das funktioniert müssen Sender und Empfänger mit der gleichen Geschwindigkeit arbeiten.
  3. Parity
    1. Ergänzen Sie jeweils zu gerader Parity (even)
      1. 01010101__0_
      2. 11001000__1_
      3. 00000001__1_
    2. Ergänzen Sie jeweils zu Ungerader Parity (odd)
      1. 01101010__1_
      2. 11100011__0_
      3. 00000000__1_
    3. Richtig oder fehler
      1. even 101011011 ✅
      2. odd 01011101 ✅
      3. even 111001111 ❌
    4. Wenn bei der Übertragung ein Bit umkippt kann dies durch die Parität erkannt werden.
  4. Für Senden und Empfang ist jeweils eine Leitung vorhanden.
  5. Sender und Empfänger haben einen Taktgenerator, durch das Startbit kann der Empfänger sich auf den Sender synchronisieren.
  6. Sender und Empfänger arbeiten nicht mit dem selben Takt sondern der Empfänger hat eine eigene Taktbasis.

Vom Seriellen-Monitor Zeichen einlesen

Synopsis: [arduino.cc/reference/en/language/functions/communication/serial/available/] [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);