Forum: Mikrocontroller und Digitale Elektronik TJA1020 mit ESP32 betreiben


von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Ich möchte mir einen LIN-Sniffer mit einem ESP32 (S3) bauen und als 
Transceiver einen TJA1020 verwenden und habe dazu noch ein paar Fragen.

Für meine Anwendung reicht ja erstmal nur den RXD zu verbinden. Dieser 
ist laut Schaltbild Open-Drain, muss also mit einem Pullup auf IO-Pegel 
gebracht werden. Sollte ich das mit einem externen Widerstand (10 k) 
tun, oder reicht da auch der interne Pullup des ESP32 auf einem GPIO?

Der LIN-Bus hat einen 12V Pegel, da muss ich mich ja eigentlich nur 
anklemmen. Der TJA selbst benötigt ja eine Betriebsspannung, die liegt 
laut Datenblatt im Bereich 5 bis 27 V. Ich habe jetzt nicht 
herausgelesen das die irgendwie von der LIN-Spannung abhängt, also 
sollte es doch reichen die 5V vom ESP-Board dafür zu nutzen?

Für einen normalen Betriebsmodus ohne irgendwelche Energiesparfunktionen 
muss laut Datenblatt NSLP auf 1 (3,3 V) gezogen werden. Mehr ist für den 
einfachen Lesebetrieb scheinbar nicht notwendig?

Nun zum ESP32. Der hat ja keinen LIN-Controller also muss man das 
entweder per Bit-Banging erledigen oder per UART. Bei UART sehe ich 
besonders die Verarbeitung sowie Erzeugung des BREAK als kritischen 
Punkt an. Der Rest wäre ja gleich? Also Baudrate mit 8N1 sollte bei LIN 
grundsätzlich Bytes liefern. Das SYNC-Byte sollte dann als 0x55 daher 
kommen. Macht es Sinn das mit UART zu tun? Ein "Trick" für das senden 
eines BREAKs ist wohl die Baudrate zu halbieren, aber das empfangen 
eines 13 Bit langen BREAK mit 1 Bit rezessiv in Folge ist wohl ein 
Problem für UARTs.

In meinem konkreten Fall habe ich mal einen Logic-Analyzer an den Bus 
gehangen und bin nach den gemessenen Werten der Meinung das die Baudrate 
9.600 sein müsste. Wenn ich vom LA den ASYNC-Serial Analyzer auf 9600 
8N1 einstelle und den Pegel auf Negativ (weil ja im Ruhezustand HIGH), 
erhalte ich auch ein vermeintlich brauchbares Datenbild.

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Solange Du keine EMV-Tests machen willst, spricht nichts dagegen den 
internen Pullup zu verwenden. Echte Widerstände sind robuster, das 
bringt in Deiner Anwendung wahrscheinlich keine Vorteile.
Schau Dir die steigende Flanke mit dem Oszilloskop an. Wenn die zu sehr 
abgerundet ist, ist der Widerstand zu groß.

Der Transceiver muß mit der Betriebsspannung des Netzwerkes versorgt 
werden. Referenz für High/Low ist die halbe Betriebsspannung.

Beim Empfang mit einem UART liefert das Sync Break einen frame error. Je 
nach Controller kann man damit einen Interrupt auslösen.

LIN mit 9k6 gibt es, üblich ist 19200.

von Rainer W. (rawi)


Lesenswert?

Olli Z. schrieb:
> Ich habe jetzt nicht herausgelesen das die irgendwie von der
> LIN-Spannung abhängt, also sollte es doch reichen die 5V vom
> ESP-Board dafür zu nutzen?

Das ist nicht so richtig toll. An BAT liegt normalerweise die gleiche 
Spannung, mit der auch der Bus läuft. Die Schwelle für den Empfänger 
(receiver threshold voltage V_th(rx)) liegen irgendwo zwischen 0.4 ... 
0.5 V_Bat @ V_Bat 7.3 ... 27 V (Datenblatt Philips/NXP s.11), d.h. wenn 
du den TJA1020 mit 5V betreibst, ist damit zu rechnen, dass die Schwelle 
wahrscheinlich bei um die 2 ... 3V liegt (nicht mehr spezifiziert). Du 
hast also einen kräftig reduzierten Störabstand. Sobald der LIN-Pegel 
nicht weit genug auf 0V gezogen wird, kann es eng werden, bei 
kapazitiver Belastung verschieben sich Signalflanken.

: Bearbeitet durch User
von Frank K. (fchk)


Lesenswert?

Olli Z. schrieb:

> Für meine Anwendung reicht ja erstmal nur den RXD zu verbinden. Dieser
> ist laut Schaltbild Open-Drain, muss also mit einem Pullup auf IO-Pegel
> gebracht werden. Sollte ich das mit einem externen Widerstand (10 k)
> tun, oder reicht da auch der interne Pullup des ESP32 auf einem GPIO?

Sehe in jedem Fall externe Widerstände vor. Nicht bestücken kannst Du 
immer noch.

> Der LIN-Bus hat einen 12V Pegel, da muss ich mich ja eigentlich nur
> anklemmen. Der TJA selbst benötigt ja eine Betriebsspannung, die liegt
> laut Datenblatt im Bereich 5 bis 27 V. Ich habe jetzt nicht
> herausgelesen das die irgendwie von der LIN-Spannung abhängt, also
> sollte es doch reichen die 5V vom ESP-Board dafür zu nutzen?

Nein, das ist so nicht vorgesehen. VBAT kommt direkt an die 
LIN-Busspannung (12 oder 24V je nach System un Transceiver).

> Für einen normalen Betriebsmodus ohne irgendwelche Energiesparfunktionen
> muss laut Datenblatt NSLP auf 1 (3,3 V) gezogen werden. Mehr ist für den
> einfachen Lesebetrieb scheinbar nicht notwendig?

ja.

> Nun zum ESP32. Der hat ja keinen LIN-Controller also muss man das
> entweder per Bit-Banging erledigen oder per UART. Bei UART sehe ich
> besonders die Verarbeitung sowie Erzeugung des BREAK als kritischen
> Punkt an. Der Rest wäre ja gleich? Also Baudrate mit 8N1 sollte bei LIN
> grundsätzlich Bytes liefern. Das SYNC-Byte sollte dann als 0x55 daher
> kommen. Macht es Sinn das mit UART zu tun? Ein "Trick" für das senden
> eines BREAKs ist wohl die Baudrate zu halbieren, aber das empfangen
> eines 13 Bit langen BREAK mit 1 Bit rezessiv in Folge ist wohl ein
> Problem für UARTs.

LIN ist im Prinzip UART mit ein paar Spezialitäten. Laut Datenblatt 
müsste der S3 Breaks senden können. Beim Empfangen eines Breaks wirst Du 
wohl ein Nullbyte plus einen Framing Error bekommen.

> In meinem konkreten Fall habe ich mal einen Logic-Analyzer an den Bus
> gehangen und bin nach den gemessenen Werten der Meinung das die Baudrate
> 9.600 sein müsste. Wenn ich vom LA den ASYNC-Serial Analyzer auf 9600
> 8N1 einstelle und den Pegel auf Negativ (weil ja im Ruhezustand HIGH),
> erhalte ich auch ein vermeintlich brauchbares Datenbild.

9600 und 19200 sind gängige Bitraten.

fchk

von Alexander (alecxs)


Lesenswert?

Frank K. schrieb:
> Beim Empfangen eines Breaks wirst Du wohl ein Nullbyte plus einen
> Framing Error bekommen.

Dafür gibt's was (ungetestet)

https://github.com/CW-B-W/ESP32-SoftwareLIN

von Frank O. (frank_o)


Lesenswert?

Mal eine allgemeine Frage zum dem Sniffer: Was erwartest du da zu sehen?

Jeder vernünftige Tester kann bestimmte Daten auslesen und die 
angeschlossenen Geräte darstellen, das sogar als Kurve, falls mal 
sporadische Fehler sind. Aber die sagt dir das Auto sowieso und der 
Tester kann sie lesen.
Deshalb würde ich gerne wissen, ob es Gründe gibt, die mir so nicht 
bekannt sind.

von Soul E. (soul_eye)


Lesenswert?

Frank O. schrieb:
> Jeder vernünftige Tester kann bestimmte Daten auslesen und die
> angeschlossenen Geräte darstellen, das sogar als Kurve,

Man kann LIN-Tools auch fertig kaufen, das ist richtig. Ich nutze hier 
Vector CANoe mit einem VN1640. Aber ich hatte den TO so verstanden, dass 
er selber etwas bauen wollte?

Zwischen den beiden Lösungen liegen ca 26 dB Preisunterschied.

von Frank O. (frank_o)


Lesenswert?

Mir ging es eher um das Sniffen. Da ich am Auto alles mit Launch Testern 
mache und machen kann, verstehe ich nicht was man da sehen will.
Man kann dann vielleicht die Nachrichten sehen und das alles 
funktioniert,  aber außer man will aus solchen Erkenntnissen selbst 
irgendeinen Tester bauen und verkaufen,  verstehe ich den Sinn nicht.
Wenn ich irgendein Gerät oder Sensor überwachen will, lege ich den auf 
das Display,  mit sinnvollen anderen Werten und das ist besser als "nur" 
ein LIN Tester.
Deshalb frage ich, weil der TO vielleicht etwas weiß,  auf das ich 
selbst gerade nicht komme und vielleicht nicht einmal kenne.
Ich möchte nur verstehen warum. Ebenso will ich auch keinen bekehren was 
anderes,  wie so einen Launch Tester zu nehmen.

: Bearbeitet durch User
von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Soul E. schrieb:
> Solange Du keine EMV-Tests machen willst, spricht nichts dagegen den
> internen Pullup zu verwenden. Echte Widerstände sind robuster, das
Ok, ich verwende jetzt einen 10k Pullup auf Vcc (3,3V).

> Der Transceiver muß mit der Betriebsspannung des Netzwerkes versorgt
> werden. Referenz für High/Low ist die halbe Betriebsspannung.
Ja, irgendwie auch logisch (im Datenblatt steht Vo(reces) = 0.9 - 
1.0VBAT und Vo(dom) 0.6 - 2,0 V) Irgendwie hab ich das überlesen. Danke 
für die Aufklärung! Also klemme ich Pin 7 (VBAT) an +12V.

Damit auf RXD was kommt muss NSLP ja auf HIGH, was laut Datenblatt 2-7V 
sind. Also kann ich den direkt, ohne Widerstand auf +3,3V legen.

> Beim Empfang mit einem UART liefert das Sync Break einen frame error. Je
> nach Controller kann man damit einen Interrupt auslösen.
Tja, wenn ich meinen LA hinter den TJA klemme, habe ich ein sauberes 
Signal. Wenn ich das versuche mit meinem ESP32 über UART zu verarbeiten 
kommt nix.
Der wesentliche Teil der Initialisierung sieht so aus:
1
esp_err_t lin_sniffer_init(const lin_sniffer_config_t *config)
2
{
3
    // Configure UART for passive listening
4
    uart_config_t uart_config = {
5
        .baud_rate = config->baudrate,
6
        .data_bits = UART_DATA_8_BITS,
7
        .parity = UART_PARITY_DISABLE,
8
        .stop_bits = UART_STOP_BITS_1,
9
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
10
        .source_clk = UART_SCLK_DEFAULT,
11
    };
12
13
    esp_err_t ret = uart_param_config(config->uart_num, &uart_config);
14
    if (ret != ESP_OK) {
15
        ESP_LOGE(TAG, "UART param config failed: %s", esp_err_to_name(ret));
16
        return ret;
17
    }
18
19
    // Set RX-only pins (no TX pin for passive sniffing)
20
    ret = uart_set_pin(config->uart_num, UART_PIN_NO_CHANGE, config->rx_pin, 
21
                       UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
22
    if (ret != ESP_OK) {
23
        ESP_LOGE(TAG, "UART set pin failed: %s", esp_err_to_name(ret));
24
        return ret;
25
    }
26
27
    // Install UART driver
28
    ret = uart_driver_install(config->uart_num, UART_BUF_SIZE * 2, UART_BUF_SIZE * 2, 0, NULL, 0);
29
    if (ret != ESP_OK) {
30
        ESP_LOGE(TAG, "UART driver install failed: %s", esp_err_to_name(ret));
31
        return ret;
32
    }
33
34
    ...
35
}

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Frank O. schrieb:
> Deshalb frage ich, weil der TO vielleicht etwas weiß,  auf das ich
> selbst gerade nicht komme und vielleicht nicht einmal kenne.
Auch wenn das hier eigentlich nichts zur Sache tut, es Dich aber 
sichtlich beschäftigt darf ich Doch beruhigen, ich bastle einfach gern, 
gehe Dingen auf den Grund und lerne gern neues. Ich bin sicher das man 
mit fertigen, teurem Equipment alles viel leichter hin bekommen könnte, 
ich habe halt eine Kiste voller Elektronikteile :-) Mein Ziel ist es die 
LIN-Kommunikation mit einem Batteriesensor zu untersuchen, aber auch das 
tut nichts zur Sache, ich könnte auch ein Türmodul oder sonst was 
nehmen.
Also, keine Geheimnisse, kannst ganz beruhigt sein :-)

von Olli Z. (z80freak)


Lesenswert?

Alexander schrieb im Beitrag #80047
> https://github.com/CW-B-W/ESP32-SoftwareLIN
Ja, kannte ich schon, danke. Ist für Arduino-IDE geschrieben und basiert 
auf weiteren Libs. Das müsste ich alles für ESP-IDF und VS Code 
portieren...
Ich habe mir wohl so ziemlich alle Libs angeschaut die es so gibt, aber 
das macht es dann irgendwann nicht besser, im Gegenteil. Daher ja meine 
Frage nach der grundsätzlichen Ausrichtung, mit Uart oder Bit-Banging 
oder doch einen externen LIN Controller?

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Olli Z. schrieb:
> Frage nach der grundsätzlichen Ausrichtung, mit Uart oder Bit-Banging
> oder doch einen externen LIN Controller?

Ich kenne die UART von Deinem Controller nicht, aber grundsätzlich 
funktioniert LIN mit einer UART. Beim Empfang liefert der Sync Break 
einen frame error, den muss man halt bestätigen um danach das 0x55 
empfangen zu können. Beim Senden schaltet man entweder die Bitrate 
runter um die 11-13 Nullen rauszuschieben, oder man setzt den Pin für 
eine Weile auf GPIO und macht das mit einem Timer, oder man schaltet 
einen zweiten Pin als GPIO parallel und macht das mit dem.


Wenn Du mit Deiner UART was senden kannst und der LA das versteht, dann 
sollte die Initialisierung geklappt haben. Dann bleibt die Frage wo der 
Sync Break hingeht, ob der irgendwie den Controller durcheinanderbringt. 
Dazu könntest Du über den LIN-Transceiver ein normales UART-Signal 
einspeisen. Z.B. von einem FTDI mit einem zweiten Transceiver dahinter.

von Frank O. (frank_o)


Lesenswert?

Olli Z. schrieb:

> Also, keine Geheimnisse, kannst ganz beruhigt sein :-)

Da bin ich wirklich beruhigt. Ich dachte schon,  dass es vielleicht 
etwas gibt, wo ich mit dem Tester nicht dran komme.

von Soul E. (soul_eye)


Lesenswert?

Frank O. schrieb:
> Da bin ich wirklich beruhigt. Ich dachte schon,  dass es vielleicht
> etwas gibt, wo ich mit dem Tester nicht dran komme.

Unter "Tester" verstehst Du vermutlich ein OBD2- oder 
Werkstatt-Diagnosegerät? Diese Geräte sind dafür gebaut, um über ein 
Diagnose-Gateway über spezielle Protokolle wie UDS oder KWP auf 
ausgewählte Steuergeräte zuzugreifen. Üblicherweise sind sie nicht dazu 
geeignet, die Kommunikation auf einem der inneren Busse (hinter dem 
Gateway) komplett auszulesen oder zu beeinflussen.

Einen Transparent-Modus, d.h. alles was kommt mitlesen und auf dem 
Bildschirm ausgeben, bieten eher solche Tools wie CanCool, CANHacker 
oder eben CANoe.

: Bearbeitet durch User
von Frank O. (frank_o)


Lesenswert?

Soul E. schrieb:
> Unter "Tester" verstehst Du vermutlich ein OBD2- oder
> Werkstatt-Diagnosegerät?

Klar!
Mitlesen will ich auch nicht die Kommunikation,  sondern die Werte,  die 
beispielsweise ein Sensor ausgibt.
Hier sind ja ziemlich gute Leute dabei und ich dachte halt, dass es auf 
dem LIN Bus vielleicht irgendwas gibt, wo ich vielleicht nicht mit dem 
üblichen "Diagnosegerät" gar nicht erst hinkomme.
Aber mein neuer Launch sollte so ziemlich überall ran kommen.
Wir können das jetzt sein lassen und den TO seinen eigentlichen Zweck 
dieses Threads überlassen.

von Alexander (alecxs)


Lesenswert?

Frank O. schrieb:
> Man kann dann vielleicht die Nachrichten sehen und das alles
> funktioniert,  aber außer man will aus solchen Erkenntnissen selbst
> irgendeinen Tester bauen und verkaufen,  verstehe ich den Sinn nicht.

Ich denke das sind zwei verschiedene Schuhe. Mit dem Launch kommst Du in 
die Steuergeräte rein, dort siehst Du was immer Dir das Steuergerät 
zeigen möchte, und zwar ausschließlich über die dort hinterlegten 
Funktionen. Im Klartext, Du kannst eigentlich keinen Sensor auslesen, Du 
kannst Dir nur die Daten anschauen die das Steuergerät vom Sensor 
bekommen hat.

Den CAN Traffic (hier LIN) abzugreifen ist was völlig anderes. Dort 
siehst Du den Bus und die Kommunikation der Steuergeräte miteinander. 
Der für mich einzige Grund sich das anzutun ist, eigene zusätzliche 
Hardware zu vernetzen die sich über die vorhandene Hardware steuern 
lässt. In meinem Fall ein ESP32 der über die Lenkradtasten (CAN) bedient 
wird, und welcher seinerseits auch das Auto bedient, u.a. - quasi MITM 
mit WiFi.

In einem anderen Thread ging es um einen ~230V Inverter von VW welcher 
sich nur über LIN einschalten lässt, der TE aber nicht das passende 
Signal vom BMS verfügbar hatte. Da könnte ich mir vorstellen dass es 
Sinn macht das von einem anderen Fzg zu sniffen und dann Replay über 
einen mit ESP32 simulierten LIN auszugeben, also eine fehlende Hardware 
zu ersetzen.

Stelle ich mir mit einem Launch schwierig vor, ist der falsche Schuh 
dafür.

Also was auch immer hier über LIN empfangen werden soll, der ESP32 ist 
wohl dazu in der Lage die Daten auszuwerten und für eigene 
automatisierte Aktionen zu verwenden, so wie der TE das gerne hätte. Das 
sehe ich als den Benefit einer solchen Aktion ggü. einem käuflichen 
Tester.

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Alexander schrieb:
> Den CAN Traffic (hier LIN) abzugreifen ist was völlig anderes.

Richtig. Man kann auch nicht WireShark, ein Programm zum mitlesen des 
Verkehrs in Computernetzwerken, ersetzen durch Firefox, ein Programm zum 
Abrufen von Daten über eben diese Netzwerke. Das sind völlig 
unterschiedliche Anwendungen.

von Olli Z. (z80freak)


Lesenswert?

Wenn ich das inzwischen richtig gelesen habe 1) hat der UART im ESP32 
eine Break-Detection mit der er einen "BRK_DET" Interrupt auslösen kann, 
der genau dafür gedacht ist.

Ich denke meinen Ansatz also mit UART nochmal neu. Der TJA1020 
funktioniert inzwischen, ich bekomme also ein sauberes Signal. Wichtig 
hierbei ist vielleicht noch das dieses dem LIN-Pegel folgt, also im 
Ruhezustand HIGH, aber ansonsten nicht invertiert ist. Eine BUS-0 ist 
also eine Logische-0.

UART0 wird intern fürs Flashen und Debuggen benutzt, also nehme ich 
UART1. Den initialisiere ich also mit 9.600 Baud, weise den GPIO4 dem 
RX-Signal zu. Für die Empfangsdetektion eines BREAK gibt es in ESP-IDF 
5.5 keine besonderen Funktionen (nur für das Senden mit BREAK), das wird 
also über Hardware und Events gelöst.

Ich muss also einen Empfangstask bauen der mit xQueueReceive() auf 
Events aus der "uart_queue" wartet und den Event-Typ auswertet 
(uart_event_t). Ist dieser Typ "UART_BREAK" dann hat es den auf dem Bus 
gegeben.

1) 
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/uart.html

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Ich habe angefügten Code geschrieben und in der Tat erhalte ich beim 
Anschluß an den LIN ein Ergebnis:
1
I (253) LIN_SNIFFER: Starting LIN Bus Sniffer
2
I (257) uart: queue free spaces: 10
3
I (8254) LIN_SNIFFER: UART_BREAK detected!
4
I (8263) LIN_SNIFFER: UART_BREAK detected!
5
I (8278) LIN_SNIFFER: UART_BREAK detected!
6
I (8288) LIN_SNIFFER: UART_BREAK detected!
7
I (8298) LIN_SNIFFER: UART_BREAK detected!
8
I (8309) LIN_SNIFFER: UART_BREAK detected!
9
I (8324) LIN_SNIFFER: UART_BREAK detected!
10
I (8334) LIN_SNIFFER: UART_BREAK detected!
11
I (8343) LIN_SNIFFER: UART_BREAK detected!
12
I (8354) LIN_SNIFFER: UART_BREAK detected!
13
I (8368) LIN_SNIFFER: UART_BREAK detected!
14
I (8378) LIN_SNIFFER: UART_BREAK detected!
15
I (8388) LIN_SNIFFER: UART_BREAK detected!
16
I (8398) LIN_SNIFFER: UART_BREAK detected!
17
I (8414) LIN_SNIFFER: UART_BREAK detected!
18
I (8424) LIN_SNIFFER: UART_BREAK detected!
19
I (8434) LIN_SNIFFER: UART_BREAK detected!
20
I (8444) LIN_SNIFFER: UART_BREAK detected!
21
I (8458) LIN_SNIFFER: UART_BREAK detected!
22
I (8462) LIN_SNIFFER: UART_DATA: 120 bytes
23
I (8468) LIN_SNIFFER: UART_BREAK detected!
24
...

Der Code initialisiert ganz normal eine UART mit 9600-8N1 und wartet 
dann auf einen UART Event UART_BREAK. Damit ich ohne angeschlossenen 
Transceiver keinen solchen Event erhalte habe ich den GPIO vom RX-Pin 
auf internen Pullup gestellt.

Als nächstes müsste ich also eine Frame-Erkennung implementieren. Die 
Bytes träufeln nach dem BREAK ja sofort weiter in den Buffer, lösen also 
dann valide UART_DATA Events aus. Das erste Byte müsste ja eigentlich 
immer ein SYNC sein, also 0x55.

Jetzt brauche ich noch ein wenig Logik um die Frames aus den eingehenden 
Datenbytes zu ermitteln und defekte auszusortieren. Ein Frame kann ja 
maximal 10 Bytes lang sein: SYNC + 8xDATA + CHKSUM.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Habe jetzt einfach mal die empfangenen Bytes ausgeben lassen und die 
BREAKs ignoriert:
1
I (257) uart: queue free spaces: 10
2
I (5939) LIN_SNIFFER: UART_DATA: 120 bytes
3
I (5939) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 20 84 80 00 00 fa 00 55
4
I (5940) LIN_SNIFFER: 06 00 00 ff 00 55 85 40 09 b6 00 55 06 00 00 ff
5
I (5944) LIN_SNIFFER: 00 55 50 00 0a f5 00 55 06 00 00 ff 00 55 85 40
6
I (5951) LIN_SNIFFER: 09 b6 00 55 06 00 00 ff 00 55 20 84 80 01 00 f9
7
I (5957) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 85 40 08 b7 00 55 06 00
8
I (5963) LIN_SNIFFER: 00 ff 00 55 0d a4 1f c5 34 42 00 55 06 00 00 ff
9
I (5969) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 20 84
10
I (5975) LIN_SNIFFER: 80 01 00 f9 00 55 06 00
11
I (6147) LIN_SNIFFER: UART_DATA: 120 bytes
12
I (6147) LIN_SNIFFER: 00 ff 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55
13
I (6148) LIN_SNIFFER: 8e 00 b2 11 ff 3c 00 55 06 00 00 ff 00 55 85 40
14
I (6154) LIN_SNIFFER: 02 bd 00 55 06 00 00 ff 00 55 20 84 80 01 00 f9
15
I (6160) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 85 40 02 bd 00 55 06 00
16
I (6167) LIN_SNIFFER: 00 ff 00 55 cf 9d 82 ac ff 33 00 55 06 00 00 ff
17
I (6173) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 20 94
18
I (6179) LIN_SNIFFER: 80 01 00 e9 00 55 06 00 00 ff 00 55 85 40 02 bd
19
I (6185) LIN_SNIFFER: 00 55 06 00 00 ff 00 55
20
I (6338) LIN_SNIFFER: UART_DATA: 105 bytes
21
I (6338) LIN_SNIFFER: 8e 00 b2 11 ff 3c 00 55 06 00 00 ff 00 55 85 40
22
I (6339) LIN_SNIFFER: 02 bd 00 55 06 00 00 ff 00 55 20 94 80 01 00 e9
23
I (6343) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 85 40 02 bd 00 55 06 00
24
I (6349) LIN_SNIFFER: 00 ff 00 55 0d 07 1e c2 34 e3 00 55 06 00 00 ff
25
I (6356) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 20 94
26
I (6362) LIN_SNIFFER: 80 01 00 e9 00 55 06 00 00 ff 00 55 85 40 02 bd
27
I (6368) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 08
28
I (6456) LIN_SNIFFER: UART_DATA: 64 bytes
29
I (6456) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 85 40 02 bd 00 55 06 00
30
I (6456) LIN_SNIFFER: 00 ff 00 55 20 94 82 03 00 e5 00 55 06 00 00 ff
31
I (6461) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 8e 00
32
I (6467) LIN_SNIFFER: b2 11 ff 3c 00 55 06 00 00 ff 00 55 85 40 02 bd
33
I (6692) LIN_SNIFFER: UART_DATA: 120 bytes
34
I (6692) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 20 94 82 03 00 e5 00 55
35
I (6693) LIN_SNIFFER: 85 40 02 bd 00 55 50 00 4a b5 00 55 2b 02 41 47
36
I (6699) LIN_SNIFFER: 39 4e 00 00 00 ed 00 55 6a ff ff ff ff ff ff 00
37
I (6705) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 97 b8 c7 e8 c3 d2 00 55
38
I (6711) LIN_SNIFFER: 20 94 82 03 00 e5 00 55 85 40 02 bd 00 55 a6 86
39
I (6718) LIN_SNIFFER: c3 69 c2 89 00 55 e7 ff ff ff ff 00 00 55 06 00
40
I (6724) LIN_SNIFFER: 00 ff 00 55 f5 02 42 56 36 54 00 00 00 da 00 55
41
I (6730) LIN_SNIFFER: 20 94 82 03 00 e5 00 55
42
I (6768) LIN_SNIFFER: UART_DATA: 33 bytes
43
I (6768) LIN_SNIFFER: 85 40 02 bd 00 55 06 00 00 ff 00 55 20 94 82 03
44
I (6768) LIN_SNIFFER: 00 e5 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55
45
I (6773) LIN_SNIFFER: 99
46
I (6788) LIN_SNIFFER: UART_DATA: 3 bytes
47
I (6788) LIN_SNIFFER: 00 55 61
48
I (7018) LIN_SNIFFER: UART_DATA: 120 bytes
49
I (7018) LIN_SNIFFER: 00 55 20 94 82 03 00 e5 00 55 85 40 02 bd 00 55
50
I (7018) LIN_SNIFFER: 06 00 00 ff 00 55 20 94 82 03 00 e5 00 55 85 40
51
I (7023) LIN_SNIFFER: 02 bd 00 55 50 00 4a b5 00 55 2b 03 31 30 43 36
52
I (7029) LIN_SNIFFER: 37 39 00 b1 00 55 6a ff ff ff ff ff ff 00 00 55
53
I (7035) LIN_SNIFFER: 06 00 00 ff 00 55 97 b8 c7 e8 c3 d2 00 55 20 94
54
I (7042) LIN_SNIFFER: 82 02 00 e6 00 55 85 40 02 bd 00 55 a6 86 c3 69
55
I (7048) LIN_SNIFFER: c2 89 00 55 e7 ff ff ff ff 00 00 55 06 00 00 ff
56
I (7054) LIN_SNIFFER: 00 55 f5 03 31 37 44 35
57
I (7123) LIN_SNIFFER: UART_DATA: 47 bytes
58
I (7123) LIN_SNIFFER: 34 37 00 af 00 55 20 84 80 00 00 fa 00 55 85 40
59
I (7123) LIN_SNIFFER: 02 bd 00 55 06 00 00 ff 00 55 20 84 80 00 00 fa
60
I (7128) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 99
61
I (7143) LIN_SNIFFER: UART_DATA: 3 bytes
62
I (7143) LIN_SNIFFER: 00 55 61
63
I (7373) LIN_SNIFFER: UART_DATA: 120 bytes
64
I (7373) LIN_SNIFFER: 00 55 20 84 80 00 00 fa 00 55 85 40 02 bd 00 55
65
I (7374) LIN_SNIFFER: 06 00 00 ff 00 55 20 84 80 00 00 fa 00 55 85 40
66
I (7378) LIN_SNIFFER: 02 bd 00 55 50 00 0a f5 00 55 2b 04 44 45 00 00
67
I (7385) LIN_SNIFFER: 00 00 00 72 00 55 6a ff ff ff ff ff ff 00 00 55
68
I (7391) LIN_SNIFFER: 06 00 00 ff 00 55 97 b8 c7 e8 c3 d2 00 55 20 84
69
I (7397) LIN_SNIFFER: 80 00 00 fa 00 55 85 40 02 bd 00 55 a6 86 c3 69
70
I (7403) LIN_SNIFFER: c2 89 00 55 e7 ff ff ff ff 00 00 55 06 00 00 ff
71
I (7409) LIN_SNIFFER: 00 55 f5 04 41 44 00 00

Ich muss also auch das durch den BREAK erzeugte 0x00 am Anfang 
ignorieren.
Laut LIN Protokoll ist die Anzahl der Datenbytes (Nutzlast) eines Frame 
abhängig von seiner ID:
ID  1..31 (0x1F) = 2 Bytes
ID 32..47 (0x2F) = 4 Bytes
ID 48..63 (0x3F) = 8 Bytes
Dem folgt dann noch das Checksum-Byte.

Ich könnte nun eine Frame-Queue bauen in der ich die Frames reinstecke, 
erstmal ungeprüft, aber ohne BREAK+SYNC. Oder ich prüfe zuerst die 
Checksum ob der Frame valide ist und stopfe nur PID und Nutzlast in die 
Queue? Davor muss ich aber immer auch prüfen ob der Frame überhaupt 
vollständig im Buffer liegt.

: Bearbeitet durch User
von Frank O. (frank_o)


Lesenswert?

Sieht schon mal nach Erfolg aus.
Gut gemacht!

von Soul E. (soul_eye)


Lesenswert?

Olli Z. schrieb:
> Ich muss also auch das durch den BREAK erzeugte 0x00 am Anfang
> ignorieren.

Klar, die UART weiß ja bei den ersten acht bit noch nicht, dass das 
gleich einen Überlauf geben wird.


> Ich könnte nun eine Frame-Queue bauen in der ich die Frames reinstecke,
> erstmal ungeprüft, aber ohne BREAK+SYNC. Oder ich prüfe zuerst die
> Checksum ob der Frame valide ist und stopfe nur PID und Nutzlast in die
> Queue? Davor muss ich aber immer auch prüfen ob der Frame überhaupt
> vollständig im Buffer liegt.

Meist macht man sich Mailboxen, wo immer die gleiche Botschaft 
reinkommt. Die Applikation findet dann da immer die neusten Daten vor.

Also empfangen, prüfen, Interupts sperren, Nutzdaten in die Mailbox, 
Interrupts wieder freigeben.

von Olli Z. (z80freak)


Lesenswert?

Der UART des ESP32 hat einen Hardware-Buffer von 128 Byte und der 
Library-Default löst bei 120 Bytes einen UART_DATA Event aus, oder nach 
einer gewissen Timeout-zeit. Ich habe beide Werte etwas nach unten 
justiert damit ich schneller auf die Daten reagieren/parsen kann. So 
oder so weiß man nie genau was im Empfangsbuffer liegt, man muss sich 
das wohl Byte für Byte anschauen.

Daher habe ich mir nun einen Parser über eine Statemachine gebaut die 
valide Frames aus dem empfangenen Bytestrom erkennt und diese dann in 
eine Queue (aka Mailbox) steck. Das Grundgerüst ist so:
1
void lin_parse_byte(lin_parser_t *parser, uint8_t byte) {
2
    switch(parser->state) {
3
        case WAIT_FOR_BREAK:
4
            // Was tun wenn wir 0x00 sehen?
5
            break;
6
            
7
        case WAIT_FOR_SYNC:
8
            // Was tun wenn wir 0x55 erwarten?
9
            break;
10
            
11
        case READ_PID:
12
            // PID lesen, Parity prüfen, data_len setzen
13
            break;
14
            
15
        case READ_DATA:
16
            // Datenbytes sammeln
17
            break;
18
            
19
        case READ_CHECKSUM:
20
            // Checksum validieren, Frame ausgeben
21
            break;
22
    }
23
}

Mein nächstes Etappenziel ist es, valide, vollständige Frames zu 
erhalten und keinen zusammenhanglosen Bytestrom. Die Frames enthalten 
die ID und die Nutzdaten, welche ich dann in einem anderen Task 
verarbeiten, aber erstmal einfach nur ausgeben werde. Für die Frames 
habe ich mir diese Struktur überlegt:
1
typedef struct {
2
    lin_parser_state_t state;
3
    uint8_t pid;          // Protected ID (mit Parity)
4
    uint8_t id;           // Frame ID (ohne Parity)
5
    uint8_t data[8];
6
    uint8_t data_len;     // Erwartete Länge
7
    uint8_t data_idx;     // Aktueller Index
8
    uint8_t checksum;
9
} lin_parser_t;

von Olli Z. (z80freak)


Lesenswert?

Ok, ich bekomme nun solche Ergebnisse mit meinem Parser:
1
I (5281) LIN_SNIFFER: UART_DATA event: received 120 bytes
2
I (5281) LIN_SNIFFER: Valid frame- ID: 0x06
3
I (5281) LIN_SNIFFER: 00 00
4
I (5281) LIN_SNIFFER: Valid frame- ID: 0x20
5
I (5285) LIN_SNIFFER: 84 80 00 00
6
I (5288) LIN_SNIFFER: Valid frame- ID: 0x06
7
I (5292) LIN_SNIFFER: 00 00
8
I (5294) LIN_SNIFFER: Valid frame- ID: 0x05
9
I (5298) LIN_SNIFFER: 40 09
10
I (5301) LIN_SNIFFER: Valid frame- ID: 0x06
11
I (5305) LIN_SNIFFER: 00 00
12
I (5307) LIN_SNIFFER: Valid frame- ID: 0x10
13
I (5311) LIN_SNIFFER: 00 0a
14
I (5314) LIN_SNIFFER: Valid frame- ID: 0x06
15
I (5318) LIN_SNIFFER: 00 00
16
I (5320) LIN_SNIFFER: Valid frame- ID: 0x05
17
I (5324) LIN_SNIFFER: 40 09
18
I (5327) LIN_SNIFFER: Valid frame- ID: 0x06
19
I (5330) LIN_SNIFFER: 00 00
20
I (5333) LIN_SNIFFER: Valid frame- ID: 0x20
21
I (5337) LIN_SNIFFER: 84 80 01 00
22
I (5340) LIN_SNIFFER: Valid frame- ID: 0x06
23
I (5344) LIN_SNIFFER: 00 00
24
I (5346) LIN_SNIFFER: Valid frame- ID: 0x05
25
I (5350) LIN_SNIFFER: 40 08
26
I (5353) LIN_SNIFFER: Valid frame- ID: 0x06
27
I (5357) LIN_SNIFFER: 00 00
28
W (5359) LIN_SNIFFER: Checksum error - ID: 0x0D
29
I (5363) LIN_SNIFFER: Valid frame- ID: 0x06
30
I (5367) LIN_SNIFFER: 00 00
31
I (5370) LIN_SNIFFER: Valid frame- ID: 0x05
32
I (5374) LIN_SNIFFER: 40 02
33
I (5376) LIN_SNIFFER: Valid frame- ID: 0x06
34
I (5380) LIN_SNIFFER: 00 00
35
I (5383) LIN_SNIFFER: Valid frame- ID: 0x20
36
I (5387) LIN_SNIFFER: 84 80 01 00
37
I (5488) LIN_SNIFFER: UART_DATA event: received 120 bytes
38
I (5489) LIN_SNIFFER: Valid frame- ID: 0x06
39
I (5489) LIN_SNIFFER: 00 00
40
I (5489) LIN_SNIFFER: Valid frame- ID: 0x05
41
I (5493) LIN_SNIFFER: 40 02
42
I (5495) LIN_SNIFFER: Valid frame- ID: 0x06
43
I (5499) LIN_SNIFFER: 00 00
44
W (5502) LIN_SNIFFER: Checksum error - ID: 0x0E
45
...

Sieht doch schon sehr gut aus, finde ich. Jetzt analysiere ich noch 
warum es so häufig Checksum-Fehler gibt, das kommt mir noch etwas 
seltsam vor...

Und dann fange ich an die Frames zu analysieren.
Die LIN ID 0x06 scheint mir sowas wie ein Keepalive zu sein?
In dem Datenstrom habe ich mir auch mal die Bytes als ASCII anzeigen 
lassen, das ist immer recht aufschlußreich und was finde ich da? Diese 
Teilenummer "BV6T-17D547-AD". Das ist aber ein Regensensor bei Ford. 
Laut Schaltplan dachte ich, der Batteriesensor liegt alleine auf dem 
LIN-Bus zum BCM, aber scheinbar ist dort wenigstens noch der Regensensor 
mit dabei! Das erklärt mir nun auch als ich mal etwas mit dem TJA1020 
rumgespielt hatte das plötzlich der Scheibenwischer "auslöste". 
Interessant! ;-)

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Ah, ich hab da einen Verdacht! Das BCM (LIN Master) fragt verschiedenste 
IDs ab um Daten von den Slaves zu lesen, aber manche IDs bleiben einfach 
unbeantwortet weil die Slaves auf dem Bus nicht existieren (Fahrzeug 
ausstattungsabhängig). Dann habe ich natürlich nur sowas wie 0x00 0x55 
0x08 auf dem Bus, quasi nur den Request, ohne Antwort.
Auf diese Situation ist mein Code noch nicht vorbereitet, das muss ich 
also irgendwie nachholen... Das Problem wird sein ein dann später 
folgendes Frame 0x00 0x55 ... mit gültigen Daten von einer Antwort eines 
Slaves welche zufällig 0x00 0x55 als Daten hat zu unterscheiden.
Der Master wird einen Timeout haben um auf die Antwort zu warten, evtl. 
ist das sogar im Protokoll definiert. Das wird auch der Grund sein warum 
ich z.B. die PID 0x08 immer am Ende eines RX-Buffers vorfinde weil der 
UART Timeout zuschlagen wird. Setze ich diesen UART-Timeout zu hoch an, 
wären wohl auch nachfolgende Frames im Buffer. Es ist also unter 
Umständen wichtig den Timeout so klein wie möglich zu stellen, also auch 
etwas über der "Response Space" damit wir nicht beantwortete Requests 
sauber ausfiltern können?
Ich sehe jedenfalls keinen wirklich guten Weg das rein auf 
Bytestrom-Basis zu unterscheiden, ob die einer PID folgenden Daten zum 
Frame gehören oder eben selbst ein neuer Frame sind?

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Olli Z. schrieb:
> Ah, ich hab da einen Verdacht! Das BCM (LIN Master) fragt verschiedenste
> IDs ab um Daten von den Slaves zu lesen, aber manche IDs bleiben einfach
> unbeantwortet weil die Slaves auf dem Bus nicht existieren (Fahrzeug
> ausstattungsabhängig). Dann habe ich natürlich nur sowas wie 0x00 0x55
> 0x08 auf dem Bus, quasi nur den Request, ohne Antwort.

Das wäre ein Rx Error, den sollte man behandeln können. Tx Error ist 
wenn der vom Master gesendete Header beschädigt wird, z.B. durch EMV 
oder Kurzschluß. Das kann natürlich nur der Master selbst erkennen.


> (...) Das Problem wird sein ein dann später
> folgendes Frame 0x00 0x55 ... mit gültigen Daten von einer Antwort eines
> Slaves welche zufällig 0x00 0x55 als Daten hat zu unterscheiden.

Genau dafür gibt es den Sync Break. Bzw bei Dir den Interrupt oder das 
frame error-Flag. Das muss man halt abfragen und als Start der Botschaft 
werten.



> Wie immer liegen die wahren Probleme in der Dynamic eines Systems.

Schalte vor dem letzten Teilnehmer 100 Ohm in Reihe mit dem LIN. Dann 
gehen dessen dominanten Pulse nicht so weit runter wie die des Masters. 
So kann man am Oszi unterscheiden was woher kommt. Bei mehreren 
Teilnehmern geht man die Slaves der Reihe nach durch. Auspinnen, 
Widerstand rein, IDs aufschreiben. Dann weisst Du schonmal, welche Daten 
wo her kommen.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Ich habe das nun soweit implementiert das ein BREAK einen neuen Frame 
startet. Das kommt aktuell dabei raus:
1
# Hier lasse ich einmal den gesamten RX-Buffer ausgeben
2
#
3
I (5943) LIN_SNIFFER: UART_DATA event: received 120 bytes
4
I (5944) LIN_SNIFFER: 00 ff 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55
5
I (5944) LIN_SNIFFER: 8e 00 b2 13 ff 3a 00 55 06 00 00 ff 00 55 85 40
6
I (5950) LIN_SNIFFER: 02 bd 00 55 06 00 00 ff 00 55 20 84 80 01 00 f9
7
I (5956) LIN_SNIFFER: 00 55 06 00 00 ff 00 55 85 40 02 bd 00 55 06 00
8
I (5962) LIN_SNIFFER: 00 ff 00 55 cf 9f 8c ba ff 19 00 55 06 00 00 ff
9
I (5969) LIN_SNIFFER: 00 55 85 40 02 bd 00 55 06 00 00 ff 00 55 20 94
10
I (5975) LIN_SNIFFER: 80 01 00 e9 00 55 06 00 00 ff 00 55 85 40 02 bd
11
I (5981) LIN_SNIFFER: 00 55 06 00 00 ff 00 55
12
13
# Und ab hier arbeitet der Frame-Scanner
14
#
15
I (5985) LIN_SNIFFER: Valid frame- ID: 0x05
16
I (5989) LIN_SNIFFER: 40 02
17
I (5991) LIN_SNIFFER: Valid frame- ID: 0x06
18
I (5995) LIN_SNIFFER: 00 00
19
20
# Hier kommt schon das erste Problem. Die Bytelänge einer ID 0x0E (PID 0x8E) müsste eigentlich 2 sein, daher meint mein Parser das Checksumbyte seit die 0x13, aber der Frame ist 4 Byte lang und das Checksumbyte wäre eigentlich 0x3a
21
#
22
W (5998) LIN_SNIFFER: Checksum error - ID: 0x0E, Received: 0x13, Classic: 0x4D, Enhanced: 0xBE
23
24
I (6006) LIN_SNIFFER: 00 b2
25
I (6008) LIN_SNIFFER: Valid frame- ID: 0x06
26
I (6012) LIN_SNIFFER: 00 00
27
I (6015) LIN_SNIFFER: Valid frame- ID: 0x05
28
I (6019) LIN_SNIFFER: 40 02
29
I (6021) LIN_SNIFFER: Valid frame- ID: 0x06
30
I (6025) LIN_SNIFFER: 00 00
31
I (6028) LIN_SNIFFER: Valid frame- ID: 0x20
32
I (6032) LIN_SNIFFER: 84 80 01 00
33
I (6035) LIN_SNIFFER: Valid frame- ID: 0x06
34
I (6039) LIN_SNIFFER: 00 00
35
I (6041) LIN_SNIFFER: Valid frame- ID: 0x05
36
I (6045) LIN_SNIFFER: 40 02
37
I (6048) LIN_SNIFFER: Valid frame- ID: 0x06
38
I (6051) LIN_SNIFFER: 00 00
39
W (6054) LIN_SNIFFER: Checksum error - ID: 0x0F, Received: 0xBA, Classic: 0xD3, Enhanced: 0x04
40
I (6062) LIN_SNIFFER: 9f 8c
41
I (6065) LIN_SNIFFER: Valid frame- ID: 0x06
42
I (6069) LIN_SNIFFER: 00 00
43
I (6071) LIN_SNIFFER: Valid frame- ID: 0x05
44
I (6075) LIN_SNIFFER: 40 02
45
I (6078) LIN_SNIFFER: Valid frame- ID: 0x06
46
I (6082) LIN_SNIFFER: 00 00
47
I (6084) LIN_SNIFFER: Valid frame- ID: 0x20
48
I (6088) LIN_SNIFFER: 94 80 01 00
49
I (6091) LIN_SNIFFER: Valid frame- ID: 0x06
50
I (6095) LIN_SNIFFER: 00 00
51
I (6097) LIN_SNIFFER: Valid frame- ID: 0x05
52
I (6101) LIN_SNIFFER: 40 02
53
I (6104) LIN_SNIFFER: Valid frame- ID: 0x06
54
I (6108) LIN_SNIFFER: 00 00

Ich kann mich also scheinbar nicht darauf verlassen das die ID-Bereiche 
Aufschluß über die Datenlänge geben. Bei den meisten stimmts, aber bei 
einigen eben leider nicht. Ohne den Widerstandstest werde ich auch 
erstmal nicht wissen ob die gesamte Nachricht vom Master kommt (Write) 
oder dieser nur eine Antwort vom Slave anfordert (Read). Antwort auf all 
diese Fragen dürfte nur die LDF geben, oder Reverse-Engineering...
Oder ich muss meine Längenerkennung nicht auf die ID stützen, sondern 
auf das nächste BREAK bzw. einen Timeout wenn dieses nicht kommt?

von Soul E. (soul_eye)


Lesenswert?

Die Zuordnung der frame length zum Identifier legt der Hersteller fest. 
Die Norm meint hierzu nur "The number of data contained in a frame with 
a specific frame identifier shall be agreed by the publisher and all 
subscribers.". D.h. wenn Du kein LDF hast, bleibt für die initale 
Erkennung nur der timeout.

von Alexander (alecxs)


Lesenswert?

Stimmte denn die Checksumme bei 4 Bytes?

von Olli Z. (z80freak)


Lesenswert?

Ja die Checksum würde stimmen (LIN 2.0 Classic)

von Olli Z. (z80freak)


Lesenswert?

Soul E. schrieb:
>> folgendes Frame 0x00 0x55 ... mit gültigen Daten von einer Antwort eines
>> Slaves welche zufällig 0x00 0x55 als Daten hat zu unterscheiden.
>
> Genau dafür gibt es den Sync Break. Bzw bei Dir den Interrupt oder das
> frame error-Flag. Das muss man halt abfragen und als Start der Botschaft
> werten.

Wenn UART_BREAK ausgelöst wird befindet sich definitiv bereits ein 0x00 
im RX-Buffer. Das nachfolgende 0x55 und ggf. weitere Daten kommen so 
schnell das wenn der RX-Buffer nicht zufällig voll ist, der Timeout noch 
nicht zuschlägt.

Ggf. folgen weitere UART_BREAK während ich noch versuche aus dem Anfang 
des RX-Buffers schlau zu werden. So oder so lässt sich in 0x00 im 
RX-Buffer nicht mehr eindeutig als BREAK oder DATUM unterscheiden. 
Anders wäre es wenn der UART_BREAK einen Zeiger auf das Byte vor dem 
Break im RX-Buffer liefern würde, tut er aber nicht.

Ich fürchte wenn man kein LDF hat um die Datenlänge sinnvoll zu 
bestimmen hilft nur ein Pattern-Matching mit Lookahead. Ich lese im 
Datenstrom ein 0x00 0x55 und interpretiere alles danach bis zum nächsten 
0x00 0x55 als Daten+Checksum. Wenn die Checksum passt habe ich ein 
gültiges Frame gefunden, wenn nicht habe ich möglicherweise 0x00 0x55 
als Daten, also lese ich von dort an weiter bis zum darauffolgenden 0x00 
0x55 und prüfe erneut.
Ist die Gesamtlänge größer 8 Byte ist es ein Fehler und ich setze am 
letzten 0x00 0x55 neu an.
Ist die Gesamtlänge 0 Bytes dann habe ich ein unbeantwortetes Frame 
gefunden welches ich ignoriere und wieder neu ansetze.
1
Stream: ... 00 55 [PID] [data...] [checksum] 00 55 [PID] ...
2
             ^                                ^
3
             Start                            Nächster Start

Vom Ablauf her so:
- Finde "0x00 0x55" -> merke Position
- Suche nächstes "0x00 0x55"
- Alles dazwischen = PID + Data + Checksum
- Validiere:
  - Länge = 0? -> Unbeantworteter Frame, verwerfen
  - Länge > 9? (PID + 8 Data + Checksum) -> Fehler, von Position des 2. 
"0x00 0x55" neu starten
  - PID Parity OK? Checksum OK? -> Gültiger Frame!
- Sonst: Das erste 0x00 0x55 war in den Daten -> schiebe Start um 1 Byte 
weiter und probiere erneut

von Alexander (alecxs)


Lesenswert?

Ich seh jetzt noch nicht wo du die 4 Byte ID liest, aber gut Du machst 
das alles zu Fuß da musst Du den Weg alleine gehen.

Ich hätte ja zumindest testhalber mal Arduino installiert. Dann hättest 
Du Referenzdaten zum Vergleich, ob das alles richtig ist wie Du das 
empfängst.

von Olli Z. (z80freak)


Lesenswert?

Alexander schrieb:
> Ich seh jetzt noch nicht wo du die 4 Byte ID liest, aber gut Du machst
> das alles zu Fuß da musst Du den Weg alleine gehen.
>
> Ich hätte ja zumindest testhalber mal Arduino installiert. Dann hättest
> Du Referenzdaten zum Vergleich, ob das alles richtig ist wie Du das
> empfängst.

Keine Ahnung was eine IDE mir da helfen könnte? Ich programmiere in VS 
Code, was macht das für einen Unterschied?

Im Kern habe ich folgende UART-Event-Handler Routine geschrieben:
1
void uart_event_task(void *pvParameters)
2
{
3
    uart_event_t event;
4
    uint8_t rx_buf[120];
5
6
    static lin_parser_t parser = {
7
        .buffer_len = 0;
8
    };
9
10
    while(1)
11
    {
12
        if (xQueueReceive(uart_queue, &event, portMAX_DELAY))
13
        {
14
            switch (event.type)
15
            {
16
                case UART_DATA:
17
                    int len = uart_read_bytes(UART_NUM, rx_buf, event.size, 10 / portTICK_PERIOD_MS);
18
                    for (int i = 0; i < len; i++) {
19
                        lin_parse_byte(&parser, rx_buf[i]);
20
                    }
21
                    break;
22
23
                case UART_BREAK:
24
                    // Optional: Für Debugging
25
                    ESP_LOGD(TAG, "UART_BREAK detected");
26
                    break;
27
28
                case UART_FIFO_OVF:
29
                    ESP_LOGW(TAG, "FIFO overflow");
30
                    uart_flush_input(UART_NUM);
31
                    xQueueReset(uart_queue);
32
                    parser.buffer_len = 0;  // Buffer zurücksetzen
33
                    break;
34
35
                case UART_BUFFER_FULL:
36
                    ESP_LOGW(TAG, "RX buffer full");
37
                    uart_flush_input(UART_NUM);
38
                    xQueueReset(uart_queue);
39
                    parser.buffer_len = 0;  // Buffer zurücksetzen
40
                    break;
41
42
                default:
43
                    break;
44
            }
45
        }
46
    }
47
}

Sie kümmert sich eigentlich nur um die UART_DATA, also die valide 
empfangenen Daten und parst diese Byte für Byte durch nachfolgende 
Statemachine:
1
void lin_parse_byte(lin_parser_t *parser, uint8_t byte)
2
{
3
    // Byte in Buffer hinzufügen
4
    if (parser->buffer_len >= sizeof(parser->buffer)) {
5
        // Buffer voll - verwerfe älteste Bytes (sliding window)
6
        memmove(parser->buffer, parser->buffer + 1, sizeof(parser->buffer) - 1);
7
        parser->buffer_len--;
8
    }
9
    parser->buffer[parser->buffer_len++] = byte;
10
    
11
    // Suche nach mindestens zwei 0x00 0x55 Pattern
12
    if (parser->buffer_len < 4) return;  // Mindestens 00 55 + 00 55
13
    
14
    // Finde alle 0x00 0x55 Positionen
15
    for (uint8_t start = 0; start < parser->buffer_len - 3; start++) {
16
        // Prüfe ob an Position 'start' ein 0x00 0x55 ist
17
        if (parser->buffer[start] != 0x00 || parser->buffer[start + 1] != 0x55) {
18
            continue;
19
        }
20
        
21
        // Wir haben ein 0x00 0x55 an Position 'start'
22
        // Suche das nächste 0x00 0x55
23
        for (uint8_t end = start + 2; end < parser->buffer_len - 1; end++) {
24
            if (parser->buffer[end] != 0x00 || parser->buffer[end + 1] != 0x55) {
25
                continue;
26
            }
27
            
28
            // Wir haben ein zweites 0x00 0x55 an Position 'end'
29
            // Frame liegt zwischen start+2 und end-1
30
            uint8_t frame_len = end - (start + 2);
31
            
32
            // Validiere Frame-Länge
33
            if (frame_len == 0) {
34
                // Unbeantwortetes Frame (nur Header)
35
                ESP_LOGD(TAG, "Header-only frame (no response)");
36
                // Entferne verarbeitete Bytes
37
                memmove(parser->buffer, parser->buffer + end, parser->buffer_len - end);
38
                parser->buffer_len -= end;
39
                return;
40
            }
41
            
42
            if (frame_len > 9) {
43
                // Zu lang - das erste 0x00 0x55 war wohl Daten
44
                // Probiere nächste Position
45
                continue;
46
            }
47
            
48
            // Extrahiere Frame-Komponenten
49
            uint8_t pid = parser->buffer[start + 2];
50
            
51
            // Prüfe PID Parity
52
            if (!lin_check_pid_parity(pid)) {
53
                ESP_LOGW(TAG, "PID parity error: 0x%02X - trying next sync", pid);
54
                continue;  // Probiere nächste Position
55
            }
56
            
57
            uint8_t id = lin_get_id_from_pid(pid);
58
            uint8_t data_len = frame_len - 2;  // PID und Checksum abziehen
59
            uint8_t checksum = parser->buffer[end - 1];
60
            
61
            // Prüfe Datenlänge
62
            if (data_len > 8) {
63
                continue;  // Zu viele Datenbytes
64
            }
65
            
66
            // Extrahiere Daten
67
            uint8_t data[8];
68
            for (uint8_t i = 0; i < data_len; i++) {
69
                data[i] = parser->buffer[start + 3 + i];
70
            }
71
            
72
            // Validiere Checksum (Classic)
73
            uint8_t chksum_classic = lin_calculate_checksum_classic(data, data_len);
74
            bool valid = (checksum == chksum_classic);
75
            
76
            if (!valid) {
77
                // Versuche Enhanced Checksum
78
                uint8_t chksum_enhanced = lin_calculate_checksum_enhanced(pid, data, data_len);
79
                valid = (checksum == chksum_enhanced);
80
            }
81
            
82
            if (valid) {
83
                // ERFOLG! Gültiger Frame gefunden
84
                ESP_LOGI(TAG, "Valid frame - ID: 0x%02X, Len: %d", id, data_len);
85
                ESP_LOG_BUFFER_HEX(TAG, data, data_len);
86
                
87
                // Speichere für spätere Verwendung
88
                parser->pid = pid;
89
                parser->id = id;
90
                parser->data_len = data_len;
91
                memcpy(parser->data, data, data_len);
92
                parser->checksum = checksum;
93
                
94
                // Entferne verarbeitete Bytes aus Buffer
95
                memmove(parser->buffer, parser->buffer + end, parser->buffer_len - end);
96
                parser->buffer_len -= end;
97
                return;
98
            } else {
99
                // Checksum ungültig
100
                ESP_LOGW(TAG, "Checksum error - ID: 0x%02X, Len: %d, Received: 0x%02X, Classic: 0x%02X", 
101
                         id, data_len, checksum, chksum_classic);
102
                ESP_LOG_BUFFER_HEX(TAG, data, data_len);
103
104
                // Entferne diesen Frame und probiere weiter
105
                memmove(parser->buffer, parser->buffer + end, parser->buffer_len - end);
106
                parser->buffer_len -= end;
107
                return;
108
            }
109
        }
110
    }
111
    // Kein zweites 0x00 0x55 gefunden - warte auf mehr Daten
112
}

Diese erzeugt die Frames nun nach einer Window-Shifting Methode mit 
Lookahead. Ist etwas aufwändinger als die vorherige SM, kommt dafür aber 
auch mit weniger Status aus und sollte robuster gegenüber alle Arten von 
Anomalien sein. Aber irgendwo kommt mein Code noch ins straucheln:
1
I (7405) LIN_SNIFFER: 02 bd 00 55 06 00 00 ff 00 55 99 00 55 61 00 55
2
I (7405) LIN_SNIFFER: 20 94 82 03 00 e5 00 55 85 40 02 bd 00 55 06 00
3
I (7407) LIN_SNIFFER: 00 ff 00 55 20 94 82 03 00 e5 00 55 85 40 02 bd
4
I (7413) LIN_SNIFFER: 00 55 50 00 4a b5 00 55 2b 03 31 30 43 36 37 39
5
I (7419) LIN_SNIFFER: 00 b1 00 55 6a ff ff ff ff ff ff 00 00 55 06 00
6
I (7425) LIN_SNIFFER: 00 ff 00 55 97 b9 c7 e8 c3 d1 00 55 20 94 82 02
7
I (7431) LIN_SNIFFER: 00 e6 00 55 85 40 02 bd 00 55 a6 87 c3 6b c2 86
8
I (7437) LIN_SNIFFER: 00 55 e7 ff ff ff ff 00
9
I (7441) LIN_SNIFFER: Valid frame - ID: 0x05, Len: 2
10
I (7446) LIN_SNIFFER: 40 02                   | @.      
11
I (7451) LIN_SNIFFER: Valid frame - ID: 0x06, Len: 2
12
I (7456) LIN_SNIFFER: 00 00                   | ..      
13
W (7461) LIN_SNIFFER: Checksum error - ID: 0x19, Len: 2, Received: 0x61, Classic: 0xAA
14
I (7469) LIN_SNIFFER: 00 55
15
I (7471) LIN_SNIFFER: Valid frame - ID: 0x20, Len: 4
16
I (7476) LIN_SNIFFER: 94 82 03 00             | ....

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Olli Z. schrieb:
> Keine Ahnung was eine IDE mir da helfen könnte? Ich programmiere in VS
> Code, was macht das für einen Unterschied?

Naja der Unterschied ist Du kannst eine fertige Lib nehmen.

Olli Z. schrieb:
> Ja, kannte ich schon, danke. Ist für Arduino-IDE geschrieben und basiert
> auf weiteren Libs. Das müsste ich alles für ESP-IDF und VS Code
> portieren...

portieren musst Du da nix

https://github.com/CW-B-W/ESP32-openLIN

von Olli Z. (z80freak)


Lesenswert?

Das kann man leider nicht so vergleichen da diese Lib davon ausgeht das 
Master oder Slave wissen wieviele Bytes zu senden/zu empfangen sind. Was 
diese und andere Libs aber machen ist das sie überall auf Timeouts 
setzen, also davon ausgehen das die Datenübertagungen immer in gewissen 
Zeitfenstern stattfinden. Das mache ich derzeit nicht und das ist 
vielleicht der Schlüssel! Ich werde mir das mal näher ansehen...

von Olli Z. (z80freak)


Lesenswert?

Nochmal ein Gedankenspiel zur Frame-Erkennung ohne Verwendung von 
UART_BREAK:

a1) Ich bekomme also einen einfachen Datenstrom in dem ein BREAK als 
0x00 enthalten ist, aber auch ein Datum sein kann.

a2) Ein Frame-Header wird immer vom Master erzeugt und stellt sich im 
Bytestrom als "0x00 0x55 PID" dar. Das kann eigentlich nur durch 
elektrische Probleme auf dem Bus korrumpiert werden.

a3) Folgt diesem Frame-Header innerhalb einer gewissen Zeit (Timeout) 
kein weiteres Byte (weder vom Master noch von einem durch die PID 
angesprochenen Slave), ist es unbeantworteter Slave-PID, sprich der 
Slave existiert nicht auf dem Bus.

a4) Einer PID müssen 2, 4 oder 8 Bytes und ein Checksum-Byte folgen.

a5) Sind aktuell nur 2 Bytes im RX-Buffer und folgt innerhalb des 
Timeouts kein weiteres, sind die Bytes als defekter Frame zu werden und 
zu verwerfen.

a6) Sind wenigstens noch 9 Bytes im Buffer prüft man ob diese Bytes mit 
dem letzten Byte als Checksum valide wären. Wenn ja ist es ein Frame.

a7) Sind wenigstens noch 5 Bytes im Buffer, führt man dieselbe Prüfung 
wie bei a6) durch.

a8) Sind wenigstens 3 Bytes im Buffer, ebenso Prüfung von a6) 
durchführen.

a9) Sind trotz genügend Bytes (mind. 3) im Buffer alle 
Checksum-Prüfungen fehl geschlagen, war der auslösende "0x00 0x55 PID" 
möglicherweise kein BREAK+SYNC+PID sondern Daten. In dem Fall verwerfe 
das gelesene "0x00 0x55" und beginne mit den restlichen Bytes neu bei 
a2)

a10) Aufgrund a3) muss also wenigstens noch ein weiteres Byte im Buffer 
liegen, ggf. zwei. Folgen diesem innerhalb der Timeout-Zeit keine 
weiteren Bytes handelt es sich um einen defekten Frame und die Bytes 
sind zu verwerfen.

Das müsste meiner Meinung nach alle Fälle abdecken. Das Prinzip nutzt 
also die Checksum-Berechnung um gültige Frames zu erkennen. Der Vorteil 
wäre das dies auch "Offline" möglich ist, also man könnte auf Bus-Seite 
einfach nur Bytes erkennen und loggen/übertragen und die Analyse 
übernimmt ein anderes Tool.
Voraussetzung ist das man immer alle Checksum-Algos (LIN 1.3, LIN 2.0, 
Classic, Enhanced) prüft und das es keine Custom-Checksum-Algos gibt.

von Soul E. (soul_eye)


Lesenswert?

Olli Z. schrieb:
> a1) Ich bekomme also einen einfachen Datenstrom in dem ein BREAK als
> 0x00 enthalten ist, aber auch ein Datum sein kann.

Als Break ist das frame error-bit gesetzt (oder der Interrupt kommt, 
falls Du den nutzt. Als Datenbyte ist es gelöscht. In beiden Fällen 
steht 0x00 im Register.

> a2) Ein Frame-Header wird immer vom Master erzeugt und stellt sich im
> Bytestrom als "0x00 0x55 PID" dar. Das kann eigentlich nur durch
> elektrische Probleme auf dem Bus korrumpiert werden.
>
> a3) Folgt diesem Frame-Header innerhalb einer gewissen Zeit (Timeout)
> kein weiteres Byte (weder vom Master noch von einem durch die PID
> angesprochenen Slave), ist es unbeantworteter Slave-PID, sprich der
> Slave existiert nicht auf dem Bus.

Wenn dem Header (0x00 0x55) gar nichts folgt, ist Dein Master kaputt.

> a4) Einer PID müssen 2, 4 oder 8 Bytes und ein Checksum-Byte folgen.

Wenn Header + PID keine Daten folgen, dann ist der Slave nicht 
vorhanden. Das ist dann ein Rx Error, damit muss der Master umgehen 
können.

Die Checksum gehört zu den Nutzdaten. Im Standard ist definiert wie die 
auszusehen hat, letztendlich liegt es an der Implementierung was da 
drinsteht.


> a5) Sind aktuell nur 2 Bytes im RX-Buffer und folgt innerhalb des
> Timeouts kein weiteres, sind die Bytes als defekter Frame zu werden und
> zu verwerfen.
>
> a6) Sind wenigstens noch 9 Bytes im Buffer prüft man ob diese Bytes mit
> dem letzten Byte als Checksum valide wären. Wenn ja ist es ein Frame.

Zumindest wenn er sich an die Norm hält.

> (...)
>
> Das müsste meiner Meinung nach alle Fälle abdecken. Das Prinzip nutzt
> also die Checksum-Berechnung um gültige Frames zu erkennen.

Das kann man so machen. Du hast aber zusätzlich das frame error-Flag, 
das Du direkt nach Empfang einer 0x00 prüfen kannst.

> Der Vorteil
> wäre das dies auch "Offline" möglich ist, also man könnte auf Bus-Seite
> einfach nur Bytes erkennen und loggen/übertragen und die Analyse
> übernimmt ein anderes Tool.
> Voraussetzung ist das man immer alle Checksum-Algos (LIN 1.3, LIN 2.0,
> Classic, Enhanced) prüft und das es keine Custom-Checksum-Algos gibt.

Richtig.

von Alexander (alecxs)


Lesenswert?

Soul E. schrieb:
> Du hast aber zusätzlich das frame error-Flag, das Du direkt nach Empfang
> einer 0x00 prüfen kannst.

Ist noch mal ein anderer Ansatz. Wie ist das beim Slave gelöst? Hier 
geht es über timestamps (eigentlich gut in der Readme.md erklärt)
1
/**
2
 * @brief Check whether there is Break Field sent on the bus.
3
 * This function blocks until the UART ISR is triggered.
4
 * After the UART ISR is triggered, this function will check
5
 * whether break field has been detected. If break field has
6
 * been detected, this function will return true, otherwise
7
 * this function will return false.
8
 * 
9
 * @return Whether the Break Field has been detected
10
 */
11
bool checkBreak();
https://github.com/CW-B-W/ESP32-SoftwareLIN/blob/master/src/SoftwareLin.cpp#L57

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Ein HW-Lin erzeugt beim Sync Break einen Sync Break-Interrupt. Wie de 
sich vom frame error-Interrupt unterscheidet wissen nur die 
Chipdesigner.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Ich habe jetzt viel rumprobiert, bin aber immer wieder in andere 
Probleme gelaufen sodass ich nun zur ursprünglichen, Byteweisen 
Verarbeitung zurückgekehrt bin, aber mit Timeout-Checks. Dabei lasse ich 
den UART-Timeout auf 10 Symbolen und mache einen LIN-Byte Timeout mit 
Timestamp-Vergleich nach 50 ms. Das scheint nun zu klappen, jedenfalls 
bekomme ich keine Parsing-Fehler mehr, kann über UART-Receive-Block 
Grenzen (120 Bytes9 hinweg parsen:
1
I (261) uart: queue free spaces: 10
2
I (5368) LIN_SNIFFER: -----------------------------------------
3
I (5368) LIN_SNIFFER: UART_DATA event: load 120 of received 120 bytes
4
I (5369) LIN_SNIFFER: 00 55 06 00 00 FF 00 55 20 84 80 00 00 FA 00 55 | .U.....U ......U
5
I (5377) LIN_SNIFFER: 06 00 00 FF 00 55 85 40 09 B6 00 55 06 00 00 FF | .....U.@...U....
6
I (5385) LIN_SNIFFER: 00 55 50 00 2A D5 00 55 06 00 00 FF 00 55 85 40 | .UP.*..U.....U.@
7
I (5393) LIN_SNIFFER: 09 B6 00 55 06 00 00 FF 00 55 20 84 80 01 00 F9 | ...U.....U .....
8
I (5400) LIN_SNIFFER: 00 55 06 00 00 FF 00 55 85 40 08 B7 00 55 06 00 | .U.....U.@...U..
9
I (5408) LIN_SNIFFER: 00 FF 00 55 0D 98 1F C2 1E 67 00 55 06 00 00 FF | ...U.....g.U....
10
I (5416) LIN_SNIFFER: 00 55 85 40 02 BD 00 55 06 00 00 FF 00 55 20 84 | .U.@...U.....U .
11
I (5424) LIN_SNIFFER: 80 01 00 F9 00 55 06 00                         | .....U..
12
I (5431) LIN_SNIFFER: -----------------------------------------
13
I (5436) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
14
I (5442) LIN_SNIFFER: 00 00                                           | ..
15
I (5449) LIN_SNIFFER: Valid frame (Classic) - ID: 0x20, Data len: 4
16
I (5455) LIN_SNIFFER: 84 80 00 00                                     | ....
17
I (5462) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
18
I (5468) LIN_SNIFFER: 00 00                                           | ..
19
I (5474) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
20
I (5480) LIN_SNIFFER: 40 09                                           | @.
21
I (5487) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
22
I (5493) LIN_SNIFFER: 00 00                                           | ..
23
I (5500) LIN_SNIFFER: Valid frame (Classic) - ID: 0x10, Data len: 2
24
I (5506) LIN_SNIFFER: 00 2A                                           | .*
25
I (5512) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
26
I (5518) LIN_SNIFFER: 00 00                                           | ..
27
I (5525) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
28
I (5531) LIN_SNIFFER: 40 09                                           | @.
29
I (5537) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
30
I (5543) LIN_SNIFFER: 00 00                                           | ..
31
I (5550) LIN_SNIFFER: Valid frame (Classic) - ID: 0x20, Data len: 4
32
I (5556) LIN_SNIFFER: 84 80 01 00                                     | ....
33
I (5563) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
34
I (5569) LIN_SNIFFER: 00 00                                           | ..
35
I (5575) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
36
I (5581) LIN_SNIFFER: 40 08                                           | @.
37
I (5588) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
38
I (5594) LIN_SNIFFER: 00 00                                           | ..
39
I (5600) LIN_SNIFFER: Valid frame (Classic) - ID: 0x0D, Data len: 4
40
I (5606) LIN_SNIFFER: 98 1F C2 1E                                     | ....
41
I (5613) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
42
I (5619) LIN_SNIFFER: 00 00                                           | ..
43
I (5626) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
44
I (5632) LIN_SNIFFER: 40 02                                           | @.
45
I (5638) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
46
I (5644) LIN_SNIFFER: 00 00                                           | ..
47
I (5651) LIN_SNIFFER: Valid frame (Classic) - ID: 0x20, Data len: 4
48
I (5657) LIN_SNIFFER: 84 80 01 00                                     | ....
49
I (5664) LIN_SNIFFER: Time elapsed 0 ms (timeout 50 ms)
50
I (5767) LIN_SNIFFER: -----------------------------------------
51
I (5768) LIN_SNIFFER: UART_DATA event: load 105 of received 105 bytes
52
I (5768) LIN_SNIFFER: 00 FF 00 55 85 40 02 BD 00 55 06 00 00 FF 00 55 | ...U.@...U.....U
53
I (5776) LIN_SNIFFER: 8E 00 B2 0F FF 3E 00 55 06 00 00 FF 00 55 85 40 | .....>.U.....U.@
54
I (5784) LIN_SNIFFER: 02 BD 00 55 06 00 00 FF 00 55 20 84 80 01 00 F9 | ...U.....U .....
55
I (5792) LIN_SNIFFER: 00 55 06 00 00 FF 00 55 85 40 02 BD 00 55 06 00 | .U.....U.@...U..
56
I (5800) LIN_SNIFFER: 00 FF 00 55 CF 9D 7C AE 18 1F 00 55 06 00 00 FF | ...U..|....U....
57
I (5808) LIN_SNIFFER: 00 55 85 40 02 BD 00 55 06 00 00 FF 00 55 20 94 | .U.@...U.....U .
58
I (5815) LIN_SNIFFER: 80 01 00 E9 00 55 06 00 00                      | .....U...
59
I (5822) LIN_SNIFFER: -----------------------------------------
60
I (5828) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
61
I (5834) LIN_SNIFFER: 00 00                                           | ..
62
I (5841) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
63
I (5847) LIN_SNIFFER: 40 02                                           | @.
64
I (5853) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
65
I (5859) LIN_SNIFFER: 00 00                                           | ..
66
I (5866) LIN_SNIFFER: Valid frame (Classic) - ID: 0x0E, Data len: 4
67
I (5872) LIN_SNIFFER: 00 B2 0F FF                                     | ....
68
I (5879) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
69
I (5885) LIN_SNIFFER: 00 00                                           | ..
70
I (5891) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
71
I (5897) LIN_SNIFFER: 40 02                                           | @.
72
I (5904) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
73
I (5910) LIN_SNIFFER: 00 00                                           | ..
74
I (5916) LIN_SNIFFER: Valid frame (Classic) - ID: 0x20, Data len: 4
75
I (5922) LIN_SNIFFER: 84 80 01 00                                     | ....
76
I (5929) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
77
I (5935) LIN_SNIFFER: 00 00                                           | ..
78
I (5942) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
79
I (5948) LIN_SNIFFER: 40 02                                           | @.
80
I (5954) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
81
I (5960) LIN_SNIFFER: 00 00                                           | ..
82
I (5967) LIN_SNIFFER: Valid frame (Classic) - ID: 0x0F, Data len: 4
83
I (5973) LIN_SNIFFER: 9D 7C AE 18                                     | .|..
84
I (5980) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
85
I (5986) LIN_SNIFFER: 00 00                                           | ..
86
I (5992) LIN_SNIFFER: Valid frame (Classic) - ID: 0x05, Data len: 2
87
I (5998) LIN_SNIFFER: 40 02                                           | @.
88
I (6005) LIN_SNIFFER: Valid frame (Classic) - ID: 0x06, Data len: 2
89
I (6011) LIN_SNIFFER: 00 00                                           | ..
90
I (6017) LIN_SNIFFER: Valid frame (Classic) - ID: 0x20, Data len: 4
91
I (6023) LIN_SNIFFER: 94 80 01 00                                     | ....
92
I (6030) LIN_SNIFFER: Time elapsed 0 ms (timeout 50 ms)
93
I (6121) LIN_SNIFFER: -----------------------------------------
94
I (6121) LIN_SNIFFER: UART_DATA event: load 120 of received 120 bytes
95
...

Man sieht schön wie er am Ende des Buffers auf den nächsten UART_DATA 
wartet (dazwischen kommen dutzende von UART_BREAKs die ich aber einfach 
ignoriere , nur aus der ReceiveQueue verarbeiten muss man sie halt. 
Danach geht es dann im Frame weiter.

Umgesetzt habe ich hier die Idee nach dem gültigen PID auf 3 Byte zu 
prüfen, die Checksum mit Classic (LIN 1.3) und Enhanced (LIN 2.x) zu 
prüfen und wenn es ungültig ist, auf 5 Byte zu prüfen, dann auf 9 Byte 
und erst dann zu behaupten das der Frame ungültig ist und das nächste 
BREAK+SYNC zu lesen.

von Olli Z. (z80freak)


Lesenswert?

In einem Kurzen Sniff konnte ich schonmal diese IDs ermitteln und zwei 
davon sogar zuordnen:
1
0x05 => 40 02
2
0x06 => 00 00
3
0x08 => Unanswered
4
0x0D => 98 1F C2 1E | 0E 1E BF 1E | 58 1F C0 1E
5
0x0E => 00 B2 0F FF
6
0x0F => 9D 7C AE 18
7
0x10 => 00 2A | 00 4A | 00 0A
8
0x17 => B9 C7 E8 C3
9
0x19 => Unanswered
10
0x20 => 84 80 00 00 | 84 80 01 00 | 94 80 01 00 | 94 82 03 00 | 94 82 02 00
11
0x21 => Unanswered
12
0x26 => 87 C3 6C C2
13
0x27 => FF FF FF FF
14
0x2A => Unanswered
15
0x2B => Battery-Sensor, Ford-Part-Number (Multiframe, 8 Byte)
16
0x35 => Rain-Sensor, Ford-Part-Number (Multiframe, 8 Byte)

Die 0x2B ist nämlich die Teilenummer des Batteriesensors 
("AG9N-10C679-DE") als Multi-Frame (LIN TP):
1
I (6767) LIN_SNIFFER: Valid frame (Classic) - ID: 0x2B, Data len: 8
2
I (6773) LIN_SNIFFER: 02 41 47 39 4E 00 00 00                         | .AG9N...
3
...
4
I (7251) LIN_SNIFFER: Valid frame (Classic) - ID: 0x2B, Data len: 8
5
I (7257) LIN_SNIFFER: 03 31 30 43 36 37 39 00                         | .10C679.
6
...
7
I (7689) LIN_SNIFFER: Valid frame (Classic) - ID: 0x2B, Data len: 8
8
I (7695) LIN_SNIFFER: 04 44 45 00 00 00 00 00                         | .DE.....

Und die 0x35 die vom Regensensor ("BV6T-17D547-AD"):
1
I (6914) LIN_SNIFFER: Valid frame (Classic) - ID: 0x35, Data len: 8
2
I (6920) LIN_SNIFFER: 02 42 56 36 54 00 00 00                         | .BV6T...
3
...
4
I (7357) LIN_SNIFFER: Valid frame (Classic) - ID: 0x35, Data len: 8
5
I (7363) LIN_SNIFFER: 03 31 37 44 35 34 37 00                         | .17D547.
6
...
7
I (7796) LIN_SNIFFER: Valid frame (Classic) - ID: 0x35, Data len: 8
8
I (7802) LIN_SNIFFER: 04 41 44 00 00 00 00 00                         | .AD.....

Seltsam nur das ich die First-Frames nicht sehe?

Bei den anderen kann es sich um weitere IDs derselben Sensoren handeln, 
oder des Scheibenwischermotors, der auch noch an diesem LIN hängt. Ab 
diesem Punkt wäre ein Analyzer-Tool wie SavvyCAN o.ä. hilfreich, welches 
Datenänderungen in IDs sichtbar macht und tracked...

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Soul E. schrieb:
> Das kann man so machen. Du hast aber zusätzlich das frame error-Flag,
> das Du direkt nach Empfang einer 0x00 prüfen kannst.

Das Problem hierbei ist dasselbe wie beim BREAK Flag, das es eben keine 
eindeutige Zuordnung zwischen dem Flag und einem Byte im Puffer gibt, 
man kann nur raten. Denn während man auf das Flag reagiert laufen 
weitere Bytes ein, es ist also nicht automatisch das erste oder letzte 
Byte im Buffer. Daher sind Tests dieser Art meiner Meinung nach komplett 
sinnlos. Oder liege ich mit meiner Annahme falsch?

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

Olli Z. schrieb:
> Das Problem hierbei ist dasselbe wie beim BREAK Flag, das es eben keine
> eindeutige Zuordnung zwischen dem Flag und einem Byte im Puffer gibt,

Wie kommt denn das Byte in den Puffer? Üblicherweise hat eine UART ein 
Empfangsdatenregister. Da holt man das Byte ab und kopiert es irgendwo 
hin. Und bei der Gelegenheit prüft die Empfangsroutine das Flag.

von Alexander (alecxs)


Lesenswert?

Ich zitiere noch mal, auch wenn's nervt:

> Making use of the timestamp and rising / falling edge information
> recorded by espsoftwareserial, we can check whether the break field is
> received, no matter how many bits the break field contains.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Soul E. schrieb:
> Empfangsdatenregister. Da holt man das Byte ab und kopiert
Bei sehr Hardwarenaher Programmierung wäre das sicher so möglich, wenn 
man schnell genug (ISR) reagiert, ja. Im meinem Fall mit ESP-IDF 
verwende ich eine Library die mir keine einzelnen RX Bytes sondern 
Chunks (event queue) bereitstellt und aktuell wüsste ich nicht ob das 
auch anders ginge, ausser halt mit einer Software-UART wie 
beschrieben...
Evtl. könnte ich den RX FIFO FULL auf 1 Byte stellen, dann feuert der 
UART_DATA bei jedem Byte, aber ob das "gesund" ist?

Und auch wenn es so ist, bin trotzdem immer noch verwundert das ich bei 
einem
BREAK ein 0x00 im Buffer habe. Meiner Meinung nach dürfte das nicht der 
Fall sein. Ein gültiges Byte hat ein Start und ein Stopp Bit (High), 
selbst ohne Parity (8N1) dürfte die UART die ersten acht 0-Bits nicht 
als gültiges Zeichen erkennen, eben wegen dem fehlenden Stop-Bit.

In meiner UARt Initialisierung habe ich explizit
uart_set_rx_break_len(UART_NUM_1, 13);
eingestellt und der ESP32 UART soll doch break per Hardware können? Dann 
dürfte dort aber niemals ein 0x00 vor dem 0x55 im Buffer auftauchen, tut 
es aber.

: Bearbeitet durch User
von Frank K. (fchk)


Lesenswert?

Olli Z. schrieb:

> Und auch wenn es so ist, bin trotzdem immer noch verwundert das ich bei
> einem
> BREAK ein 0x00 im Buffer habe. Meiner Meinung nach dürfte das nicht der
> Fall sein. Ein gültiges Byte hat ein Start und ein Stopp Bit (High),
> selbst ohne Parity (8N1) dürfte die UART die ersten acht 0-Bits nicht
> als gültiges Zeichen erkennen, eben wegen dem fehlenden Stop-Bit.

Das ist normal so, wenn Du weißt, wie ein UART hardwaretechnisch 
aufgebaut ist. Da werden eben laufend 0-Bits in ein Schieberegister 
hineingeschoben, nachdem das Startbit erkannt worden ist, und die 
Statemachine erkennt eben, dass nach dem 8. Bit kein Stopbit da ist. Zu 
diesem Zeitpunkt ist also das 0-Byte im Empfangsregister bereits drin, 
und dazu kommt noch ein Errorflag. So machen es alle. Dieses Verhalten 
siehe ich hier auch bei dsPIC33 als LIN-Slaves.

Dein eigentliches Problem ist, dass Du eine Hardware bzw 
Hardware/Software-Plattform für die Realisierung gewählt hast, die für 
die Aufgabe nicht wirklich geeignet ist. Das merkst Du jetzt gerade.

fchk

von Olli Z. (z80freak)


Lesenswert?

Ich verstehe schon was Du sagst, aber arbeitet der UART nicht so:
1
RX-Pin
2
3
Bit-Stream
4
5
BREAK-Detektor   ← hier wird LIN-Break erkannt
6
7
Startbit-Detektor
8
9
Byte-Assembler (8N1)
10
11
RX FIFO

Demnach dürfte das in Hardware eben kein 0x00 im Buffer erzeugen. Ich 
vermute daher das dies durch den Treiber (ESP-IDF Library 5.5) erfolgt.

Ob nun der ESP32 der "Richtige für den Job" ist oder nicht... kann sein. 
Ich könnte auch STM32, Atmega, PICxxx, oder sonst was nehmen, aber ich 
bin sicher da gibt es auch so div. Tücken. Ich glaube eigentlich nicht 
das der ESP32 das nicht kann, eher das ich es falsch mache. 
Möglicherweise muss ich tiefer gehen als die Treiber-Library...

Ein Problem der Library scheint eben auch zu sein das ein UART_BREAK 
Event nicht zwingend direkt vor einem UART_DATA Event ausgelöst wird, 
bzw. diesen zur Folge hat.

Meine aktuelle Lösung der heuristischen Frame Erkennung funktioniert ja, 
aber ich finde sie eben nicht optimal. Ich bin da sicher sehr 
theoretisch unterwegs, aber wenn ich eine Rundum-Sorglos-Lösung gewollt 
hätte, hätte ich mir eine gekauft. Genau das will ich ja nicht um eben 
diese fiesen kleinen Details zu finden und hoffentlich auch zu lösen. 
Das mag nicht jeder verstehen, ok, aber so bin ich halt :-)

Nachtrag: Die ESP‑IDF API‑Dokumentation spezifiziert ja UART‑Ereignisse 
wie:
UART_DATA  Daten sind empfangbar
UART_BREAK  Eine Break‑Bedingung wurde erkannt
UART_FRAME_ERR  Frame‑Error (z. B. Stopbit fehlt)
UART_PARITY_ERR  Paritätsfehler erkannt
UART_FIFO_OVF  FIFO‑Overflow

Damit ist auch klar das sie sehr wohl zwischen einem Break und einem 
Frame-Error unterscheiden kann, auf Hardware-Ebene.

: Bearbeitet durch User
von Frank K. (fchk)


Lesenswert?

Olli Z. schrieb:
> Ich verstehe schon was Du sagst, aber arbeitet der UART nicht so:
>
1
> RX-Pin
2
>  ↓
3
> Bit-Stream
4
>  ↓
5
> BREAK-Detektor   ← hier wird LIN-Break erkannt
6
>  ↓
7
> Startbit-Detektor
8
>  ↓
9
> Byte-Assembler (8N1)
10
>  ↓
11
> RX FIFO
12
>

So sequentiell, wie Du es darstellst, ist das falsch. Der "Break 
Detektor" müsste die letzten 13 Bits betrachten, die alle 0 sein müssen. 
Deine Datstellung impliziert, dass es da ein 13 Bit breites 
Schieberegister gibt, und das tut es nicht. Außerdem muss zuerst das 
0-Startbit detektiert werden, weil damit auch die Synchonisation auf den 
Bittakt erfolgt.

Die Realität sieht anders aus.

fchk

von Olli Z. (z80freak)


Lesenswert?

Aber warum kann man dann beim ESP32 UART die Anzahl der BREAK-Bits 
einstellen wenn er diese nicht zählen könnte und immer nur 8 Bit zur 
Verfügung stehen?

Das Start-Bit kommt ja in jedem Fall, durch die fallende Flanke von 1 
(Ruhepegel) nach 0 (Dominanter Pegel) beim BREAK.

: Bearbeitet durch User
von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Ich habe meinen Code jetzt erstmal um einen Frame-Export im CANDUMP 
Format ergänzt und aus dem Output SavvyCAN gefüttert.
1
# LIN Bus Sniffer - candump format
2
# Connect with SavvyCAN or use: cat > logfile.log
3
# Statistics every 30s in ESP-IDF monitor
4
(29.677899) lin0 006#0000 # OK Classic
5
(29.678809) lin0 020#84800000 # OK Classic
6
(30.430514) lin0 006#0000 # OK Classic
7
(30.431737) lin0 020#94820300 # OK Classic
8
(30.432471) lin0 005#4002 # OK Classic
9
(30.432752) lin0 010#004A # OK Classic
10
(30.433023) lin0 02B#024147394E000000 # OK Classic
11
(30.433288) lin0 02A# # UNANSWERED
12
(30.433441) lin0 006#0000 # OK Classic
13
(30.433579) lin0 017#BCC7E8C3 # OK Classic
14
(30.433720) lin0 020#94820300 # OK Classic
15
(30.433968) lin0 005#4002 # OK Classic
16
(30.434209) lin0 026#87C36CC2 # OK Classic
17
(30.434374) lin0 027#FFFFFFFF # OK Classic
18
(30.434529) lin0 006#0000 # OK Classic
19
(30.434662) lin0 035#0242563654000000 # OK Classic
20
(30.434830) lin0 020#94820300 # OK Classic
21
(30.507039) lin0 006#0000 # OK Classic
22
(30.507530) lin0 020#94820300 # OK Classic
23
(30.507681) lin0 005#4002 # OK Classic
24
(30.507816) lin0 006#0000 # OK Classic
25
(30.507967) lin0 019# # UNANSWERED
26
(30.527674) lin0 021# # UNANSWERED
27
(30.756922) lin0 020#94820300 # OK Classic
28
(30.757327) lin0 005#4002 # OK Classic
29
(30.757473) lin0 006#0000 # OK Classic
30
(30.757614) lin0 020#94820300 # OK Classic
31
...
In gewisser Weise sind CAN und LIN auf Datenebene kompatibel, das 
Programm merkt da kaum einen Unterschied und bietet mir all die schönen 
Analyse- und Statistik-Funktionen nun auch für LIN an. Das kann sich so 
schon sehen lassen.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Um die Inhalte des LIN-Busses zu entschlüsseln mache ich einen 
gesonderten Thread auf. Hier sollte es ja nur darum gehen die Sniffer 
Hard/Software ans laufen zu bekommen. Damit bin ich noch nicht 100% 
durch, aber habe zumindest eine gute Funktion erreicht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.