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);