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?
- Die Software analysiert die Einträge in der Windows-Registrierungsdatenbank und stellt daraus eine Auswahl der einzelnen PassThru Devices zur Verfügung.
- Die Treiber-DLL wird dynamisch geladen.
- 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.

- Unterstützte Sprachen: C/C++, C-Sharp, Visual-VB, Python, Pascal (Delphi), LabView
- Multiple SAE J2534 Driver Support
- Unterstützung der API-Version 04.04 und 5.00 (in Vorbereitung)
- Nachrichtenfilter und periodische Nachrichten werden unterstützt
- CAN-FD
- Thread Safe
- Viele Beispiele in allen Programmiersprachen und Dokumentation
- Support und Pflege der API
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:
- Plattformunabhängig, benutzt nicht die Windows-Registrierungsdatenbank.
- Detaillierte Beschreibung der Hardware und Anschlusskabel.
Contra D-PDU-API:
- Die Norm ist viel stärker auf den Automotive-Bereich zugeschnitten als SAE J2534.
- Benutzt für Ereignishandling Callback-Funktionen (Callback-Funktionen sind zur professionellen Interprozesskommunikation ungeeignet).
- Weniger verbreitet als SAE J2534.
Fazit: ISO 22900-2 ist für den Einsatz in der Industrie weniger geeignet.