Hallo, ich starte noch ein neues Lernprojekt, diesmal zum Thema “CAN Bus”. Mein Ziel ist es einen Adapter zu bauen der über die OBD-Schnittstelle meines Autos Kontakt mit dem CAN-Bus (mein Fahrzeug hat 3 davon) aufnimmt und die Daten per Funk an einen PC senden kann um sie dort mittels Software auszuwerten.
Wie immer einen herzlichen Dank an das Forum, die Betreiber und Super-Nette Community hier dafür das ich mein Projekt hier ausbreiten darf. Den Sourcecode lege ich natürlich öffentlich zugänglich in meinem Github Repository ab GitHub - igittigitt/yesweCAN
Wie immer zunächst ein paar fachliche Anforderungen
- OBD-Adapter
- Als Basis-Chip soll ein ESP32 sein (mein aktueller Liebling, nur deswegen)
- Der Adapter soll wenigstens über 2 CAN-Bus Schnittstellen verfügen, bzw. zwischen diesen umschalten können (HS-CAN, MS-CAN, ggf. MM-CAN)
- Der Adapter soll im Fahrzeug verbleiben können, auch während der Fahrt, also über eigene Schutz- und Stromsparfunktionen verfügen (Unterspannungsschutz, Entladeschutz der Batterie, Load-Dump Transienten-Schutz beim Motorstart, Fremdstart, etc.)
- Schreibzugriffe in den CAN-Bus müssen von einer externen Steuersoftware “genehmigt” werden, ansonsten nur passiver Sniffer-Mode
- Der Adapter soll Daten auf eine SD-Card loggen können um Langzeitaufzeichnungen zu ermöglichen. Idealerweise eine Möglichkeit diese Daten auch per WLAN zu übertragen um nicht jedesmal die SD entnehmen zu müssen.
- Übertragung der Daten per Funk an einen Remote-PC/Tablet zur Visualisierung und Auswertung in einem Standard-Format (z.B. GVRET oder Lawicel)
- Eine OTA Update-Möglichkeit um nicht jedesmal den Adapter an einem PC anschließen zu müssen wenn ich meine Software optimieren möchte.
- Software
- Als Auswertungssoftware favorisiere ich “Savvycan”
Auswahl des ESP32
Über den ESP Product Selector habe ich geschaut welche ESP Varianten wenigstens einen, besser 2 CAN-Controller besitzen. CAN heißt übrigens im ESP-Terminologie “TWAI” (Two-Wire Automotive Interface). Da viel meine Wahl schnell auf den ESP32-C6, dieser hat laut Datenblatt “Two TWAI® controllers, compatible with ISO 11898-1 (CAN Specification 2.0)”. Der ist perfekt für meine Zwecke und ich habe bereits einige von diesen ESPs auf einem fertigen “Super-Mini” Board hier, die auch noch sehr günstig sind. Der C6 bietet auch noch genügend andere Schnittstellen wie SPI für eine SD-Card, hat WLAN und Bluetooth (BLE5) und auch etwas RAM (512 KB) für die Zwischenverarbeitung. Darüber hinaus bleiben noch ein paar Schnittstellen für eine Bordspannungsüberwachung per ADC und GPIOs für Taster/LEDs zur Steuerung am Adapter selbst.
Gehäuse
Zunächst baue ich mal einen Prototypen auf, dann schaue ich mal wie ich das mit dem Gehäuse regele. Ich habe noch ein paar ELM-Adapter Gehäuse, die haben aber das Problem das sie gerade eingesteckt werden und somit rausstehen. Ich denke da eher an eine geschlossene Box mit einem Kabel und einem OBD-Stecker, idealerweise einem gewinkelten.
Hier mal die wichtigsten Signale am OBD-Port (für mein Fahrzeug):
In meinem Fall ist Pin 16 DAUERPLUS, direkt mit der Batterie verbunden. Das kann in anderen Fahrzeugen anders geregelt sein, möglicherweise wirklich ein Zünd-Plus. Damit könnte man dann im Stand keine Frames mehr sniffen und bräuchte eine alternative Stromversorgung über einen LiPo-Akku damit es noch eine Weile klappt.
Stromversorgung
Die ist im KFZ über OBD üblicherweise 12V (naja, irgendwas um die 12V, also 6V bis 15V) und oftmals alles andere als sauber. Für den ESP32 brauche ich 3,3V bzw. 5V wenn ich den Onboard-LDO nutze. Für den Rest der Schaltung könnte ich ggf. noch 5V benötigen (für SD-Card, CAN-Transceiver, etc.). Im KFZ sollte zum einen alles möglichst wenig Strom verbrauchen, vor allem aber in einen Sleep gehen wenn es nicht arbeiten soll sonst ist die Batterie bald am Ende. Unter 6V steigen die meisten KFZ-Module mit Brown-Out eh aus, also benötige ich nur einen Buck-Converter, aber Automotive-Tauglich auf 5V, welcher auch einen Sleep-Mode hat, bzw. in einen solchen geschaltet werden kann, bzw. einen extrem geringen Strombedarf im Leerlauf (Quiescent Current).
Die Steuerung des Sleep muss im ESP erfolgen und der sollte auch die Peripherie an/ausschalten. Das ist ein bewährtes Power-Management-Pattern bei Automotive Modulen das die SBC die anderen Baugruppen steuert.
Für die Dimensionierung eines Spannungsreglers ist der Gesamtstromverbrauch entscheidend.
- EPS32 => Im Aktiv-Modus ~ 60 mA, bei WiFi ~ 200 mA (Peak bis 400 mA)
- CAN-Transceiver => Im Aktiv-Modus ~ 50 mA
- SD-Card => ca. 50 mA (Peak bis 200 mA)
- Hühnerfutter (LEDs) => 25 mA
Der Regler sollte also ~ 250 mA dauerhaft und 700 mA im Peak verkraften und dabei nicht all zu heiß werden (niedriger V-Drop). Da denke ich an einen 5V/1A Regler. Um sich gegen die Bordspannungsspitzen zu behaupten und eine gewisse EMV zu gewährleisten sollte man eine TVS-Diode, einen Eingangs LC-Filter haben. Auch ein Verpolschutz mittels Schottky-Diode ist angebracht.
Für so etwas sollte man eigentlich Automotive-Regler einsetzen wie aber dafür müsste man ein eigenes Board routen und dazu habe ich nicht die Lust. Daher musste ich auf etwas zurückgreifen was es fertig gibt und da bin ich bei diesem hier gelandet:
Für knapp 6€ ist das ein guter Regler für KFZ-Umgebungen, da er einen hohen Eingangsspannungsbereich (3,8V bis 32V) hat, genügend Strom liefern kann (bis zu 3,5 A bei 5V) bei gleichzeitig hohem Wirkungsgrad. Zudem hat er einen extrem niedrigen Ruhestrom von nur 22µA, erzeugt selbst wenig EMI, hat div. Schutzschaltungen integriert wie:
Unterspannungsabschaltung, Spitzenstrombegrenzung, Thermische Abschaltung und verfügt über einen Enable-Pin für eine echte Vollabschaltung.
Dazu dann von +12V kommend erst die Drossel (47µH, 1A) und nachgeschaltet in Sperrrichtung gegen GND eine TVS-Diode, welche knapp über der maximalen Bordspannung liegt, sowas wie eine “P6KE16CA” (16V). Danach dann eine SS14 oder 1N4007 in Flußrichtung als Verpolschutz (oder besser, einen Mosfet IRLML6344 in high-side Konfiguration, da dieser praktisch keinen Spannungsabfall erzeugt). Dahinter könnte man noch nen 10-22µF/25V Kondensator für die LC-Filterung setzen.
CAN-Bus Transceiver
Hier reden wir über sog. LOW-Speed Transceiver (< 1 MBit/s), also klassisches CAN, die sind in großer Vielfalt und billig zu bekommen. Häufige Vertreter sind der MCP2551, der TJA1050 und SN65HVD230.
Das Problem hier ist ein wenig der ESP32 mit seiner 3,3V Architektur. Ein CAN-Bus benötigt für saubere Pegel wenigstens 5V auf der CAN-Seite. Einige haben keine getrennte Spannungsversorgung für die IO-Logik wodurch sie dann dort TTL Pegel mit 5V erzeugen/erwarten, was für den ESP problematisch werden könnte. TX-Seitig vom ESP liegt der 3,3V Pegel noch in der Toleranz eines 5V CAN-Transceivers, aber RX-Seitig sollte man wohl besser einen Pegelwandler einsetzen um den ESP-Eingang nicht zu überlasten. Also entweder einen 5V CAN-Transceiver mit Pegelwandler, oder einen mit getrennter Vio und Vcan und doppelter Versorgung einsetzen.
Ich arbeite gern mit den NXP TJA-Transceivern, aber das spielt keine Rolle. Hier ein TJA1050 (5V !) auf einem Board für wenige Cent:
Der TJA1050 ist auf 5V ausgelegt. Leider besitzt er keine Wake-Up Funktion, D.H. man kann darüber den ESP nicht per GPIO aus dem Sleep holen. Der TJA1051 ist Pin-Kompatibel, legt den Transceiver aber schlafen wenn auf dem CAN nichts mehr los ist und kann bei CAN Aktivität ein Wake-Up Signal auf RX erzeugen. Den werde ich mal ausprobieren.
(!) ACHTUNG: Auf diesem Board sieht man ober rechts gut einen 120 Ohm Widerstand. Das ist der CAN Terminator. Den sollte man runterlöten wenn man damit ans Fahrzeug will, zumindest wenn man sich nicht an ein Gateway sondern direkt an den CAN ankoppelt. Diesen braucht man ggf. nur für eigene Projekte auf dem Labortisch. Macht vielleicht Sinn dafür einen Jumper einzubauen, der den bei Bedarf zuschaltet/trennt. Ich habe jetzt einfach den Widerstand runtergelötet.
Verbindung vom ESP32-C6 zum CAN-Transceiver
Es gibt zum glück keine festen Ports, man kann praktisch jeden GPIO mit dem/den CAN, äh “TWAI” Controllern verbinden. Ich nutze für die ersten Tests einfach mal GPIO5 zum senden (TX) und GPIO4 zum empfangen (RX). Wie immer gilt sich vor der Wahl eines GPIOs über deren sonstige Funktionen/Belegungen im Board zu informieren.
Die Software
Hier wird es wie immer interessant
Ich versuche mich mal Stück für Stück anzunähern…
Also fange ich mit einem Minimalbeispiel an. Dabei möchte ich einfach nur eine CAN-Botschaft empfangen (Sniffen) können. Die TWAI Funktionen erhält man mit folgendem Include:
#include <driver/twai.h>
Zunächst legt man fest welche Pins genutzt werden sollen und welchen CAN-Modus man wünscht (nur zuhören ohne ACK oder normal). Man kann es sich etwas einfacher machen indem man das Makro “TWAI_GENERAL_CONFIG_DEFAULT()” nutzt, welches andere, relevante Werte setzt und man sich auf das wesentliche konzentrieren kann. Ich mach das erstmal so:
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(
GPIO_NUM_5, // TX GPIO
GPIO_NUM_4, // RX GPIO
TWAI_MODE_LISTEN_ONLY // Operation mode
);
Anschließend legt man noch die Timing-Parameter für den CAN Bus fest. Auch hier behelfe ich mir mit einem geeigneten Makro bei dem ich nur die Geschwindigkeit wählen muss:
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); // 500 kbit/s
Als letztes noch den Filter festlegen. Da ich alles sehen will nehme ich einfach:
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
Nun installiert man den Treiber mit diesen Setups:
twai_driver_install(&g_config, &t_config, &f_config);
und ist startklar:
twai_start();
Danach werden eingehende Meldungen in die Receive-Buffer geschrieben die man wie Queues abfragen kann:
twai_message_t msg;
if (twai_receive(&msg, timeout) == ESP_OK) {
// valid CAN frame having msg.identifier and msg.data_length_code in msg.data
} else {
// something was wrong!
}
Der “timeout” ist klassisch in Ticks und “0” ergibt keine Blockade beim lesen.
Daraus habe ich mal dieses Testprogramm erstellt, welches einfach auf neue Botschaften pollt:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/twai.h"
#include "esp_log.h"
#define TX_GPIO GPIO_NUM_5
#define RX_GPIO GPIO_NUM_4
static const char *TAG = "CAN_TEST";
void app_main(void)
{
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO, RX_GPIO, TWAI_MODE_LISTEN_ONLY);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(TAG, "CAN Receiver started...");
while (1) {
twai_message_t msg;
if (twai_receive(&msg, 0) == ESP_OK) {
printf("ID: 0x%03lX DLC: %d DATA: ", msg.identifier, msg.data_length_code);
for (int i = 0; i < msg.data_length_code; i++) {
printf("%02X ", msg.data[i]);
}
printf("\n");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
Als Test-Setup habe ich den Tranceiver an einen CAN-Bus angeschlossen auf den ich mittels eines anderen Tools jede Sekunde eine Botschaft sende. Das Ergebnis erscheint dann so:
I (228) CAN_TEST: CAN Receiver started...
ID: 0x123 DLC: 8 DATA: DE AD BE EF 01 02 03 04
ID: 0x123 DLC: 8 DATA: DE AD BE EF 01 02 03 04
ID: 0x123 DLC: 8 DATA: DE AD BE EF 01 02 03 04
PRIMA!










