SAE J2534 (PassThru) für die Industrie

Herstellerunabhängige Schnittstelle für PC-CAN-Adapter

1. Tausend Apps mit einem CAN Interface

Für jede Hardware gibt es eine andere Diagnosesoftware und für jede Diagnosesoftware einen anderen CAN-Adapter. Das Dilemma: Jeder Hersteller von CAN PC Interfaces hat seine eigene API, eventuell für CAN-FD sogar noch eine weitere. SAE J2543 schafft hier Abhilfe. Die Norm definiert eine einheitliche, herstellerunabhängige API für CAN und CAN-FD, bedauerlicherweise nur für das Betriebssystem Microsoft Windows.

SAE J2534 Übersicht

Immer mehr Steuergeräte (ECUs) und damit verbunden immer mehr Bussysteme kommen im Fahrzeug zum Einsatz. Damit Werkstätten nicht für jeden Fahrzeughersteller und jede Diagnosesoftware eine eigene Testhardware für den PC kaufen müssen, wurde von SAE die Norm J2534 entwickelt. Eine DLL stellt eine genormte API-Schnittstelle bereit, die alle im Fahrzeug üblichen Bussysteme abdeckt. Die API wird umgangssprachlich auch als PassThru API und die Hardware, die als Schnittstellen-Interface dient, als PassThru Device bezeichnet. Die Schnittstelle zum PC ist in der Norm nicht festgelegt, in der Regel ist es USB, es kann aber auch Ethernet, WLAN, Bluetooth usw. sein. Das verwendete Übertragungsprotokoll zwischen PC und PassThru Device ist nicht festgelegt. Der Begriff PassThru Device bedeutet nur, dass es für die Hardware eine PassThru-Treiber-DLL gibt.

Die Anzahl der Diagnose-Schnittstellen ist in der Norm variabel, es kann auch nur CAN/CAN-FD sein. In diesem Artikel werden nur CAN, CAN-FD behandelt und ISO-TP (ISO 15765) wird kurz angeschnitten. Die Norm spezifiziert auch „Single Wire CAN“ und „Fault-tolerant CAN“ (Low-Speed CAN, ISO 11898-3), beide finden in der Industrie keine Anwendung und werden hier nicht behandelt.

2. Installiertes PassThru Device auf dem PC

Beispiel: PassThru-Implementierung mit einem Tiny-CAN Interface. Die grün strichlierte Linie in der Grafik zeigt den Übergang zwischen herstellerspezifischer API zur PassThru API.

PassThru-Treiber, die auf dem PC installiert werden, tragen sich unter dem Pfad „HKEY_LOCAL_MACHINE\SOFTWARE\PassThruSupport.04.04“ in die Windows-Registrierungsdatenbank ein. Die Bezeichnung „04.04“ steht für die API-Version. Die Version „04.04“ ist im Augenblick noch die Standard-Version, obwohl SAE bereits die erweiterte Version „5.00“ definiert hat. Beide Versionen unterstützen CAN-FD.

Drei Einträge sind für die Nutzung der API von entscheidender Bedeutung:

CAN* Gibt die Anzahl der CAN-Schnittstellen an, welche das PC Interface hat. Ist dieser Eintrag nicht vorhanden oder 0, unterstützt die Hardware die RAW-CAN-Nachrichtenübertragung nicht.
FunctionLibrary Gibt die PassThru-API-Treiber-DLL samt Pfad an.Name und Pfad sind in der Norm nicht festgelegt. Die DLL muss demnach von der Applikation dynamisch geladen werden.
ConfigApplication Konfigurations-Utility, Einstellungen für Log-File usw. Das Programm ist herstellerspezifisch, die Norm macht hier keine Vorgaben.

* Dieser Eintrag ist eigentlich veraltet und nur noch aus Kompatibilitätsgründen vorhanden. Informationen zum PassThru Device und den vorhandenen Schnittstellen sollten über „PassThruIoctl – GET_DEVICE_INFO“ und „PassThruIoctl –GET_PROTOCOL_INFO“ abgefragt werden.

3. Eigene App mit PassThru entwickeln

Wie funktioniert nun ein Programm, welches PassThru benutzt?
  1. Die Software analysiert die Einträge in der Windows-Registrierungsdatenbank und stellt daraus eine Auswahl der einzelnen PassThru Devices zur Verfügung.
  2. Die Treiber-DLL wird dynamisch geladen.
  3. Wenn verfügbar kann die Software die „Discovery“-Mechanismen benutzen, um mehr über die Fähigkeiten der verbundenen Hardware zu erfahren, z. B. ob die Hardware CAN-FD usw. unterstützt.
(Die Einträge in der Windows-Registrierungsdatenbank beschreiben nicht unbedingt die Fähigkeiten der aktuell verbundenen Hardware.)

Beispiel: Device-Auswahl in CANcool

Die Funktionen der J2534 API DLL „FunctionLibrary“ im Überblick:

PassThruOpen Öffnet die PC-Schnittstelle zum PassThru Device.
PassThruClose Schließt die PC-Schnittstelle zum PassThru Device.
PassThruConnect Öffnet einen Protokollkanal.
PassThruDisconnect Schließt einen Protokollkanal.
PassThruReadMsgs Lesen von Nachrichten
PassThruWriteMsgs Schreiben von Nachrichten
PassThruStartPeriodicMsg Startet das Senden einer periodischen Nachricht mit angegebener Intervallzeit.
PassThruStopPeriodicMsg Stoppt den periodischen Nachrichtenversand der angegebenen Nachricht.
PassThruStartMsgFilter Setzt einen Filter für empfangene Nachrichten.
PassThruStopMsgFilter Löscht den angegebenen Nachrichtenfilter.
PassThruSetProgrammingVoltage Legt eine Spannung an einen spezifizierten Pin an.
PassThruReadVersion Gibt die DLL- und Firmware-Version aus.
PassThruGetLastError Gibt einen Fehlertext zu dem letzten aufgetretenen Fehler aus.
PassThruIoctl Spezielle I/O-Kontrollfunktionen: Konfigurationsparameter lesen/schreiben, FIFOs, Filter, periodische Nachrichten löschen usw.

PassThru Hardware öffnen, Protokollkanal öffnen, was heißt das? Wie oben bereits beschrieben ist unsere PassThru Hardware ein USB-zu-CAN-Adapter. Der CAN-Bus kann als RAW-CAN-Protokoll (CAN) oder als ISO-TP-Protokoll (ISO 15765) geöffnet werden, auch beide Protokolle gleichzeitig sind möglich. Wenn wir nun ein Protokoll öffnen wollen, muss zuerst das PassThru Device selbst geöffnet werden.

Im industriellen Umfeld wird nur RAW-CAN benötigt. ISO-TP ist sehr automotive-spezifisch, OBD und UDS bauen darauf auf. ISO-TP ermöglicht die segmentierte Datenübertragung, die maximale Nachrichtenlänge ist 4095 Byte.

Beispiel 1: CAN-Schnittstelle öffnen und schließen

static HINSTANCE DriverHandle = NULL; /***************************************************************/ /* Treiber-DLL entladen */ /***************************************************************/ static void UnloadJ2534Driver(void) { if (DriverHandle) FreeLibrary(DriverHandle); DriverHandle = NULL; } /***************************************************************/ /* Treiber-DLL laden */ /***************************************************************/ static int32_t LoadJ2534Driver(const char *file_name) { if (!(DriverHandle = LoadLibraryA(file_name))) return(-1); PassThruOpen = (TpassThruOpen)GetProcAddress(DriverHandle, (LPCSTR)"PassThruOpen")); /* ... Wiederholen für alle Funktionen ... */ return(0); } static char FirmwareVersion[80]; static char DllVersion[80]; static char ApiVersion[80]; int main(int argc, char **argv) { long err; unsigned long dev_id, ch_id; /***** 1. PassThru-Treiber-DLL laden Funktion: int32_t LoadJ2534Driver(const char *file_name) Parameter: "file_name": Spezifiziert den zu ladenden PassThru-Treiber. Der Dateiname inklusive Pfad wird wie im Kapitel "2. Installiertes PassThru Device auf dem PC" aus der Windows-Registrierungsdatenbank ausgelesen. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ if ((err = LoadJ2534Driver(driver_file))) { printf("LoadJ2534Driver \"%s\" Error: %ld\n", driver_file, err); goto ende; } printf("PassThru Driver \"%s\" load successful\n", driver_file); /***** 2. PassThru Device öffnen Funktion: long __stdcall PassThruOpen(void *pName, unsigned long *pDeviceID) Parameter: "pName": Name des zu öffnenden Devices. Ist der Parameter NULL, wird ein beliebiges Device geöffnet "pDeviceID": Wird von "PassThruOpen" gesetzt, spezifiziert das geöffnete Device mit einen eindeutigen Schlüssel. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ if ((err = PassThruOpen(NULL, &dev_id))) { printf("PassThruOpen Error: %ld\n", err); goto ende; } printf("PassThruOpen Ok\n"); /***** 3. Versions-Informationen auslesen (optional) Funktion: long __stdcall PassThruReadVersion(unsigned long DeviceID, char *pFirmwareVersion, char *pDllVersion, char *pApiVersion); Parameter: "DeviceID" : Eindeutige Device ID, wird von "PassThruOpen" zurückgegeben. "pFirmwareVersion" : Firmware-Version als String. "pDllVersion" : DLL-Version als String. "pApiVersion" : API-Version als String. Die Parameter pFirmwareVersion, pDllVersion, pApiVersion werden von PassThruReadVersion geschrieben, die String-Puffer müssen jeweils 80 Byte groß sein. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ if ((err = PassThruReadVersion(dev_id, FirmwareVersion, DllVersion, ApiVersion))) { printf("PassThruReadVersion Error: %ld\n", err); goto ende; } printf("PassThruReadVersion Ok\n"); printf(" Firmware Version : %s\n", FirmwareVersion); printf(" Dll Version : %s\n", DllVersion); printf(" API Version : %s\n", ApiVersion); /***** 4. Protokollkanal öffnen Funktion: long __stdcall PassThruConnect(unsigned long DeviceID, unsigned long ProtocolID, unsigned long Flags, unsigned long Baudrate, unsigned long *pChannelID); Parameter: "DeviceID": Eindeutige Device ID, wird von "PassThruOpen" zurückgegeben. "ProtocolID": Spezifiziert das zu benutzende Protokoll, in umserem Fall CAN "Flags": 0 = Nur Standard CAN Frames (11 Bit ID) empfangen CAN_29BIT_ID = Nur Extended CAN Frames (29 Bit ID) empfangen CAN_ID_BOTH = Standard & Extended CAN Frames empfangen "Baudrate": CAN-Baudrate in Bit/s, 125000 = 125kBit/s "pChannelID": Wird von "PassThruConnect" gesetzt, spezifiziert die Verbindung zum Protokoll mit einen eindeutigen Schlüssel. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ if ((err = PassThruConnect(dev_id, CAN, CAN_ID_BOTH, 125000, &ch_id))) { printf("PassThruConnect Error: %ld\n", err); goto ende; } printf("PassThruConnect Ok\n"); /******************************************************************************/ /* CAN-Nachrichten lesen, schreiben, .... */ /******************************************************************************/ // Eigenen Code einfügen .... ende: /***** 5. Protokollkanal schließen Funktion: long __stdcall PassThruDisconnect(unsigned long ChannelID) Parameter: "ChannelID": Channel ID des zu schließenden Kanals, wird von "PassThruConnect" zurückgegeben. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ PassThruDisconnect(ch_id); /***** 6. PassThru Device schließen Funktion: long __stdcall PassThruClose(unsigned long DeviceID) Parameter: "DeviceID": Device ID des zu schließenden PassThru Devices, wird von "PassThruOpen" zurückgegeben. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ PassThruClose(dev_id); /***** 7. PassThru-Treiber entladen Funktion: void UnloadJ2534Driver(void) Parameter: keine */ UnloadJ2534Driver(); printf("\nEnde.\n"); return(0); }

Da der Pfad der PassThru-Treiber-DLL über die Windows-Registrierungsdatenbank ermittelt wird, muss die Treiber-DLL dynamisch mit „LoadLibrary“ geladen werden, die Funktionen werden mit „GetProcAddress“ ermittelt. Die „API-Funktionen“ sind „C Standard Calls“.

Die PassThru-Nachricht

Die Struktur einer PassThru-Nachricht ist universell für alle spezifizierten Protokolle, weshalb das Datenfeld 4128 Byte groß ist. Die „PASSTHRU_MSG“-Struktur:

#pragma pack(push,1) typedef struct _PASSTHRU_MSG { unsigned long ProtocolID; // Protokoll Type: CAN, CAN_FD_PS, ... unsigned long RxStatus; // Flags, die den Type einer empfangenen Nachricht definieren. // TX_MSG_TYPE = Echo einer Tx-Nachricht // CAN_29BIT_ID = 29 Bit ID // CAN_FD_FORMAT = CAN FD Nachricht // CAN_FD_BRS = CAN FD (BRS) Baud Rate Switch aktiv // CAN_FD_ESI = CAN FD (EIS) Error State Indicator unsigned long TxFlags; // Flags, die den Type einer zu sendenden Nachricht festlegen. // CAN_29BIT_ID = 29 Bit ID // CAN_FD_FORMAT = CAN FD Nachricht // CAN_FD_BRS = CAN FD (BRS) Baud Rate Switch aktiv unsigned long Timestamp; // Timestamp in µS unsigned long DataSize; // Datengröße in Byte inklusive ID Länge (4 Byte) unsigned long ExtraDataIndex; // Wird nicht benutzt, muss ExtraDataIndex = DataSize // gesetzt werden. unsigned char Data[4128]; // Die ersten 4 Byte beinhalten immer den CAN Identifier. // (Der Identifier ist immer 4 Byte, egal ob Standard oder // Extended Frame Format.) CAN-Daten: 0 - 8 Byte für // Classical-CAN und 0 - 64 Byte für CAN-FD. // Die Nutzdatenlänge wird "DataSize" - 4 ermittelt. } PASSTHRU_MSG; #pragma pack(pop)

In den Kommentarzeilen (grün) wird die Aufschlüsselung der „PASSTHRU_MSG“ in eine CAN-Nachricht beschrieben.

Beispiel 2: CAN-Nachricht senden

static const char TestData[] = "HALLO"; #define SetUInt32ToData(p, d) do { \ (*p++) = (uint8_t)((d) >> 24); \ (*p++) = (uint8_t)((d) >> 16); \ (*p++) = (uint8_t)((d) >> 8); \ (*p++) = (uint8_t)(d); \ } while(0) static void TxMessage(unsigned long ch_id) { long err; unsigned long num_msgs; PASSTHRU_MSG msg; uint8_t *p; /***** 1. Zu sendende Nachricht erzeugen ID = 0x123, DLC = 6, Data = "HALLO\0" */ num_msgs = 1; // Anzahl zu schreibender Nachrichten = 1 msg.ProtocolID = CAN; // Protokoll ist CAN //msg.RxStatus // wird nicht verwendet msg.TxFlags = 0; // Standard CAN Frame //msg.Timestamp // wird nicht verwendet msg.DataSize = sizeof(TestData) + 4; // Nachrichtenlänge + 4 (ID) p = msg.Data; SetUInt32ToData(p, 0x123); // Nachrichten ID = 0x123 memcpy(p, TestData, sizeof(TestData)); /***** 2. CAN-Nachricht absenden Funktion: long __stdcall PassThruWriteMsgs(unsigned long ChannelID, PASSTHRU_MSG *pMsg, unsigned long *pNumMsgs, unsigned long Timeout) Parameter: "ChannelID": Eindeutige Kanal ID, wird von "PassThruConnect" zurückgegeben. "pMsg": Pointer auf die zu sendenden Nachrichten. "pNumMsgs": Anzahl der Nachrichten, die geschrieben werden sollen, wird bei Rücksprung auf die Anzahl der tatsächlich geschriebenen Nachrichten gesetzt. "Timeout": Wartezeit in ms, bis die Nachrichten gesendet werden. 0 = So viele Nachrichten wie möglich in das Sende-FIFO schreiben und sofort zurückkehren. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ if ((err = PassThruWriteMsgs(ch_id, &msg, &num_msgs, 0))) printf("PassThruWriteMsgs Error: %ld\n", err); else printf("PassThruWriteMsgs Ok, messages write: %lu\n", num_msgs); }

Beispiel 3: CAN-Nachrichten empfangen

__attribute__( ( always_inline ) ) static inline uint32_t GetUint32FromData(uint8_t **data) { uint8_t *d; uint8_t l, m1, m2, h; d = *data; h = *d++; m2 = *d++; m1 = *d++; l = *d++; *data = d; return(l | (m1 << 8) | (m2 << 16) | (h << 24)); } static void RxMessages(unsigned long ch_id) { long err; unsigned long num_msgs, msg_len, i, id; PASSTHRU_MSG msg; uint8_t *p; printf("\nRead Messages:\n"); while (!kbhit()) { /***** CAN-Nachricht empfangen Funktion: long __stdcall PassThruReadMsgs(unsigned long ChannelID, PASSTHRU_MSG *pMsg, unsigned long *pNumMsgs, unsigned long Timeout); Parameter: "ChannelID": Eindeutige Kanal ID, wird von "PassThruConnect" zurückgegeben. "pMsg": Pointer auf Empfangspuffer. "pNumMsgs": Größe des Empfangspuffers, wird bei Rücksprung auf die Anzahl der tatsächlich empfangenen Nachrichten gesetzt. "Timeout": Wartezeit in ms, die auf empfangene Nachrichten gewartet wird. Rückgabewert: Bei fehlerfreier Ausführung gibt die Funktion 0, andernfalls einen Fehlercode zurück. */ num_msgs = 1; err = PassThruReadMsgs(ch_id, &msg, &num_msgs, 10); // Timeout = 10ms if ((err) && (err != ERR_TIMEOUT) && (err != ERR_BUFFER_EMPTY)) { printf("PassThruReadMsgs Error: %ld\n", err); break; } else if (num_msgs) { p = msg.Data; id = GetUint32FromData(&p); msg_len = msg.DataSize - 4; printf("id:%03lX dlc:%01lu data:", id, msg_len); if (msg_len) { for (i = 0; i < msg_len; i++) printf("%02X ", *p++); } else printf(" keine"); printf("\n\r"); } } }

4. Das „Schweizer Taschenmesser“ für PassThru

Unser kostenloses Programm „J2534 Scan Utility“ durchsucht die Windows-Registrierungsdatenbank nach installierten J2534-Treibern und listet diese auf. Aber damit nicht genug. Die Treiber-DLL wird geladen und geprüft. Das Interface kann zudem geöffnet werden und es lassen sich CAN-Nachrichten versenden und empfangen.

5. CAN-Bus Analyse- und Simulationssoftware mit PassThru-Unterstützung

Open Source CAN-Bus Analyse- und Simulationssoftware für „Classical“-CAN und CAN-FD: Neben PassThru Devices werden auch SLCAN- und Tiny-CAN-Module unterstützt.

6. Ein Wrapper zu PassThru

MHS-Elektronik bietet seinen Kunden eine Wrapper DLL von der Tiny-CAN API zur PassThru API an. Unser Open Source Tool CANcool greift auf diesen Wrapper zurück.

7. Links, Internet

Die Norm
https://www.sae.org
Dort können die SAE J2534-x-Normen bezogen werden.

QT-Framework
https://www.qt.io
Anwender, die das QT-Framework benutzen, können sich freuen. QT unterstützt die PassThru API in der Version 04.04.

J2534 auf GitHub
https://github.com/search?q=J2534
61 Ergebnisse sind bei der Suche von J2534 auf GitHub zu finden, darunter sehr interessante Implementierungen für C# und Python. Für „C“ und „C++“ ist auch einiges zu finden, unter anderem ein Bastelprojekt für „Arduino“.

Tiny-CAN API to PassThru API
https://www.mhs-elektronik.de
Anwendern, die bereits die Tiny-CAN API benutzen, bietet sich die Tiny-CAN-zu-PassThru-Wrapper-DLL an.

CANcool
https://github.com/MHS-Elektronik/CANcool

J2534 Scan Utility
https://mhs-elektronik.de/index.php?module=download

Bei der Suche im Internet konnte keine einzige Software für CANopen gefunden werden, die PassThru unterstützt, weder kommerziell noch Open Source.

8. PassThru vs. herstellerspezifischer API

Es gibt nur wenig, das die Norm nicht abdeckt, gerade mit der API-Version 5.00 hat SAE noch einmal deutlich nachgelegt, so wurden z. B. Fehler-Nachrichten ergänzt. Nur wenige sehr spezielle Features, z. B. „Retransmission Disable“, die Abfrage des Bus-Status („Error Warning“, „Error Passiv“, …), funktionieren nicht. Das größte Manko von PassThru ist, dass keine RTR-Frames unterstützt werden, in den meisten Protokollen und Anwendungen werden RTR-Frames jedoch nicht verwendet. Beinahe jeder namhafte Hersteller von CAN-PC-Schnittstellenadaptern unterstützt inzwischen PassThru. Wenn bei der Entwicklung von Software die Kundenzufriedenheit an oberster Stelle steht, ist die Implementierung von PassThru wohl alternativlos. Sie müssen nur darauf achten, dass Sie, wenn Sie die API-Version 5.00 benutzen möchten, zur API-Version 04.04 abwärtskompatibel sind, da die meisten Hersteller die API-Version 5.00 noch nicht unterstützen.

9. ISO vs. SAE

Eine Alternative zu SAE J2534 ist ISO 22900-2 (D-PDU-API). Die Bezeichnung „-2“ ist hier besonders wichtig, sie gibt nicht die Version der Norm, sondern den Teil der Norm an. Die D-PDU-API ist sehr aufwändig, andere würden sagen, sehr mächtig. So werden hier z. B. die einzelnen Devices über XML-Dateien beschrieben.

Pro D-PDU-API gegenüber SAE J2534:

Contra D-PDU-API:

Fazit: ISO 22900-2 ist für den Einsatz in der Industrie weniger geeignet.