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
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.
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
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
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
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.
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.
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
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
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 :-)
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
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.
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.
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
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.
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
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.
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
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
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
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.
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; |
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
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
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
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?
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.
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
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.
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
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
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...
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.
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.
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
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
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.
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
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
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.
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
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
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
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
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
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
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.



