Forum: Mikrocontroller und Digitale Elektronik Günstige CAN-Filter Lösung


von Olli Z. (z80freak)


Lesenswert?

Ich habe versucht mit einem Atmega328 und zwei MCP2515 (je mit TJA1050 
Transceiver) ein bidirektionales "Filter-Device" für einen 125kbit 
CAN-Bus zu bauen. Es sollen bestimmte Daten bestimmter IDs geändert 
werden. Aber selbst im "Durchzugmodus" ohne jegliche Datenverarbeitung 
verliert diese Lösung zuviele Pakete. Es sieht sieht so aus als wäre der 
MCP nicht für sowas zu gebrauchen...

Frage: Gibt es eine günstige Alternative dazu? Sicher wäre es besser 
einen uC mit integriertem CAN-Controller zu haben, wie den AT90CAN oder 
den STM32. Die haben aber alle "nur" einen CAN drauf und wenn bräuchte 
ich wohl zwei?

Wer hat schonmal solch einen Filter/Gateway gebaut und kann mir einen 
Tipp geben?

von H.Joachim S. (crazyhorse)


Lesenswert?

Olli Z. schrieb:
> ohne jegliche Datenverarbeitung
> verliert diese Lösung zuviele Pakete. Es sieht sieht so aus als wäre der
> MCP nicht für sowas zu gebrauchen...

Dann hast du was grundlegend falsch gemacht.
Genauso eine Lösung läuft bei mir mit 2x500kBit und ziemlich viel 
traffic (Motor-CAN), dazu werden auch noch Daten verwurstet.

SPI-Frequenz 8MHz, ATMega32 mit 16MHz.

von Stefan K. (stefan64)


Lesenswert?

STM32 hat 2 CAN onboard.

125kbit sollten eigendlich machbar sein, wenn ich mich recht erinnere, 
dann lief ein MCP2515 bei mir bis 250kbit ohne Datenverlust. Das kommt 
natürlich etwas auf die Firmware des ATmega drauf an. Allerdings würde 
ich mir heute keinen MCP2515 mehr antun.

Viele Grüße, Stefan

von H.Joachim S. (crazyhorse)


Lesenswert?

Würde ich heute auch nicht mehr, aber es geht problemlos.
CAN-Botschaften auf Bit-Ebene sind länger als die Daten, die auf der SPI 
hin- und hergeschoben werden, also zusätzlich Zeit gewonnen. Aber man 
kann das auch grob 1:1 rechnen.
Bei 8Mbit SPI und 2x125kBit CAN hat man doch Zeit ohne Ende.
Interessant wird allenfalls, wenn der MCP bei fast vollem Bus seine 
Sendedaten aufgrund niedriger Priorisierung nicht los wird und man sich 
ständig darum kümmern muss. Aber dann ist eh am Grundkonzept was faul.

von Thomas F. (igel)


Lesenswert?

Olli Z. schrieb:
> bidirektionales "Filter-Device" für einen 125kbit
> CAN-Bus zu bauen.

Kommt halt auf die Buslast an. Wenn beide Busse schon über ~40% Buslast 
haben und dann noch die Botschaften des anderen Busses mit drauf sollen 
wird es arg voll. Das ist aber dann ein Transportproblem und keines des 
MCP2515.

Alternativ zu Atmega haben die AtXMega zwei SPI-Schnittstellen. Aber wie 
H.Joachim schon schrieb sollte es daran nicht sccheitern.
Nach meiner Erfahrung ist das Problem eher die Software die 
Herausforderung um darin schnelle Puffer in beide Richtungen zu 
implementieren.

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

H.Joachim S. schrieb:
> Dann hast du was grundlegend falsch gemacht.
> Genauso eine Lösung läuft bei mir mit 2x500kBit und ziemlich viel
> traffic (Motor-CAN), dazu werden auch noch Daten verwurstet.
> SPI-Frequenz 8MHz, ATMega32 mit 16MHz.

Ok, das habe ich ja fast befürchtet :-/
Also ich habe zwei handelsübliche CAN-Shields mit MCP2515 und TJA1050 
drauf genommen und von beiden Modulen MISO => D12, MOSI => D11, SCK => 
D13 verbunden. Die CS- und INT-Leitungen auf separate Eingänge des 
Arduino.

Dann habe ich, nach langer Sucherei die MCP2515-Lib von Corey Fowler 
verwendet (https://github.com/coryjfowler/MCP_CAN_lib) weil meine 
CAN-Shields einen 8 MHz Quarz drauf haben und viele andere Libs einen 16 
MHz voraussetzten.

Ich initialisiere das ganze so:
1
void setup()
2
{
3
  ...
4
  pinMode(CAR_CAN_INT, INPUT);
5
  if (CAR_CAN.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) != CAN_OK) {
6
    Serial.println("!! ERROR initializing CAR-CAN interface !!");
7
  }
8
  CAR_CAN.setMode(MCP_NORMAL);
9
  CAR_CAN.enOneShotTX();
10
  
11
  pinMode(NAV_CAN_INT, INPUT);
12
  if (NAV_CAN.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) != CAN_OK) {
13
    Serial.println("!! ERROR initializing NAV-CAN interface !!");
14
    halt();
15
  }
16
  NAV_CAN.setMode(MCP_NORMAL);
17
  NAV_CAN.enOneShotTX();
18
  
19
  SPI.setClockDivider(SPI_CLOCK_DIV2);         // Set SPI to run at 8MHz (16MHz / 2 = 8 MHz)
20
  ...

Das mit dem "enOneShotTX()" war nur ein Versuch, weil ich befürchtete 
das der MCP bestimmte Nachrichten nicht weg bekommt und diese deswegen 
im Buffer liegen bleiben und resendet werden. Währenddessen schiebe ich 
von "hinten" aber neue Nachrichten nach und dann verliert er welche. 
Inzwischen ist mir aber klar geworden das der OSM nur dazu dient, 
Nachrichten auf den bus zu senden die auch wenn niemand sie acknowledged 
keinen Sendefehler im Controller erzeugen. Zudem brachte das nichts für 
die Stabilität.

Die Verarbeitungsroutine sieht dann so aus:
1
void loop()
2
{
3
  // Forward message from CAR to NAV
4
  if (!digitalRead(CAR_CAN_INT)) {
5
    CAR_CAN.readMsgBuf(&rxId, &len, rxBuf);
6
    NAV_CAN.sendMsgBuf(rxId, 0, len, rxBuf);
7
  }
8
9
  // Forward message from NAV to CAR
10
  if (!digitalRead(NAV_CAN_INT)) {
11
    NAV_CAN.readMsgBuf(&rxId, &len, rxBuf);
12
    CAR_CAN.sendMsgBuf(rxId, 0, len, rxBuf);
13
  }
14
}

Also recht trivial, dachte ich. Das Teil steckt zwischen Radio und 
Fahrzeug auf dem Multimedia-CAN. Dieser hat 125kbaud und arbeitet mit 
Standard-IDs (11 Bit).

Was mache ich denn hier falsch? Und wie hast Du es denn realisiert?

von Olli Z. (z80freak)


Lesenswert?

Stefan K. schrieb:
> STM32 hat 2 CAN onboard.
Ich sehe da im Blockschaltbild und der Doku nur einen CAN. Wie werden 
denn zwei angeschlossen?

> 125kbit sollten eigendlich machbar sein, wenn ich mich recht erinnere,
> dann lief ein MCP2515 bei mir bis 250kbit ohne Datenverlust. Das kommt
Zumindest laut Datenblatt kann der bis 1 MBaud. Soviel will ich ja 
garnicht. Gebe mich mit lausigen 125kbit zufrieden :-) Der SPI 
dazwischen kann bis 8 MHz, also das sollte ja eigentlich reichen, dachte 
ich.

> natürlich etwas auf die Firmware des ATmega drauf an. Allerdings würde
> ich mir heute keinen MCP2515 mehr antun.
Naja, das Ding ist ja billig und weit verbreitet. Und ich wollte mit 
meinen bestehenden Mitteln was zusammenbauen. Der STM32 und sicher auch 
viele andere Chips sind da mit sicherheit besser geeignet, da 
CAN-Controller integriert und eine Serielle Übertragung entfällt. Aber 
irgendwie dachte ich das die Anfoderung derart trivial sei, das sogar 
ein Arduino mit zwei MCP fast zuviel wäre...

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

H.Joachim S. schrieb:
> Würde ich heute auch nicht mehr, aber es geht problemlos.
Ja, ich weiss wohl das ich da Altertümchen einsetze... ;-)

> CAN-Botschaften auf Bit-Ebene sind länger als die Daten, die auf der SPI
> hin- und hergeschoben werden, also zusätzlich Zeit gewonnen. Aber man
> kann das auch grob 1:1 rechnen.
> Bei 8Mbit SPI und 2x125kBit CAN hat man doch Zeit ohne Ende.

So habe ich auch kalkuliert. Der MCP mach SPI bis 10 MHz in MODE0 oder 
MODE3. SPI selbst hat ja keinen Protokolloverhead, aber das 
MCP-Protokoll ein wenig. Um Daten zu lesen muss man natürlich ein 
Kommando senden. Der MCP empfängt erstmal alles in einen seiner beiden 
internen Puffer, dann senkt er INT. Ab hier muss dann die CPU einen 
Read-Befehl an den Controller senden und dieser sendet ID und Daten. 
Danach kann das ganze dann wieder vom anderen MCP gesendet werden.
Also hat man wenigstens die Kommandobytes als Overhead. Aber wie Du 
schon sagst müsste das aufgrund der deutlich höheren Taktrate beim SPI 
kaum ins Gewicht fallen.

> Interessant wird allenfalls, wenn der MCP bei fast vollem Bus seine
> Sendedaten aufgrund niedriger Priorisierung nicht los wird und man sich
> ständig darum kümmern muss. Aber dann ist eh am Grundkonzept was faul.

In meinem Setup hat der MCP keine eigene Adresse. Er soll auf alle IDs 
reagieren und alle senden. Ich denke auch das das lesen grundsätzlich 
nicht das Problem ist.

von Olli Z. (z80freak)


Lesenswert?

Thomas F. schrieb:
> Kommt halt auf die Buslast an. Wenn beide Busse schon über ~40% Buslast
> haben und dann noch die Botschaften des anderen Busses mit drauf sollen
> wird es arg voll. Das ist aber dann ein Transportproblem und keines des
> MCP2515.

Da war mein Eingangsthread vielleicht mißverständlich: Das Device kommt 
in Reihe zu einem bestehenden Bus. Da gibt es keine neuen Nachrichten, 
auch werden keine Busse zusammengelegt.

von Stefan K. (stefan64)


Lesenswert?

Du hast Recht, nicht alle STM32 haben 2 * CAN. Du kannst auf der 
ST-Seite nachschauen, welches Derivat wieviele Schnittstellen hat.

STM32F1xx: ein paar ...
STM32F2xx: alle ...
STM32F4xx: fast alle ... haben 2 CAN-Schnittstellen.

Ich hatte mich mit dem MCP2515 beim Debuggen ziemlich rumgeärgert, weil 
die Statusflags nur über SPI lesbar sind. Das ist ein ziemlicher 
Rückfall gewesen gegenüber dem Debuggen der anderen internen 
Schnittstellen über jtag. Wenn Du aber eine fertige Lib verwendest, dann 
ist das natürlich kein Problem für Dich.

Viele Grüße, Stefan

von H.Joachim S. (crazyhorse)


Lesenswert?

Hm, ich kann mit deiner Software nicht viel anfangen...

Wie regelst du den Empfang? Sollte per Interrupt geschehen.
Leg dir 4 message-Variablen (2x Receive, 2xTransmit) an.

typedef struct
{   unsigned int  id;
    unsigned char rtr;
    unsigned char length;
    unsigned char data[8];
} CANMessage;

In der jeweiligen ISR liest du den MCP2515 aus:
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{CAN_sw=0;                          //CS0 verwenden
 can_get_message(&CAN0Receive);
 CAN0MessageReceived=1;             //zeigt an, dass auf CAN0 empfangen 
wurde
}

// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{CAN_sw=1;                          //CS1 verwenden
 can_get_message(&CAN1Receive);
 CAN1MessageReceived=1;
}


In der main fragst du die beiden flags (CAN0MessageReceived, 
CAN1MessageReceived) ab und kannst dann entsprechend reagieren. Im 
einfachsten Fall erst mal auf dem jeweiligen anderen Kanal senden.
Da die Interrupts ohne Rücksicht auf Verluste auf du SPI zugreifen, 
musst du diese beim Senden sperren.

if (CAN0MessageReceived)
   {CAN1Transmit=CAN0Received;
    CAN0MessageReceived=0;
    #asm ("cli")
    CAN_sw=1;
    can_send_message (&CAN1Transmit);
    #asm ("sei")
    }

entsprechend für CAN1MessageReceived.

Viel mehr brauchst du erstmal gar nicht.

von Thomas F. (igel)


Lesenswert?

H.Joachim S. schrieb:
> Da die Interrupts ohne Rücksicht auf Verluste auf du SPI zugreifen,
> musst du diese beim Senden sperren.

Das trifft doch nur dann zu wenn der byteweise SPI-Transfer auch 
SPI-interruptgesteuert die Bytes rausschiebt? Oder denke ich gerade 
falsch?

von H.Joachim S. (crazyhorse)


Lesenswert?

Nö, der Interrupt des MCP2515 löst die SPI-Aktionen aus. SPI selbst 
läuft ohne Interrupt.

von Thomas F. (igel)


Lesenswert?

H.Joachim S. schrieb:
> SPI selbst läuft ohne Interrupt.

Wenn man die komplette CAN-Botschaft "in einem Rutsch" per SPI abholt 
(RX) dann ist das Programm ja allein damit beschäftigt. Dann kann es 
nicht zeitgleich einen SPI-Senden-Task starten der dann das Empfangen 
stört. Somit braucht die Senderoutine nicht extra gesperrt werden, so 
mein Gedanke.

Außer man startet den SPI-TX-Task im CAN-Interrupt. Aber das ist dann 
doch eher schlechter Stil denn so eine "Langzeitroutine" würde man doch 
eher aus der Main heraus starten?

von H.Joachim S. (crazyhorse)


Lesenswert?

-senden zum MCP läuft gerade
-externer Interrupt kommt
-und dann knallt dir die Leseroutine komplett dazwischen

von Olli Z. (z80freak)


Lesenswert?

Ich sehe noch nicht den funktionalen Unterschied zwischen meine 
Sende/Leseroutine, getriggert durch lesen der INT vom Shield und dem 
triggern einer ISR. Letztlich muss eine Botschaft gelesen und wieder 
rausgeschrieben werden.
Ich gehe davon aus, das die Lib nach dem auslesen der Botschaft, den INT 
wieder hochzieht. Laut Datenblatt soll das MCP wphl selbst machen, wenn 
eine Interruptbedingung intern wieder geklärt wurde. Man kann das aber 
wohl auch per Statusregister machen.

Die Routine wird doch nicht schneller oder stabiler durch den ISR im 
Atmega?

Aber Joachim, vielleicht sendest Du mir mal Deinen Code und ich probiere 
es aus. Dann wird man ja sehen. Vielleicht irre ich mich ja.

von H.Joachim S. (crazyhorse)


Lesenswert?

Olli Z. schrieb:
> vielleicht sendest Du mir mal Deinen Code

Kann ich nicht machen, ist aus einem noch laufenden Produkt mmit ein 
paar relativ geheimen CAN-Infos.
Könnte ja mal versuchen, alle interna rauszuschmeissen und das für einen 
Mega328 zu compilieren.

von Thomas F. (igel)


Lesenswert?

H.Joachim S. schrieb:
> -senden zum MCP läuft gerade
> -externer Interrupt kommt
> -und dann knallt dir die Leseroutine komplett dazwischen

Ach so.

Ich setze bei mir im Ext.Interrupt vom MCP2515 nur mal schnell ein 
Flag-Bit und nicht mehr, wie man es halt so hier gelernt hat.
Die eigentliche Leseroutine rufe ich dann erst in der Main aufgrund des 
Flags auf.
So habe ich das Problem nicht.

von H.Joachim S. (crazyhorse)


Lesenswert?

Kann man so machen - aber der MCP2515 hat nur 2 rec-buffer. Wenn du in 
der main sicherstellen kannst, dass nichts verloren geht?
Für mich hat der Empfang Priorität, damit nichts verlorengeht. Und das 
tütet man dann bei Bedarf in einen MC-internen Buffer ein und kann es 
(zur Not mit etwas Zeitversatz) immer noch bearbeiten. Läuft der 
MCP-Buffer über, sind Botschaften definitiv verloren.

von Olli Z. (z80freak)


Lesenswert?

Und genau diesen Überlauf müsste man doch erkennen können? Wie ist das 
mit dem REC oder TEC Fehlerzähler?

von Thomas F. (igel)


Lesenswert?

Olli Z. schrieb:
> Und genau diesen Überlauf müsste man doch erkennen können? Wie ist das
> mit dem REC oder TEC Fehlerzähler?

Ja, aber nicht mit TEC und REC, sondern mit RX0OVR und RX1OVR.

TEC und REC zählen Übertragungsfehler auf dem Bus.

von H.Joachim S. (crazyhorse)


Lesenswert?

Aber futsch ist futsch, auch wenn man es nachträglich erkennen kann.

Thomas F. schrieb:
> Ich setze bei mir im Ext.Interrupt vom MCP2515 nur mal schnell ein
> Flag-Bit und nicht mehr,

Halte ich in diesem Fall für falsch, da kannste dir den Interrupt gleich 
ganz sparen (statt dein flag in der main zu pollen, könntest du gleich 
zyklisch den Pegel am Int-Pin pollen). Vorteilhaft wäre in diesem Fall 
sogar, dass man jeden beliebigen Pin nehmen kann.
Also: alle Ereignisse vom MCP so schnell wie möglich holen (umso mehr 
gilt das, wenn man mehr als einen dran hat), quittieren und 
wegschaufeln. Was damit dann passiert hat Zeit und kann in der main 
erledigt werden.

von Thomas F. (igel)


Lesenswert?

H.Joachim S. schrieb:
> Thomas F. schrieb:
>> Ich setze bei mir im Ext.Interrupt vom MCP2515 nur mal schnell ein
>> Flag-Bit und nicht mehr,
>
> Halte ich in diesem Fall für falsch, da kannste dir den Interrupt gleich
> ganz sparen (statt dein flag in der main zu pollen, könntest du gleich
> zyklisch den Pegel am Int-Pin pollen). Vorteilhaft wäre in diesem Fall
> sogar, dass man jeden beliebigen Pin nehmen kann.

Stimmt.
Das Platinenlayout mit Ext.Int war halt zuerst da.
Und wenn eine Firmware mal läuft dann kopiert man die gerne immer wieder 
ins nächste Projekt mit rein;-)

von H.Joachim S. (crazyhorse)


Lesenswert?

Thomas F. schrieb:
> Das Platinenlayout mit Ext.Int war halt zuerst da.

So ist es ja auch erstmal richtig.
Dann muss man es nur noch richtig benutzen :-).
Je nach Buslast und Busgeschwindigkeit trennen sich dann irgendwann die 
optimalen von den suboptimalen Lösungen.
Aber ich verfolge das Thema MCP2515 auch nicht mehr aktiv. War damals ne 
gute Lösung, als Controller mit internem CAN selten und teuer waren. 
Heute ist es Gefrickel und teurer als gleich eine CPU mit CAN zu 
verwenden.

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Also, positive Rückmeldung von mir. Nachdem ich mich intensiv mit SPI 
und dem MCP beschäftigt hatte, etliche Libs und Sketches durchforscht, 
Datenblätter gelesen und viel viel mit dem LA debugged habe darf ich nun 
endlich Erfolg melden.

Ich habe mein Interface störungsfrei am laufen. Arduino Nano mit zwei 
MCP2515-Shields. Aktuell verwende ich 8 MHz SPI-Takt, aber bereits bei 1 
bzw. 2 MHz traten keine Störungen mehr auf.

Es war aber nicht der SPI-Takt entscheident, sondern die Abfrage- und 
Sendelogik. Hier musste ich die MCP-Lib etwas anpassen und in meiner 
main einen Ringpuffer verwenden.

Ich mache das ganze nicht interruptgesteuert, sondern einfach durch 
IO-Abfrage des INT-Signals. Wenn ich im weitern Verlauf des Projektes 
mehr Zeit in anderen Routinen verdaddeln muss, wäre eine 
Interrupt-Variante hilfreich damit wirklich keine Pakete verloren gehen. 
Da das ganze aber mehr auch ein Proof-Of-Concept war werde ich für 
komplexere Datenbehandlungen wohl eher auf einen CAN-Controller mit mehr 
Puffern zurückgreifen (SJA1000T) oder gleich einen STM32F3 oder F4 mit 
2xCAN on Board.

Habe aber viel gelernt dabei und danke Euch allen für die Unterstützung!

: Bearbeitet durch User
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
Noch kein Account? Hier anmelden.