Serielle Schnittstelle spiegeln

Hallo,
ich möchte gern einen LINBus-Sniffer bauen, bzw. habe ihn schon mit Hilfe dieser Bibliothek (die bei mir nicht funktioniert) nachgebaut:

Soweit alles gut, nun ist mein zu sniffender LINBus leider nicht richtig standardkonform, sondern irgendeine herstellerspezifische Abwandlung. Die Frames sind scheinbar anders aufgebaut, ich finde scheinbar auch keinen Sync frame vor.
Um die Originalkommunikation möglichst wenig zu stören, bis ich sie verstanden habe, möchte ich das Signal des Masters mit möglichst unverändertem Timing an den Slave geben und umgekehrt.
Gleichzeitig möchte ich aber Master und Slave getrennt mitlesen.
Die üblichen Sniffer für LINBus, z.B. dieser:

empfangen einen ganzen Frame und senden ihn dann erst weiter. Das scheitert, wenn die Bibliothek den Frame nicht sauber empfangen / unterscheiden / parsen kann. Was ich erst kann, wenn ich ihn verstanden habe (-> nicht standardkonform).
Das Arduino-Beispiel SerialPassthrough gibt die Daten byteweise weiter, aber versaut natürlich (ebenfalls) das Timing.
Ich möchte eigentlich also sowas machen:

digitalWrite(MASTER_TX,digitalRead(SLAVE_RX));
digitalWrite(SLAVE_TX,digitalRead(MASTER_RX));

Das funktioniert auch, Master und Slave verstehen sich weiterhin, das Timing ändert ich nur im unteren Sub-Bit-Bereich.

Nur kann ich so die Bytes nicht mitlesen, denn wenn ich die Schnittstellen als HardwareSerial initialisiere, kann ich nicht mehr mit digitalRead / digitalWrite drauf zugreifen.

Wenn ich Master und Slave elektrisch verbinde, kann ich zwar mitlesen, aber kann nicht unterscheiden, was der Master und was der Slave gesendet hat, da 1-Wire-Bus.

Hat jemand eine Idee, wie ich byteweise beide Richtungen getrennt mitlesen kann, ohne das Originaltiming zu zerstören?

Danke Euch!

Jede Verarbeitung benötigt Zeit, eine gewisse Latenz dürfte nicht zu vermeiden sein. Ein erster Versuch könnte so aussehen:

bool slaveMerker = digitalRead(SLAVE_RX);
digitalWrite(MASTER_TX,slaveMerker);
bool masterMerker = digitalRead(MASTER_RX);
digitalWrite(SLAVE_TX,masterMerker);

Da fehlt noch jegliche Verarbeitung.

Ich würde auf Interrupts setzen, um eine möglichst schnelle Reaktion zu ermöglichen und die Auswertung in loop in der "freien Zeit" zu machen.

Ich hab mal kurz bei wikipedia quergelesen, weil ich ja nu auch nicht alles kenne...
Bei 2V/µS bis Du für eine Flanke bei 6µS und dann werden wohl "nur" max 19200 Bd gebraucht.

digitalRead/-write würde ich nicht verwenden, Geht nicht direkt auf den Pin schreiben und ggfls. mit pcint lesen?

Also, wie gesagt, das Microtiming innerhalb eines Bits ist egal, da sind kleine Latenzen in Ordnung. Ich kann nur nicht ein ganzen Byte oder gar einen ganzen Frame abwarten, ehe ich es rausgebe. Denn bevor der Master den nächsten Frame startet (was er alle 8 ms tut), muss der Slave den Frame erhalte haben, und antworten.
Wie oben geschrieben:

digitalWrite(MASTER_TX,digitalRead(SLAVE_RX));
digitalWrite(SLAVE_TX,digitalRead(MASTER_RX));

funktioniert. Nur wie kann ich das gleichzeitig als Bytes mitlesen und z.B. über meinen Serial Monitor ausgeben?

Da wäre zu klären, über welchen µC wir reden.

1 Like

Prozessor wäre mir egal, habe einen ESP32, einen Arduino Uno, einen ATtiny85, oder einen ESP8266 rumzuliegen.

Wieso mußt Du die Daten Empfangen und wieder Senden wenn Du sie nicht modifizieren mußt? Wieso hörst Du nicht nur mit?

Grüße Uwe

Geht es nicht auch darum welche Station gerade sendet?

Vielleicht noch eine Idee zum ausarbeiten: Widerstand in die Leitung und die Polarität des Spannungsabfalles messen.

Ich würde erwarten daß das aus dem Kontext (request / answer) bzw aus den gesendeten Daten/ Datenheader heraus lesbar ist.
Grüße Uwe

Handelt es sich beim LIN-Bus nicht um einen Eindrahtbus? Zwei Pins würde ich verstehen, aber wie kommst Du dann auf vier Pins?

Die Pins 2 und 3 eignen sich für Interrupts, da hat man eine von loop unabhängige Reaktionszeit.

Programm mit Interrupts ohne Auswertung
const byte MASTER_PIN = 2;
const byte SLAVE_PIN = 3;

volatile bool masterMerker = false;
volatile bool slaveMerker = false;

void masterIRQ()
{
  masterMerker = digitalRead(MASTER_PIN);
  if (masterMerker)
  {
    pinMode(SLAVE_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(SLAVE_PIN), slaveIRQ, CHANGE);
  } else {
    detachInterrupt(digitalPinToInterrupt(SLAVE_PIN));
    pinMode(SLAVE_PIN, OUTPUT);
    digitalWrite(SLAVE_PIN, LOW);
  }
}

void slaveIRQ()
{
  slaveMerker = digitalRead(SLAVE_PIN);
  if (slaveMerker)
  {
    pinMode(MASTER_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(MASTER_PIN), masterIRQ, CHANGE);
  } else {
    detachInterrupt(digitalPinToInterrupt(MASTER_PIN));
    pinMode(MASTER_PIN, OUTPUT);
    digitalWrite(MASTER_PIN, LOW);
  }
}

void setup() {
  Serial.begin(9600);     // Debug
  Serial.println("\nStart");
  pinMode(MASTER_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(MASTER_PIN), masterIRQ, CHANGE);
  pinMode(SLAVE_PIN, INPUT_PULLUP);
}

void loop() {
}

Wenn ich MASTER_PIN mit einem 100µs Rechteck belege, bekomme ich eine Verzögerung von 16 µs.

Bild vom Logikanalysator


Unten MASTER_PIN, oben SLAVE_PIN

Wie soll man dann wissen, wann die Daten anfangen? Mir scheint ein Logikanalysator der erste sinnvolle Schritt zu sein.

Hey,
danke allen für die Beteiligung!

Ja, es geht darum, auch zu wissen, welche Station gerade sendet, durch bloßes Mithören verliere ich diese Information (= Eindrahtbus).
Die Idee mit dem Widerstand und Polarität des Spannungsabfalls find ich gut, werd ich mir zunächst mal auf dem Oszi angucken. Ist halt ein Open-Collector-Bus, da fließt nicht allzu viel Strom. Aber vielleicht reicht es ja...
Wie könnte ich aber diese Information dann in den µC bekommen? (Auf einfache Weise, ohne eine aufwändige Komparatorschaltung.)

Serielle Dekodierung und Logging im Oszi ist vorhanden, aber wie ich schon schrieb, hält sich die Komponente nicht an das normale LIN-Protokoll. Daher kann der integrierte Lin-Decoder das nicht parsen.
Was könnte mir ein Logianalyser sonst noch bieten für diesen Use Case?

Schon allein, dass manche Bytes des Masters manchmal ein neuntes Bit haben, bzw. das Stop-Bit low ist, ist schon komisch.

Zum Eindrahtbus: Ja, der Bus ist ein Draht, aber hinter dem Transmitter (TJA10200) auf TTL-Seite nicht mehr, der hat einen TX- und RX-Pin. Deshalb sind es 4 Pins: Master RX rein, an den Slave TX raus und von Slave RX rein an den Master TX raus.
Und diese beiden Datenflüsse getrennt mitlesen, aber verzögerungsfrei weiterreichen ist die Aufgabe. Verzögerungsfrei ist relativ, wie geschrieben: eine Sub-Bit-Verzögerung wie bei digitalRead/digitalWrite wäre kein Problem, aber ein ganzes Byte schon.

Das Herauslesen aus den Daten kann man machen, wenn man das Protokoll verstanden hat. Aber darum geht es ja eben erst. Und da ist es überaus hilfreich, die Frage von der Antwort unterscheiden zu können. Oder anders gesagt: Alle Information, die ich sicher bekommen kann, muss ich nutzen, um möglichst wenige noch zu enträtselnde Parameter zu haben. Wenn ich nicht mal weiß, wer was "gesagt" hat, wird es erheblich schwerer.

Ein erster Ansatz ist ja, dass der Master ohne angeschlossenen Slave von alleine Dinge sendet. Er sendet alle 8 ms ein Byte (manchmal 9 bit lang), und dazwischen meistens High, aber manchmal auch zwei etwa bytelange Strecken "low".
Das Syncbyte (0x55) kommt ab und zu, aber ist auch nur eines der Bytes, die alle 8 ms kommen. Nicht direkt ein Byte vor dem ID-Byte, wie es der Standard vorsähe.
Für sämtliche vom Master gesendeten Bytes ist die Paritätsprüfung des LIN-Protokolls erfolgreich.

Jetzt könnte ich vermuten, was bei angeschlossenem Slave sich mit dem deckt, was der Master ohne diesen eh sendet, kommt vom Master. Ist aber auch nur eine Mutmaßung. Der Master könnte ja etwas anderes senden, wenn ein Slave da ist / anwortet.
Deshalb is es für mich keine Option, auf die Information zu verzichten, wer was sendet.

Ich denke, der Weg mit dem Widerstand in Reihe könnte was werden.
Idee hierzu: Ich gehe doch zurück zur Analyse mit meinem Oszi (scheiterte bisher an der fehlenden Information, wer gerade spricht), aber hole mir auf dem zweiten Kanal die Polarität an diesem Widerstand dazu.

Ansonsten fiele mir noch ein: mit digitialRead/digitalWrite durchreichen, und im Rest der Zeit mit einem Softwareserial entschlüsseln.
Hier wäre die Frage: Muss ich mir eins schreiben, oder gibt es eine Bibliothek, die die Signale zu Bytes parst, aber die Pins trotzdem für mein eigenes Read/Write bzw. einen Interrupt offen hält?
Das habe ich noch nicht probiert, probiere ich vielleicht mal mit SoftwareSerial.h. Beim HardwareSerial habe ich genau da einen Konflikt: Nach

Serial1.begin(19200, SERIAL_8N1, LIN_SLV_RX, LIN_SLV_TX);
Serial2.begin(19200, SERIAL_8N1, LIN_MAS_RX, LIN_MAS_TX);

kann ich nicht mehr

digitalWrite(LIN_SLV_TX,digitalRead(LIN_MAS_RX));
digitalWrite(LIN_MAS_TX,digitalRead(LIN_SLV_RX));

machen.
Kann man das evtl. irgendwie lösen?

Danke Euch allen schonmal sehr für den Input!

Du kannst ja jenachdem was an Strom erlaubt ist den Pullup kleiner machen. Und abhängig davon was als max. V für den Low Pegel den Meßwiderstand größer... aber vermutlich wirst du da immer noch ein paar Opamps und co draufwerfen müssen um schöne TTL-Pegel zu bekommen.

Hmm... vielleicht wird nur das elektrische interface von LIN benutzt und dann 8 Bit Serial mit Parity?

Müsste nicht alles was auf TX rausgeht direkt wieder auf RX reinkommen?

kopfklatsch das ist mir jetzt peinlich, natürlich, das ist ein zusätzliches Partitätsbit. Man ist immer so 8N1 gewöhnt. Und die LIN-Spec hat ja eigene 2 Paritätsbits innerhalb der 8 Datenbits und kein 9. Bit, dass ich da nicht drauf kam.
Also, damit einen Schritt weiter:
Der Master sendet alle 8 ms ein Byte, 8O1. Das passt.
Innerhalb der 8 Bits sind, der LIN-Spec entsprechend, 2 weitere Paritätsbits. Die stimen auch.
Was nicht der Spec entspricht: kein Sync Break, kein Sync Field. Oder, wenn die ab zu auftauchende 0x55 als Sync field gelten sollte, dann fehlt das direkt darauf folgende ID Byte.

Den Slave habe ich gerade nicht hier.

Aber zum eigentlichen Problem: Wenn ich das 9. Bit prüfen möchte, bin ich mit Arduino-Bibliotheken eh raus, denn soweit ich gelesen habe, machen die alle keinen Paritätscheck. Oder weiß jemand etwas anderes?
Dann würde ich das Signal also selbst parsen müssen, und damit wäre mein gesamtes Problem eh gelöst.
Und sooo ein Hexenwerk ist das ja auch gar nicht, hab das vor Jahren schonmal für einen ATtiny programmiert.

Hmm... ist zumindest dokumentiert das man begin() SERIAL_8O1 mitgeben kann.

Unterschiedliche Pins verwenden.

Mit den einen reichst Du die Daten durch und bestimmst den Urheber Master oder Slave, mit den anderen analysierst Du den Inhalt.

Ja, kann man mitgeben, das Paritätsbit wird beim Senden sogar korrekt gesetzt. Wird aber beim Empfangen nicht überprüft, sondern einfach verworfen. So zumindest mein bisheriges Rechercheergebnis.

Ah, ok, auch ne gute Idee. Also, 2 RX-Pins elektrisch verbinden, den einen dem Serial1 widmen, den anderem dem digitalRead.
Werd ich nächste Woche mal ausprobieren. Hab derzeit den Slave nicht hier...

Also, vielen Dank nochmal für die Tipps. Ich habe es jetzt so gelöst:
Habe in den Busdraht zwei Dioden und Widerstände eingehangen, etwa so:
grafik
Und zunächst mit den zwei Kanälen meines Oszis die Spannungen über je einem Widerstand abgebildet und mit dem seriellen Decoder entschlüsseln lassen.
Habe Potis genommen, um den süßen Punkt zu finden, an dem der Spannungsabfall an den Widerständen groß genug ist zum messen, aber der Bus noch funktioniert.
Die Widerstände waren etwas klein, die Spannungen nur wenige Millivolt. Hat aber für eine zuverlässige Dekodierung genügt. Wollte ich es jetzt in einen Arduino-Eingang bringen, müsste ich größere Widerstände nehmen.
Jetzt habe ich aber im Oszi schon gesehen, was der Master sendet, und zwar ist immer das erste Byte nach einer Pause von ihm, es ist immer eine Auswahl von 6 verschiedenen. Und genau dann, wenn dieses Byte 0x78 lautet, folgen noch 2 weitere, ansonsten nicht. Alles andere ist dann immer der Slave. Und der Master sendet auch immer genau das, auch wenn gar kein Slave dran ist, er scheint also auf dem Bus nicht zu reagieren.
Ich denke, das genügt mir schon, ich werde im Weiteren nur noch wieder beide Richtungen zusammen abhören, jetzt kann ich mit diesen Erkenntnissen alles eindeutig dem Master oder dem Slave zuordnen.

... könntest Du einen Operationsverstärker nutzen.

Danke für die Rückmeldung :slightly_smiling_face:

Hab es heute nochmal mit einem größeren Widerstand wiederholt. So sieht das ganze dann aus:


Ein Op-Verstärker ist mir zu kompliziert und unnötig.
Da gibt es noch irgendwie ein Übersprechen von Kanal A auf B. Aber ist mir egal, es genügt für die Dekodierung.
Das längerfistige Loggen und parsen mach ich dann mit dem Arduino, aber ohne Trennung Master/Slave. Jetzt weiß ich ja hinreichend, wie die Frames aufgebaut sind.