CAN-Bus Filterdevice für KFZ

Ich versuche mit einem Arduino einen CAN-Filter für den Multimedia-CAN-Bus in meinem Auto zu bauen. Dieser soll zunächst bidirektional alles durchlassen und bei Bedarf den Inhalt bestimmter IDs ändern.

Der MM MS-CAN in meinem Fahrzeug arbeitet mit Standard-Frames (11-Bit ID) und 125 kbps. Für einen ersten Test habe ich einen Arduino Nano und zwei CAN-Modulen mit MCP2515 (8 MHz) und TJA1050 genommen. Die Module habe ich in Reihe zum CAN-Bus Eingang des Radios geschaltet, d.h. Fahrzeugseitiger CAN in ein Modul und Radioseitiger in das andere. Auf CAN_H und CAN_L habe ich geachtet. Da im Radio kein CAN-Bus Terminator drin ist, habe ich das Radioseitige Modul per Jumper terminiert und das Fahrzeugseitige natürlich nicht.

Als Lib habe ich die “CAN-BUS Lib” von Cory J. Fowler verwendet, da mir diese recht weit fortgeschritten, aktuell gepflegt erschien und vor allem weil sie den MCP2515 mit 8 MHz von hause aus unterstützt.

Darin enthalten ist sogar schon ein Beispiel für einen solchen Repeater. Leider bleiben aufgrund der nicht vorhandenen Dokumentation sehr viele Fragen offen, es könnte also gut sein das ich was falsch gemacht hab. Jedenfalls habe ich den Sketch leicht an meinen Aufbau angepasst in Betrieb genommen. Anfangs sah es sogar so aus als würde es funktionieren. Leider hat das Audiogerät immer wieder Aussetzer gezeigt.

Nach etwas Recherche bin ich auf den One-Shot-Mode (OSM) gekommen, denn die IDs werden sowohl vom Radio als auch vom Fahrzeug her ständig wiederholt, da braucht es kein Resend vom Controller aus. Ich vermute das hat mehr Kollisionen und Bufferüberläufe verursacht als Pakete erfolgreich gesendet. Der OneShot-Mode sendet einmal und gut, ohne Fehlerzähler und Resend. Aber auch in diesem Modus blieb es bei den Aussetzern.

Ich kann es mir nicht erklären und suche daher hier um Rat. Vielleicht muss man der Lib ja noch weitere Parameter mitgeben (Filter/Mask, oder sowas) damit wirklich alles transparent von A nach B und B nach A kommt. Oder ich mache einen grundsätzlichen Denkfehler und das geht allein der Performance wegen nicht?

Anbei der Code und hier noch meine Fragen, generell dazu:

1.) Muss die Zeile mit “SPI.setClockDivider(…)” nicht VOR der ersten Kommunikation mit den Controllern stehen? Wie kann der sonst kommunizieren weil zu schnell? Und ist das “SPI_CLOCK_DIV2” auch für meinen 8 MHz getatkteten MCP richtig? Weil das Beispiel von einem 16 MHz getakteten ausgeht…

2.) Offenbar wird in der “begin()” Methode der CAN-Lib der Interrupt-Mode aktiviert (zumindest entnehme ich das den Quellen). Wenn das Modul ein Paket empfangen hat, zieht er das Signal auf LOW und der Arduino erkennt das. Wie lange hält der Controller das LOW? Bis zum lesen der Nachricht? Oder muss ich den wieder mit einem Befehl auf HIGH setzen? Verliere ich ggf. Nachrichten dadurch?

3.) 125 kbaud max. pro Bus ist ja eigentlich nicht viel. Selbst mal zwei sind nur 250. Das sollte der MCP und der Arduino mit seinen 16MHz doch locker weckstecken?

#include "MCP_CAN_lib/mcp_can.h"
#include <SPI.h>

unsigned long rxId;
byte len;
byte rxBuf[8];

#define CAR_CAN_CS 10     // CS of interface where CAN-Bus of car is connected to
#define CAR_CAN_INT 2     // INT of car-interface is connect to this Arduino port
#define NAV_CAN_CS 9      // CS of interface where CAN-Bus of radio is connected to
#define NAV_CAN_INT 3     // INT of radio-interface is connect to this Arduino port

MCP_CAN CAN0(CAR_CAN_CS);
MCP_CAN CAN1(NAV_CAN_CS);

void setup()
{
  Serial.begin(115200);
  
  // init CAN0 bus, baudrate: 125k@8MHz
  if(CAN0.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK){
  Serial.print("CAN0 (CAR): Init OK!\r\n");
  CAN0.setMode(MCP_NORMAL);
  } else Serial.print("CAN0 (CAR): Init Fail!!!\r\n");
  CAN0.enOneShotTX();
  
  // init CAN1 bus, baudrate: 125k@8MHz
  if(CAN1.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK){
  Serial.print("CAN1 (NAV): Init OK!\r\n");
  CAN1.setMode(MCP_NORMAL);
  } else Serial.print("CAN1 (NAV): Init Fail!!!\r\n");
  CAN1.enOneShotTX();
  
  SPI.setClockDivider(SPI_CLOCK_DIV2);         // Set SPI to run at 8MHz (16MHz / 2 = 8 MHz)
}

void loop(){  
  if(!digitalRead(CAR_CAN_INT)){               // If pin 2 is low, read CAN0 receive buffer
    CAN0.readMsgBuf(&rxId, &len, rxBuf);       // Read data: len = data length, buf = data byte(s)
    CAN1.sendMsgBuf(rxId, 0, len, rxBuf);      // Immediately send message out CAN1 interface
  }
  if(!digitalRead(NAV_CAN_INT)){               // If pin 3 is low, read CAN1 receive buffer
    CAN1.readMsgBuf(&rxId, &len, rxBuf);       // Read data: len = data length, buf = data byte(s)
    CAN0.sendMsgBuf(rxId, 0, len, rxBuf);      // Immediately send message out CAN0 interface
  }
}

Dual_CAN_TEST.zip (36.9 KB)

Hallo,

:o

das ist schon eine sehr spezielle Frage. Diese Tage bin ich hier schon kritisiert worden, weil ich einem Fragesteller überhaupt CAN für sein Projekt empfohlen habe.

Also aus meinen begrenzten Erfahrungen mit den CAN-Libraries von Collin Kidder für den Arduino Due und Teensy 3.x würde ich mal folgende Dinge anmerken:

  1. Die 125 kBit/s auf den beiden CANs gehen ja nicht direkt zum Arduino. Sondern die beiden MCPs hängen ja über SPI am Arduino. Wenn es einen Flaschenhals gibt, dann wahrscheinlich da.

  2. Ich hatte mir, auch schon wegen anderer Fragen hier, mal die Libs für den MCP2515 angesehen. Aufgefallen ist mir dabei, dass die meisten die schnellen CAN-Bitraten wie 500000 und 1000000 nicht unterstützen. Da scheint es also eine Grenze zu geben, was mit dieser Lösung machbar ist. Ob das dann bedeutet, dass aus 1x 250000 geht noch folgt, dass 2x 125000 auch geht, kann ich nicht sagen.
    (Edit: Deine Lib scheint ja alle Geschwindigkeiten zu unterstützen, eventuell geht es also doch.)
    (Edit 2: Noch mal recherchiert: z.B. Watterott CANdy Shield, Quarz 16 MHz im Schaltpan, verlinkte Lib kann maximal 500000.)

  3. Ich habe gar nicht danach geschaut, mit welchem Takt diese Shields den MCP betreiben und ob es da Unterschiede gibt. Nimmst du die 8 MHz weil du den Arduino auch mit 8 MHz (3,3 V ?) betreibst ?

Hallo und vielen Dank für die Anregungen!

Also mein Nano wird mit 16 MHz getaktet und mit 5V betrieben. Soweit ich das ermitteln konnte ist die Default-Geschwindigkeit des SPI dann 4 MHz. Also jenseits von dem was der CAN-Bus überhaupt liefern könnte.

Der MCP 2515 kann max 10 MHz SPI. CAN-Seitig könnte(!) er, sowie der darauf befindliche TJA1050 Transceiver bis zu 1MBit.

Ich verlasse mich darauf das der MCP einen INT auf Low bringt um den Eingang einer Nachricht zu zeigen. Dann lese ich diese Nachricht vom MCP. So ist es auch in der Demo vom Lib-Ersteller gemacht. Ich weiss aber nicht, ob der INT wirklich nur beim vollständigen Empfang gezogen wird. Laut MCP Datenblatt gibt es mind. 9 verschiedene Ereignisse die den INT low setzten, nicht nur RX.

So wie es aussieht muss man das Datenblatt des MCP komplett lesen und verstehen, ebenso die Lib um herauszubekommen wie diese arbeitet. Das hätte ich mir, in einfacherer Form als Anleitung/Dokumentation gewünscht, denn der Autor muss das ja alles wissen.

Wenn Du das dann alles verstanden hast, wäre es ein schöner Zug von Dir, Dein Wissen in eine Anleitung zu gießen, wie Du sie Dir gewünscht hast. Dann haben viele nach Dir einen Nutzen davon.

Gruß Tommy

Aussetzer wären das was ich erwarten würde, wenn der Arduino nicht mitkommt.

Bei der Baudrate könnte der CAN-Bus noch über 1000 Messages pro Sekunde liefern. Das wird nicht der Fall sein, aber es werden häufig Gruppen von Messages kommen, die sehr dicht aufeinander folgen.

Der empfangende MCP kann, wenn ich mich recht erinnere, maximal 2 Messages puffern.

D.h. effektiv hat der Arduino nur ein paar hundert Mikrosekunden, um die Daten über SPI aus den Registern des einen MCP zu ziehen, zu verarbeiten und auf den anderen MCP zu schreiben.

Messages können nun verloren gehen, wenn der MCP sie aus dem Puffer schmeißt, weil wieder neue reinkommen. Oder die Interrupt Logik vertrödelt Daten, wenn schon wieder neue kommen, während noch kopiert wird.

Zu sagen "4 MHz SPI ist viel schneller als 2x 125 kBit CAN" ist auch so eine Sache. Erstens werden die Daten ja nicht 1:1 umkopiert, sondern die Lib muss über SPI Register des MCP lesen. Da ist noch einiger Overhead, wie das Setzen der CS-Leitungen und Auswertung der Registerinhalte.

Könnte sein, dass im MCP2515 ein abgespeckter PIC-Mikrocontroller steckt, Microchip hat das bei einigen ICs so gemacht. Warum man den dann bei einer zeitkritischen Anwendung von 16 auf 8 MHz runtertaktet, will sich mir auch nicht erschliessen.

Dieser "one shot mode" dient nur dazu zu verhindern, dass eine Message pausenlos gesendet wird, wenn kein anderer Knoten am Bus ist, der ein ACK produziert. Das hat eigentlich nichts mit dem Grundproblem hier zu tun.

Aber warten wir mal ab.

@Tommy56, du wirst vielleicht lachen, aber genau das werde ich auch tun! Und das das bei mir nicht nur floskeln sind, davon kann sich jeder gern mal selbst ein Bild auf meiner Website https://mk4-wiki.denkdose.de machen :slight_smile:

aber dazu muss ich das Ganze ja erstmal verstehen und zum laufen bringen und da ist jede mIthilfe, wie auch später beim erstellen einer guten Doku, willkommen. Mal schauen ob andere, evtl. auch Du sich daran ebenso rege beteiligen wie beim stellen von Anforderungen :wink:

Das war keine Anforderung, sondern nur ein Gedanke.

Gruß Tommy

Jab, und ich bin da voll dabei. Aber erstmal mit dem Problem starten. Gern(!) dokumentiere ich alles für die Nachwelt. Neben meinem Auto-Wiki habe ich auch noch eines für Elektronik eingerichtet, ich denke das landet dann eher da.

Also, um meiner Lösung näher zu kommen sehe ich folgende Themenkomplexe die es zu verstehen gilt:
1.) Wie funktioniert SPI?
2.) Wie funktioniert CAN?
3.) Wie arbeitet der MCP2515 auf SPI-Seite und was ist zu beachten?
4.) Wie arbeitet der MCP2515 auf CAN-Seite und was ist zu beachten?
5.) Wie sollte eine Initalisierung und Kommunikation von Arduino und MCP (CAN) aussehen?
6.) Welche Library unterstützt das alles am besten (schlank!) und wie?

Alles in Bezug auf Arduino und seine Möglichkeiten. Ja, beim Arduino Due oder den AT90CAN Controllern von Atmel hat man bereits integrierte CAN-Controller. Da ist es sicherlich einfacher, aber auf mit den recht günstigen Modulen sollte es ja gehen.

Zu 1) Alles hier beschrieben Arduino - SPI
Soweit mir bislang klar, hat der Arduino eine Hardware SPI (Serial-Periphal-Interface). D.H. man kümmert sich nicht um die einzelnen Bits und Signale, sondern arbeitet mit Bytes auf der Protokollebene. SPI selbst hat getrennte Leitungen zum senden/empfangen, kann also bidirektional arbeiten. Die Sendeleitung vom Arduino weg heißt MOSI (Master-Out, Slave-In) und die Empfangsleitung MISO (Master-In, Slave-Out). Daneben gibt es noch eine Taktleitung SCK. Dabei ist der Takt variabel und wird vom Master erzeugt. Damit werden dann die Bits für Sende- und sowohl auch Empfangsleitung synchronisiert.
SPI ist ein Single-Master Bus-Protokoll, es können also mehrere Slaves an einem Bus mit einem einzigen Master betrieben werden. Dabei geht die Kommunikation immer vom Master aus. Mit wem dieser reden möchte, wird durch ein CS (SS) Signal gesteuert (also nicht durch eine Adresse wie z.B. beim CAN). Pro Slave braucht es ein steuerbares Signale (direkt vom Arduino-Pin oder ggf mittles BCD-zu-Dezimal-Dekoder). Es muss auch definiert sein, wie lange das CS Signal anstehen muss, damit sich der Slave auf den Empfang einstellt. Grundsätzlich muss das CS-Signal so lange anstehen bis die Übertragung vollständig beendet ist.

Die maximale Taktrate der SCK-Leitung im Arduino hängt von der Prozessortaktfrequenz und div. Einstellparametern ab. Sie wird durch Teilerfaktoren angepasst. Soweit mir bekannt werden bei SPI reine Daten ohne jeglichen Protokolloverhead gesendet. Es gibt also keine Längen, Adressen, Typen und Prüfsummen. Das macht es sehr effizient. Natürlich können hier Übertragungsfehler auftreten die dann auf Softwareebene (z.B. durch eigenes senden von Prüfsummen) gehandelt werden müssen. Bei 10 MHz können, je nach Verlegungsart der Leitungen, schon erhebliche Störungen auftreten. Dem gilt es sicher irgendwie entgegen zu wirken, besonders bei so fliegenden Verkabelungen auf einem Steckbrett...

Weiterhin ist noch wichtig das sich Master und Slave(s) darüber einig sind welches Bit zuerst kommt, das höherwertigste (D7, also MSB) oder das niederwertigste (D0, also LSB). Am häufigsten kommt wohl MSB vor, wie bei so vielen anderen seriellen Protokollen auch.
Zuletzt muss auch noch klar sein, wann ein gültiges Bit anliegt/übernommen werden kann. Bei der steigenden, oder fallenden Flanke des Taktsignals und wie die Grundpolarität ist (5V=Ruhepegel oder 0V=Ruhepegel). Hier kenn SPI 4 Modi. Ist das alles definiert und stimmig, sollte die Kommunikation grundsätzlich fehler- und konfliktfrei funktionieren.

Zu 3.)
Die SPI-Schnittstelle ist im Datenblatt vom MCP2515 ab Seite 63 dokumentiert und folgende Eigenschaften:

  • Max. SPI-Geschwindigkeit (SCK-Taktfrequenz) = 10 MHz
  • Die Betriebsspannung kann 3.3V oder 5V betragen und ist somit Pegelkompatibel für Arduino.
  • SPI-Modus 0 oder 3: Das sagt das Datenblatt: "Commands and data are sent to the device via the SI pin, with data being clocked in on the rising edge of SCK." und "Data is driven out on the SO line on the falling edge of SCK.". Das entspricht der Funktionsweise im Arduino-SPI SPI_MODE0 oder SPI_MODE3. Der Unterschied ist dann noch die Polarität des Signals. In den Diagrammen sieht es so aus, als wäre 0 (LOW) der Ruhepegel.
  • /CS muss vor und während der Datenübertragung LOW sein. Eine Datenübertragung besteht aus einem oder mehreren Bytes.

Schön, dass das Thema CAN hier mal etwas mehr Platz einnimmt. Einen Mitleser hast du schon mal.

Auch wenn ich glaube, dass der MCP2515 wirklich schon etwas alt ist. Immerhin ist er der letzte CAN-Chip mit SPI-Interface, der noch nicht eingestellt wurde.

Anwendungen finden die Arduino Shields mit dem MCP2515 z.B. bei den Modelleisenbahnern. Digitalsysteme, wie das von Märklin, arbeiten mit CAN.

Dann findet CAN auch noch auf dem Wasser statt, dazu gibt es einen der längsten Thread im englischen Teil des Forums

die zugehörige Library

zeigt ein interessantes Detail: Sie kann mit MCP2515, Due oder Teensy verwendet werden, die entsprechenden Anbindungen sind austauschbar. Die komplexeren Anwendungen wandern da aber auch eher zu den 32 Bittern ab.

Im Automotive Bereich geht der Trend anscheinend auch eher zu Due oder Teensy. Ein Beispiel ist der auf dem Due basierende M2
https://www.macchina.cc/

Ja, bin da gern dabei. Obwohl dieser Link https://www.macchina.cc/ einem echt Lust auf mehr macht... aber die Grundlagen sind überall dieselben und warum nicht klein anfangen.
Gutes Stichwort! Libraries gibt es wie Sand am Meer. Leider bin ich da erstmal gescheitert, denn man weiss nicht ob die gewählte Lib gut oder schlecht ist, es gibt einfach zuviele davon. Und dann steht man leider wieder vor demselben Problem: Wie bekomme ich es ans laufen und was kann ich tun wenn es nicht so will wie ich?
Und diese Frage löst man nur durch Wissen :slight_smile:
Mit super-universal-libs die alles können kommt man da meines Erachtens nach nicht weiter. Das will ich garnicht, zumindest nicht zu diesem Zeitpunkt. Ich will erstmal verstehen und möglichst direkt an meiner eingesetzten Hardware, ohne weitere Abstraktionsschichten dazwischen.

Daher wäre MEIN Vorschlag zur Zusammenarbeit hier, das wir versuchen gemeinsam das Wissen um SPI und den MCP2515 aufzubauen und wirklich bei NULL anfangen. Am Ende steht vielleicht sowas wie eine eigene Lib, zumindest sollte alles funktionieren :slight_smile:

Um die Ergebnisse nicht durch zig Posts raussuchen zu müssen wäre mein Vorschlag die Ergebnisse in meinem Wiki zu dokumentieren. Sehr gerne lade ich Dich, ArduFE als schreibberechtigten Autor da mit ein!

Also ich könnte z.B. etwas über CAN-Transceiver hinzusteuern, davon haben sich mittlerweile einige angesammelt, vom MCP2551 bis zum TJA1051T/3. Das kann ich aber auch hier im Forum tun, du kannst die Infos dann kopieren.

Dem MCP2515 stehe ich weiterhin etwas skeptisch gegenüber, zumindest für die Verwendung als CAN-Filterdevice. Ich denke das ist eher ein Schaltkreis, der einzelne Messages über seine Filter aus dem Bus herausfischt.

Für Anwendungen, die den gesamten Busverkehr verarbeiten, scheint mir die Empfangslogik mit den zwei Puffern nicht gedacht zu sein. Beim von dir erwähnten AT90CAN steht der Unterschied schon auf dem Titelbild (!) des Datenblattes: Der hat 15 Puffer für CAN-Messages, die zum Senden und Empfangen benutzt werden.

Auch die CAN-Library auf dem Teensy benutzt standardmäßig 14 der 16 Puffer als Empfangspuffer. Und das bei Chips von 72 bis 180 MHz.

Dass es also den MCP aus den Socken haut, wenn da mehrere Messages schnell hintereinanderkommen, kann man schon aus dem Blockschaltbild sehen ...

Prima, ArduFe! Melde dich doch mal hier an: http://e-wiki.denkdose.de/
Das habe ich extra dafür eingerichtet, ist also praktisch noch leer :slight_smile:

Mach doch mal eine Aufstellung der Treiber, dann lege ich im Wiki für jeden eine Seitenstruktur an, die Du dann mit "Leben" befüllen kannst. Dabei reicht es erstmal nur Text reinzuschreiben. Datenblätter und Bilder sind willkommen, könnte ich aber vermutlich auch beisteuern. Ich würde den Artikel dann danach in Form bringen. So können wir uns die Arbeit ein wenig teilen.

Ja, mit dem MCP2515 hast Du vielleicht recht. Es gibt ja auch Chips die intern einen Forward machen, ohne das man die Daten durch einen Prozessor bearbeiten muss. Es ist halt wie überall die Frage der Selektion. Vielleicht sollte man das mit diesem Chip lieber aufgeben und was besseres nehmen, anstelle krampfhaft zu versuchen etwas draus zu machen was es nicht ist. Mich hat halt die anfängliche Einfachheit der Konstruktion begeistert und das man die Teile sehr günstig bekommen kann.

Teensy arbeitet ja mit sehr unterschiedlichen uCs. Was schwebt Dir denn da vor?
Der Teensy 2.0 hat einen AT32U4. Der fällt flach, da kein CAN.
Teensy++ 2.0 hat einen AT90USB, ebenfalls ohne CAN.
Teensy 3.0 und Teensy 3.1 haben einen NXP MK20DX... drauf, ebenfalls ohne eigenen CAN-Controller.
Teensy 3.2 hat einen NXP MK20P... drauf, der hat, soweit ich ermitteln konnte, 1 CAN-Controller.
Teensy 3.5 und 3.6 scheinen 2 CAN-Controller drauf zu haben.
Für den AT90CAN ist mir keine Plattform bekannt die so allgemein verfügbar wäre wie Arduino oder Teensy. Aber selbst wenn, der hat nur einen CAN-Controller. Der zweite wäre dann wieder extern, mit MCP o.ä.
Dann gibt es noch den STM32. Der hat, soweit ich weiss auch nur 1 CAN-Controller.

In Summe muss man natürlich auch die Verhältnismäßigkeit wahren. Ein Teensy 3.5 ist schon ein Kracher. Ob es das wirklich braucht? Ich glaube das ein Atmega328 für die Verarbeitung der CAN-Daten schon schnell genug wäre. Vielleicht ist nur der Controller schlecht und ein anderer besser?

Hier wäre auch eine gute Aufstellung zu finden: CAN – Mikrocontroller.net

Also mal zur Übersicht der Teensys:

Die 8 Bit Typen sind eigentlich veraltet, werden nur noch verkauft solange Nachfrage vorhanden.

3.0 wird nicht mehr produziert.

Der 3.1 wurde durch den 3.2 ersetzt (stärkerer Spannungsregler)

Der 3.5 hat einen CAN, nur der 3.6 hat zwei.

Allerdings wäre das Senden in deinem Projekt eventuell auch mit einem zusätzlichen MCP2515 machbar.

Mein CAN Einstieg war kein Arduino, sondern der hier
https://developer.mbed.org/platforms/mbed-LPC1768/
der hat auch 2 CAN
https://developer.mbed.org/handbook/CAN

Im Prinzip wäre so ein Cortex M3 mit 96 MHz und 32 kB RAM die passende Größe für so einen CAN Filter. Aber der Teensy 3.6 ist billiger.

Appropos billig, ein mbed unter 20 Euro mit 2 x CAN wäre der hier,
https://developer.mbed.org/platforms/ST-Nucleo-F446RE/
Wäre mir jetzt aber zu unförmig.

Ach so ja, weil hier schliesslich das Arduino Forum ist:

Auch der Due hat 2 CAN, dein Projekte wäre im wesentlichen eine Variante des "Traffic Modifier" Beispiels

Teensy 3.5 oder 3.6 wäre also angesagt. 25-30€ zzgl. den Transceivern. Wenn wir aber schon über andere Chips spekulieren, dann kämen auch noch ganz andere Lösungen in Frage. Das meiste davon dürfte mit MCP2515 nichts mehr zu tun haben.

Die Kernfrage war aber noch, ob die Probleme aufgrund des kleinen Empfangspuffers (2 Nachrichten) kommen und wirklich Nachrichten verloren gehen, oder ob es noch an was anderem liegt. Das müsste doch irgendwie erkennbar sein, ob das passiert....

Dann gäbe es ja z.B. noch die Alternative andere CAN-Controller zu nutzen, die mehr Pufferplätze haben, wie z.B. ein SJA1000T oder ein MCP25020 oder sowas.

mikrotron:
Die Kernfrage war aber noch, ob die Probleme aufgrund des kleinen Empfangspuffers (2 Nachrichten) kommen und wirklich Nachrichten verloren gehen, oder ob es noch an was anderem liegt. Das müsste doch irgendwie erkennbar sein, ob das passiert....

Das hat man im "Troll-Forum" doch ganz gut beschrieben. Die Software muss sicherstellen, dass vom MCP signalisierte Daten auch richtig abgeholt und versendet werden, nicht das sie verloren gehen, weil schon wieder andere Daten signalisiert werden.

Wenn der MCP was verliert, dann meldet er einen Overflow.

[Edit]
Und wie schon erwähnt, der Teensy 3.5 hat nur einen CAN.

Was andere Chips angeht, würde ich der Empfehlung zu STM32 folgen. Was spezielle CAN-Controller angeht, findet das heute in der Praxis fast nur noch in FPGA statt.

Also, ich habe mir das ganze mal mit einem Logicanalyzer angeschaut und da erkenne ich, das besonders das aussenden problematisch ist, weil das im vergleich zum empfangen sehr lange dauert.
Habe die Tracedatei vom LA (Saleae. Die Software bekommt man dort kostenlos zum Download) mal angehangen.

Auf den Bildern kann man ganz gut erkennen wie das aussieht. Ansicht recht sauber, aber durch das lange senden werden dann IDs im MAB (message-assembly-buffer) überschrieben, weil es keine freien RX-Puffer mehr gibt. Also ist es schon richtig das der MCP zum empfangen nicht geeignet ist, weil er so lang zum senden braucht. Evtl. habe ich auf der Sendeseite ja auch noch ein Problem, da sollte ich mir vielleicht den TX vom NAVCAN mal auf einen Kanal legen. Leider habe ich nur die 7 Kanal-Version eines LA, ich muss da also auf Signale verzichten.

Da das senden in der Lib von CoreyJFowler so gebaut ist, das er erst von sendMsgBuf() zurückkehrt wenn das TX-Flag die Übertragung anzeigt, kann ich in dieser Zeit auch keine weitere Nachrichten vom CARCAN abrufen und in einem RAM Ringbuffer im Arduino parken. Dazu müsste das senden auch über den Ringpuffer via ISR laufen. Problematisch dabei ist jedoch, das sich beide Controller den SPI-Bus teilen müssen. D.H. man müsste softwareseitig verhindern, das während die Vordergrundroutine gerade mit einem MCP spricht, die ISR das auch will. Die müssen sich gegenseitig lücken lassen.

Ich hab zwar keinen Plan wie ich das anstellen würde, aber wenn ich z.B. das auslesen als eine atomare Operation ansehe, d.h. die Reihe von SPI-Paketen nicht unterbreche und das gleiche beim beladen des MCP zum senden, dann verliere ich damit nicht viel Zeit weil das direkt hintereinander läuft und gemacht werden muss, nutzt alles nichts. Beim senden aber warte ich nicht bis das Paket raus ist, sondern kehre direkt zum Main zurück und lese ggf. weitere CAN-Pakete von dem anderen MCP in den Puffer. Zwischendurch (irgendwie timergesteuert, so alle 100us oder sowas) mache ich dann, wenn der SPI-Bus grad frei ist, eine Abfrage beim sendenden MCP. Ist der noch nicht fertig, gehts zurück zu main. Ist er aber fertig, dann entferne ich die Nachricht aus dem Ringpuffer und falls eine weitere da ist, schicke ich diese zum MCP.

Wenn man das so machen würde, dann könnte ich mir vorstellen das praktisch keine Pakete verloren gehen. Aktuell betreibe ich den SPI mit 1MHz (DIV32 mit 16MHz Arduino) weil ich sorge hatte das die hohe Frequenz in meinem Steckbrett probleme macht. Man könnte also die Lese und Schreibzeiten theoretisch noch auf ein Achtel reduzieren.

24_mhz_dual-mcp-canrx.zip

Hm, nur mal so ein paar Ideen.

Im Teensy Forum gab es ein paar Debatten und einen Fork der FlexCAN-Library, wegen ein paar Problemen mit einer Ringbuffer Struktur, die möglicherweise bei Interrupts Daten verliert.

Das hat nur indirekt mit deinem Thema zu tun, aber 2 von 3 aktuellen Versionen der Teensy CAN-Library legen im Controller für Senden und Empfangen noch zusätzlich Ringbuffer an, in denen die empfangen oder noch zu sendenden Daten liegen.

D.h. es wird nur direkt gesendet, wenn eine Mailbox auf dem CAN-Controller frei ist, sonst landet das Zeug im Ausgangsringbuffer. Ein Interrupt vom CAN-Controller sorgt bei einer freien Mailbox dafür, dass dann das nächste Paket aus dem Ringbuffer geholt wird.

(Die Seeleute vom Arduino NMEA2000 Projekt aus dem englischen Forum oben, glauben dass da manchmal Pakete durch gleichzeitigen Zugriff auf den Ringbuffer verloren gehen und haben deshalb die Library geforkt.)

Aber zumindest die Technik dürfte für dich interessant sein. Eventuell kannst du nicht einfach senden und dabei ein noch nicht fertiges altes Senden überfahren, sondern musst noch einen Zwischenspeicher einbauen und erst bei wieder geringerer Buslast langsam aufholen.

So, bin wieder einen Schritt weiter!

Also ganz klar, ohne Zwischenspeicherung im Arduino geht nix. Ich habe dafür die Lib QueueArray verwendet.

Wie beschrieben hat die MCP2515-Lib von Corey Fowler die Senderoutine so ausgelegt, das diese erst zurückkehrt wenn der MCP das CAN-Paket absetzen konnte. Das blockiert zulange, daher habe ich sie um eine Funktion "sendMsgAsync()" erweitert. Diese übergibt nur die zu sendenden Daten an den MCP.

Meine Programmlogik habe ich dann so geändert:

  • Wenn eine Nachricht auf einem der Boards eingeht (via INT-Pin signalisiert) dann lese die Nachricht und schiebe sie in die Sendequeue des jeweils anderen Boards.
  • Liegt eine Nachricht in einer der beiden Sendequeues und im MCP sind noch freie TX-Buffer (Statusabrage via SPI), dann entnehme diese und schreibe sie in den freien Buffer im MCP.

Alternativ zur Abfrage des Sendestatusregisters könnte man auch den INT-Pin so konfigurieren, das neben dem Empfang auch das erfolgreiche Senden einer Nachricht angezeigt wird. So könnte man die Senderoutine mit einer Variablen blockieren bis wieder Puffer frei sein. Man erspart sich also permanente Statusabfragen via SPI. Ob der Aufwand aber dem Nutzen gerecht wird...

Ein Problem bei der QueueArray-Lib ist jedoch noch, das diese sich dynamisch vergrößert. Irgendwann ist dann einfach kein Arbeitsspeicher mehr vorhanden. Ich werde das noch so ändern das man eine vordefinierte Größe hat (z.b. 8 oder 16 Nachrichten pro Queue, was halt gebraucht wird damit es klappt). Leider hat der Arduino nicht viel RAM und ich kenne auch keinen Weg ihm mehr zu verschaffen.
Wenn ich den Ringpuffer begrenze muss ich, sobald dieser voll ist, eingehende Nachrichten verwerfen oder jeweils die jüngste im Ringpuffer. Das ist zwar schlecht, aber zumindest bleibt das System dann stabil.

Um nun zu testen welchen Erfolg meine Anpassungen haben, habe ich folgendes Test-Setup erstellt:

Mit Hilfe zweier USBzuCAN Schnittstellen sowie CANHacker habe ich einmal einen Sender (CARCAN) und einen Empfänger (NAVCAN) simuliert. Ich sende hier eine einzige CAN-ID mit Dummy-Daten. CANHacker hat Sende- und Empfangszähler wodurch ich einen möglichen Paketverlust erkennen könnte. Dann passe ich die Periodenzeit des Senders an und schaue ob der Emfpänger dem folgt.

Mit diesem Setup bin ich aktuell schonmal bis auf knapp 10ms Periodenzeit gekommen. Die Latenz (Durchlaufverzögerung) beträgt dabei 3ms. Das ist schon ganz ordentlich, aber auch noch Unidirektional.

Als nächstes mache ich das ganze Bidirektional und erhöhe auch mal den SPI-Takt. Wenn das dann alles stabil läuft würde ich nochmal einen Test im Fahrzeug wagen.

Also noch nicht am Ende, aber ich sehe ein Licht im Tunnel :slight_smile:

Ich glaub ich hab es fast geschafft! Nachdem ich die Zeitfresser in der MCP Lib von Corey Fowler entfernt habe, intelligent mit Queue und ohne auf das senden zu warten arbeite und den SPI Takt auf 8 Mhz erhöhen konnte scheint es zu laufen. Habe heute noch eine Testfahrt gemacht und es gab keine Probleme! :-)))

Zuvor, mit 500 kHz SPI Takt gingen scheinbar noch vereinzelte IDs verloren. Leider hab ich noch keine Idee wie ich diesen Zustand erkennen kann um sicher zu wissen welche änderung am Code welchen Effekt hat.

Ich sehe am LA nur das ich mit 0,5 MHz SPI ca. 3ms Durchlaufzeit von CAR nach NAV CAN habe und bei 8 MHz nur noch 1,3 ms. Der Takt scheint aber schon einen Einfluss zu haben, aber er ist wohl nicht gross. Vermutlich würde es schon bei 1 oder 2 MHz stabil laufen.

Jetzt muss ich noch meinen Code schön machen. Anstelle der modufizierten Queue würde ich den Ringbuffer selbst kodieren wollen. Dann wär noch die Frage, ob ich wirklich die Lib nutze, weil ich nur wenige Befehle verwende und darin auch noch Optimierungspotential gefunden hab. Letztlich wör überhaupt die Frage ob ich nicht lieber einen reinen AVR Framework nehme anstelle dem vom Arduino. Ich fürchte nämlich das da außerhalb der loop() noch zuviel Zeit flöten geht, für irgendwas...