Mehrere serielle Terminal-Programme verwenden gleichzeitig gleiche Hardware

Vielen Dank nochmals für die interessanten und sehr zuvorkommenden Ideen zur Umsetzung des gewünschten Vorhabens!

Nachdem die neuen RS3232/TTL Konverter neusten Stands hervorragend funktionieren, musste lediglich noch das Format für die anzusteuernde Hardware auf "char" umgestellt werden.

Wichtig ist, wenn eine Datenleitung mehrmals aus technischen Erwägungen unterbrochen werden muss, also zwischen die Leitungen Hardware eingefügt wird, wie beispielsweise Konverter oder ein Arduino usw., dann sollten alle TX- und RX-Leitungen sehr genau geprüft werden, damit nicht zwei gleiche Signale irrtümlich miteinander verbunden werden.

Bei diesem Vorhaben hatte jede TX- und RX-Leitung einen USB/RS232 Konverter, einen RS3232/TTL Konverter und zusammen wurden sie mittels eines Arduinos MEGA über die Serial-Ports angesteuert. Der Aufbau wurde ganz bewusst so gewählt, denn fällt ein RS3232/TTL Konverter aus, wird er bei dem geringen Preis einfach getauscht. Er schützt in gewisser Weise die etwas hochpreisigere Hardware.

char inByte;

void setup()
{

// initialize serial ports:
  Serial.begin(4800);
  // Serial.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial1.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial2.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial3.begin(4800, SERIAL_8N2);  // Baud, Config
}

void loop()
{

//****************************************************  
  // read from Serial 1, send to Serial 3:
  if (Serial1.available())
    {Serial3.print(inByte = Serial1.read());}

//****************************************************
   // read from Serial 3, send to Serial 1:
  if (Serial3.available())
    {Serial1.print(inByte = Serial3.read());}

}

Vorschlag:

void setup()
{
  Serial1.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial3.begin(4800, SERIAL_8N2);  // Baud, Config
}

void serialEvent1(){ Serial3.write(Serial1.read()); }
void serialEvent3(){ Serial1.write(Serial3.read()); }



void loop(){}

Chapeau Bas!

Zu deutsch, ich weiss nicht aus welchem Fenster ich schauen soll. Der Arduino läuft sogar einen Ticken flüssiger. Das macht sich bemerkbar, wenn die anderen Serials auch noch eingebunden werden. Ich hatte das mit meiner Version schon beobachtet, das ist jetzt viel besser geworden.

Neidlos, danke, danke!

Schön, dass es dir gefällt.
Und danke für die Blumen.

Sehr gerne!

Soweit funktioniert alles mit zwei verschmerzbar kleinen Unzulänglichkeiten und einem unangenehmen Nachteil.

Bei den anfänglichen Tests wurde eine Serial.print(variable) Ausgabe zur Überprüfung der Daten zwischen

Serial1 mit Serial3,
Serial2 mit Serial3

verwendet. Um nun die Serial, eigentlich "Null", ebenfalls verwenden zu können, muss die Serial.print(variable) Ausgabe deaktiviert sein, was zur Folge hat, das keine Kommunikation mehr stattfindet. Dieser Umstand konnte mit einer LCD-Ausgabe über SDA und SLC umgangen werden. Die LCD-Ausgabe ist bei jedem Programmteil

Serial mit Serial3,
Serial1 mit Serial3,
Serial2 mit Serial3

notwendig. Die LCD-Ausgabe ersetzt somit die übliche Serial.print(variable) Ausgabe. Ein LCD-Display wurde jedoch nicht angeschlossen.

Der nächste Umstand entstand durch die Terminal-Programme selbst. Sobald die Terminal-Programme die Serial, Serial1, Serial2 mit Daten ansprechen, kommt es recht oft zu Daten-Kollisionen im Arduino. Die Hardware kann die Empfangs-Daten an sie nicht auswerten. Leider verfügen die Terminal-Programme selten über eine RTS-Steuerung, die dafür sorgt, wann die Terminal-Programme ihre Daten senden dürfen. ― Dies ist auch nicht zwingend notwendig, den üblicherweise wird nicht mit mehreren Terminal-Programmen parallel gearbeitet. Was aber wenn verschiedene Terminal-Programme über exzellente Programm-Funktionen verfügen und genau das liegt vor. Sobald keine Kommunikations-Standards vorliegen, treten unweigerlich Probleme mit Datenkollisionen auf. Unter Windows 10 können virtuelle Ports das kaum noch bzw. nicht mehr bewerkstelligen. ―

Die Deaktivierung und Aktivierung der RX-Dateneingänge der DARTs brachte keine Abhilfe. Abgesehen vom Zeitverzug, erbrachten Serial.end() und Serial.beginn() ebenso keine Abhilfe. Erst mit einem Analogschalter 4066 konnten die Eingänge abgeschottet werden. Jedes Terminal-Programm bekommt gezielt durch den 4066 eine Datenfreigabe. Ab diesem Zeitpunkt traten keine weiteren Daten-Kollisionen auf. Drei Terminal-Programme können nun nacheinander über die Serial3 ihr Daten an die Hardware senden und empfangen. Natürlich nicht gleichzeitigt, sondern mit einem kleineren Zeitverzug.

Nachteil:
Wird nun von einem Terminalprogramm ein Befehl an die Hardware gesendet, dann gelingt das gut. Nur in einem Fall nicht, weil das Terminal-Programm nicht lange genug wartet, um wiederholt den gewünschten Befehl erneut an die Hardware zu senden.

#include <Wire.h>             // LCD library
#include <LiquidCrystal_I2C.h>      
LiquidCrystal_I2C lcd(0x27, 16, 2); 

char inByte;

void setup()
{

// initialize serial ports:
  //Serial.begin(4800);
  Serial.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial1.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial2.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial3.begin(4800, SERIAL_8N2);  // Baud, Config
}

void loop()
{

//****************************************************  
  // read from Serial 0, send to Serial 3:
  if (Serial.available())
    {Serial3.print(inByte = Serial.read());}
    // Serial1.print(inByte);
      //=====[ LCD ]====
      lcd.setCursor(0, 0);
      lcd.print("P0: "); 
      lcd.print(inByte);
      lcd.setCursor(7, 0);
      lcd.print("         ");
      lcd.setCursor(7, 0);
      //=====[ LCD ]====

//****************************************************
   // read from Serial 3, send to Serial 0:
  if (Serial3.available())
    {Serial.print(inByte = Serial3.read());}



//****************************************************  
  // read from Serial 1, send to Serial 3:
  if (Serial1.available())
    {Serial3.print(inByte = Serial1.read());}
    // Serial1.print(inByte);
      //=====[ LCD ]====
      lcd.setCursor(0, 0);
      lcd.print("P1: "); 
      lcd.print(inByte);
      lcd.setCursor(7, 0);
      lcd.print("         ");
      lcd.setCursor(7, 0);
      //=====[ LCD ]====

//****************************************************
   // read from Serial 3, send to Serial 1:
  if (Serial3.available())
    {Serial1.print(inByte = Serial3.read());}



//****************************************************  
  // read from Serial 2, send to Serial 3:
  if (Serial2.available())
    {Serial3.print(inByte = Serial2.read());}
    // Serial2.print(inByte);
      //=====[ LCD ]====
      lcd.setCursor(0, 0);
      lcd.print("P2: "); 
      lcd.print(inByte);
      lcd.setCursor(7, 0);
      lcd.print("         ");
      lcd.setCursor(7, 0);
      //=====[ LCD ]====

//****************************************************
   // read from Serial 3, send to Serial 2:
  if (Serial3.available())
    {Serial2.print(inByte = Serial3.read());}

}

Du hast einen Design-Fehler.
Die Abholung der Daten erfolgt blockadefrei, aber Du vergisst die Herkunft vor der Ausgabe und schiebst alles was irgendwo herkommt auf Serial3.

Kannst Du bitte mal einen vollständigen Datensatz auf Serial3 in HEX ausgeben und zeigen?
Ich kann mir sehr gut vorstellen, das das letzte Zeichen evtl. ein \r oder \n ist.
Damit wird Dein 4066 obsolet.

OK, vielen Dank für den Hinweis. Ichsetzte das so um und stelle das Ergebnis hier ein.

Der 4066 musste durchgeschaltet werden, damit die Daten wie gewünscht fliessen.

digitalWrite (9, HIGH);
digitalWrite (10, HIGH);
digitalWrite (8, HIGH);

Terminal-Programm sendet an die Hardware. Es werden die Flags der Hardware abgefragt und am Terminal das Ergebnis angezeigt.

Das sind zwei Abfrage gleichzeitig:
00 00 00 03 10
00 00 00 01 FA

dem folgt eine Wiederholung.

Nun die HEX-Daten:
0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA000310

void setup() {

  // initialize serial ports:
  Serial.begin(115200);
  Serial1.begin(115200, SERIAL_8N2);
  Serial3.begin(4800, SERIAL_8N2);

digitalWrite (9, HIGH);   // 4066
digitalWrite (10, HIGH); // 4066
digitalWrite (8, HIGH);   // 4066
}

void loop() {
  // read from Serial 1, send to Serial 3:
  if (Serial1.available()) {
    int inByte = Serial1.read();
    Serial.print(inByte, HEX);
    Serial3.print(inByte, HEX);
  }

  // read from Serial 3, send to Serial 1:
  if (Serial3.available()) {
    int inByte = Serial3.read();
    Serial.print(inByte, HEX);
    Serial1.print(inByte, HEX);
  }

}

Las mir etwas Zeit zur Analyse, aber ich denke das wird.

Nachtrag:
Es gibt keinen EndeCode, aber ist die Länge der Abfragen immer 5 Bytes?

Hier eine erweiterte Darstellung, das Terminal-Programm sendet einen Befehl an die Hardware:

0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA000310014401A0001C0001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA0003100001FA000310 usw...

Das sind die Zeichen: 00 50 92 02 0A 00 00 00 01 0C dazwischen. Dann erfolgen die abfragen der Flags von der Hardware.

Ja danke, 5 Byte, laut Hersteller, ist korrekt!

Na SUPER!
Las mir nen Moment, ich ticker mal was in die Tasten...

Dann mal was ganz krudes.
Was der Code machen soll, habe ich in der ersten Funktion kommentiert.
Es passiert folgendes (nach meiner Vorstellung)

  • Ich lese auf der jeweiligen Seriellen Schnittstelle ein zeichen ein, wenn eines vorhanden ist
  • daraus baue ich wieder ein komplettes Paket
  • erst wenn das Paket vollständig ist, gebe ich genau dieses Paket wieder aus.

Was passiert, wenn das Paket von Serial1 und Serial2 gleichzeitig kommt?
Das Paket, welches als erstes vollständig ist, wird dann auf Serial3 ausgegeben. Im nächsten Umlauf dann - wenn es vollständig ist - das nächste Paket.

Was ich jetzt aussen vor gelassen habe, ist eine Antwort abzuwarten - die interessiert mich aber auch erstmal nicht...

Frage dazu:
Wenn ein 5Byte Paket kommt, wird darauf immer eine Antwort gesendet?
Dann liesse sich das ganz einfach implementieren.

char inByte;
char stringPort[4][6] = {'\0'};      // Zeichenkette für jeden SerialPort
uint8_t portPosition[4] = {0};       // Position des Zeichens in der Zeichenkette

void setup()
{
  // initialize serial ports:
  //Serial.begin(4800);
  Serial.begin(4800, SERIAL_8N2);   // Baud, Config
  Serial1.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial2.begin(4800, SERIAL_8N2);  // Baud, Config
  Serial3.begin(4800, SERIAL_8N2);  // Baud, Config
}

void loop()
{
  readSerial();
  readSerial1();
  readSerial2();
}

void readSerial()
{
  // read from Serial 0, send to Serial 3:
  if (Serial.available())                       // wenn ein Zeichen vorhanden ...
  {
    inByte = Serial.read();                     // ... einlesen ...
    stringPort[0][portPosition[0]] = inByte;    // ... in Zeichenkette aufnehmen ...
    portPosition[0]++;                          // ... nächste Position berechnen
  }
  if (portPosition[0] == 5)                        // ... wenn Ende der Zeichenkette erreicht
  {Serial3.println(stringPort[0]);}             // ... gebe aus was da ist ...
  memset(stringPort[0], '\0', sizeof(stringPort[0])); // ... lösche den Inhalt ...
  portPosition[0] = 0  ;                        // ... und setze Position zurück
}

void readSerial1()
{
  // read from Serial 1, send to Serial 3:
  if (Serial1.available())
  {
    inByte = Serial1.read();
    stringPort[1][portPosition[1]] = inByte;
    portPosition[1]++;
  }
  if (portPosition[1] == 5)
  {Serial3.println(stringPort[1]);}
  memset(stringPort[1], '\0', sizeof(stringPort[1]));
  portPosition[1] = 0;
}

void readSerial2()
{
  // read from Serial 2, send to Serial 3:
  if (Serial2.available())
  {
    inByte = Serial2.read();
    stringPort[2][portPosition[2]] = inByte;
    portPosition[2]++;
  }
  if (portPosition[2] == 5)
  {Serial3.println(stringPort[0]);}
  memset(stringPort[2], '\0', sizeof(stringPort[2]));
  portPosition[2] = 0;
}

OK, lieben Dank mal!

Zur Frage: Wenn ein 5Byte Paket kommt, wird darauf immer eine Antwort gesendet?

Wenn die Datenleitung steht oder keine Daten-Kollision vorliegt, dann erfolgt eine Antwort. Viele Terminal-Programme wiederholen den Vorgang bis alles fehlerfrei geklapp hat. Andere Terminal-Programme geben nur einmal den Befehl an die Hardware und wenn ein Fehler auftritt, dann war es das. Und das führt zu einem Problem. Ich muss dann warten, bis der 4066 das Tor wieder für das Terminal-Programm frei macht, um dann den Befehl erneut abzusenden.

Ich teste den Code dankend...

Ok, also eine Antwort kommt. -Auch wieder feste Länge? (5 Bytes?)

Und genau daran habe ich gedacht.
Das wird...

Teste Du aus - ich überleg mal, was ich dazu baue.

OK, vielen Dank!

Ich habe die 4066 noch aktiviert und die Serial-Ausgabe hinzugefügt.

Das Terminal-Programm gibt die beiden Anfragen an die Hardware aus:
00 00 00 03 10
00 00 00 01 FA

Das sieht bei Serial am Arduino sehr gut aus:

0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA0003100001FFFFFFFA

Ok, dann muss ich jetzt also die Antwort mit 5 bytes auswerten und an den jeweiligen Port zurück geben.

Um zu vermeiden, das es zu Endlosschleifen kommt: gibt es ein timeout in der Dokumentation oder muss ich mir etwas ausdenken? (Antwortzeit z.B. auf 50ms?)

Timeout gibt es keines, der Hardware-Hersteller liefert keine Terminal-Software, der überlässt es den Anwendern. Die Programmierer halten sich nicht an verbindliche Standards und genau hier liegt das Problem.