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?
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.
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
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.
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.
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:
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
voidloop()
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?
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...
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.
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.
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
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.
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?
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?
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.
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.
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.
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.
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.
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.
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;-)
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.
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!