Forum: Mikrocontroller und Digitale Elektronik CAN Verarbeitung mehrer Botschaften


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Decoder (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich habe hier eine Relaiskarte mit 20 Relais. Darauf werkelt ein 
STM32F303 und ein TJA1043. Jedes Relais hängt über einen ULN2803 am STM 
und kann ein und ausgeschaltet werden.

Vom PC aus sende ich via CAN Interface Daten an die Platine, dass klappt 
auch alles wunderbar.

Jetzt habe ich mehrere Botschaften, als Beispiel 0x10, 0x11, 0x12.

Aktuell hole ich die Daten bei Eintreffen des RxCallbackInterrupts ab 
und schreibe sie in "RxHeader" und "RxData"

Im Header befindet sich die ID der Botschaft, diese Werte ich im Haupt 
Programm aus. Aktuell in der Form hier:
1
    
2
if (RxHeader.StdId==0x10)
3
    {
4
      if (RxData[0] == 0x1)
5
      {
6
        sleep();
7
      }
8
    }

Jetzt Frage ich mich, wie man es richtig macht?
So erscheint mir etwas umständlich und es wird schnell unübersichtlich.

Ein Ansatz wäre eine SWITCH CASE Anweisung, die anhand der ID in eine 
zugehörige Funktion springt und dann werden da die Daten ausgewertet und 
die Ausgänge gesetzt.
1
    switch (RxHeader.StdId) 
2
    {
3
        case 0x10: 
4
        {
5
      //Heizung[0]=RxData;
6
        message_10();
7
        break;
8
        }
9
        case 0x11:  
10
        {
11
        //Heizung[1]=RxData;
12
        message_11();
13
        break;
14
        }
15
        default:           
16
      break;
17
    }

Gibt es eine bessere Lösung? Wie machen das die Profis?

von Achim M. (minifloat)


Bewertung
3 lesenswert
nicht lesenswert
Decoder schrieb:
> Gibt es eine bessere Lösung?

Kaum. Wird meist nicht anders gemacht.

Decoder schrieb:
> Wie machen das die Profis?

Profis™ klatschen gefühlt nochmal 2-3 Abstraktionsebenen dazwischen.

Deine beiden Ansätze oder eine Kombination daraus (z.B. Switch-Case und 
Neue-Daten-Sind-Da-Flag für periodische Task) sind tausendfach genau so 
implementiert.

: Bearbeitet durch User
von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
Decoder schrieb:
> Gibt es eine bessere Lösung? Wie machen das die Profis?

Das ist gut so. Wenn es mehr werden, Zeit eine Rolle spielt oder das 
ganze modular bleiben soll, kann man auch mit Funktionspointer-Arrays 
arbeiten. Aber das nur, wenn man Kanonen braucht, nicht bei Spatzen:
1
int fnc=RxHeader.StdId;
2
3
    if(fnc<countof(fncArr)) 
4
    {
5
        if(fncArr[fnc]) {fncArr[fnc]();}
6
    }

Hier können Module zur Laufzeit beliebige Funktionen einhängen. Noch 
wichtiger: die Main braucht die Funktionen nicht zu kennen!

: Bearbeitet durch User
von Decoder (Gast)


Bewertung
0 lesenswert
nicht lesenswert
OK danke für die schnellen Antworten, dann geh ich mal so ins Rennen :).

Viele Grüße

von Thomas F. (igel)


Bewertung
0 lesenswert
nicht lesenswert
Wären hier nicht Funktionszeiger angebracht?

https://www.mikrocontroller.net/articles/Funktionszeiger_in_C

von Programmierer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Als erstes benutzt man die ID-Filter-Funktion der Hardware. Man trägt 
die gewünschten IDs in eine Liste in der Peripherie ein, und nur wenn 
eine Nachricht mit einer dieser IDs ankommt, wird der Interrupt 
überhaupt aufgerufen. Dabei übergibt die Hardware eine Nummer, die 
angibt, welcher Eintrag der Filter-Liste zugeschlagen hat. Diese Nummer 
kann man dann nutzen, um auf ein Array aus Funktionszeigern zuzugreifen, 
um darüber die Behandlung für die Nachricht aufzurufen. So kann man in 
O(1) direkt die korrekte Behandlung ausführen.

A. S. schrieb:
> Zeit eine Rolle spielt

Compiler können switch-case sehr gut optimieren (binäre Suche oder Jump 
Tables bei kontinuierlichen IDs).

von Rudolph R. (rudolph)


Bewertung
0 lesenswert
nicht lesenswert
Decoder schrieb:
> ich habe hier eine Relaiskarte mit 20 Relais. Darauf werkelt ein
> STM32F303 und ein TJA1043. Jedes Relais hängt über einen ULN2803 am STM
> und kann ein und ausgeschaltet werden.

Ich habe Relaiskarten mit 20 Relais. Da ist ein ATSAMC21G18A drauf und 
ein TJA1050T. Jedes Relais hängt über einen BCR135 am ATSAM.
Und mit einem 5pol Dip-Schalter kann man 32 von diesen Platinen an einen 
Bus hängen.

Mein Controller hat einen Empfangs-FIFO und Filter, das hat der STM32 
grundsätzlich auch, wenn auch nur mit 3 Botschaften im FIFO und 14 
Filtern.

Ich benutze gar keinen Interrupt, meine Hauptschleife arbeitet mit 5ms 
Systick Ticks und ruft CAN_Read() und CAN_Write() auf.
Warum 5ms? Copy-Paste, funktioniert so, geht sicher auch langsamer.

CAN_Read() schaut ob was im FIFO ist, prüft ob die ID mit der 
Empfangs-Botschaft für die Relais übereinstimmt und ob die Länge richtig 
ist.
Ein Bit von meiner 3-Byte Botschaft bestimmt ob die Relais gesetzt oder 
nur der Zustand abgefragt werden soll, ist dieses Bit gesetzt werden die 
Daten kopiert und die Funkion aufgerufen welche die Relais setzt.
Und es wird ein Flag gesetzt das eine Antwort-Botschaft gesendet werden 
darf.

Wenn ich einen Interrupt benutzen würde, dann würde ich das praktisch 
auch genau so machen, 20 I/Os zu setzen kostet doch quasi keine Zeit.

Wobei mir eine Anwendung einfällt bei der ich das vor Jahren mal hatte, 
ein AT90CAN32 und 8 8-Bit Latches für 64 Relais.
Der komplette CAN-Interrupt mit dem Abladen der 8 Bytes in die 8 Latches 
über einen Port und vier Adress-Leitungen hat mit dem 16MHz Controller 
knapp 1µs gedauert.

von H.Joachim S. (crazyhorse)


Bewertung
0 lesenswert
nicht lesenswert
3fach-FIFO und nur alle 5ms nachschauen? Sportlich.

von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
Decoder schrieb:
> Ein Ansatz wäre eine SWITCH CASE Anweisung

Ist besser lesbar als if/else Monster und der Compiler kann es auch 
besser optimieren (Sprungtabelle).
Man kann aber auch gleich den Wert als Index auf ein Array aus 
Funktionspointern benutzen.

von Rudolph R. (rudolph)


Bewertung
0 lesenswert
nicht lesenswert
H.Joachim S. schrieb:
> 3fach-FIFO und nur alle 5ms nachschauen? Sportlich.

Für eine Relais-Karte? Eher nicht so, die Botschaft darf eh nicht so oft 
kommen.
Aber das ist nicht was ich geschrieben habe, der STM32 hat nur einen 
3-fach FIFO.
Der ATSAMC21 hat einen 64-fach FIFO, der läuft in 5ms nicht über.

von Auch Karl, ein anderer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Achim M. schrieb:
> Decoder schrieb:
>> Gibt es eine bessere Lösung?
>
> Kaum. Wird meist nicht anders gemacht.
>
Besser ist abhängig vom Bedarf. In diesem Fall mit festen ids und ohne 
nennenswerte zeitliche Anforderungen ist das kaum besser zu machen. 
Sowohl aus Performance Sicht als auch aus Lesbarkeitssicht.

Der bxCan im stm hat ein Feld in einem Register das den Index des 
getroffenen Filters anzeigt. Damit kann man direkt in eine Sprungtabelle 
gehen. Lohnt aber hier sicher nicht.
> Decoder schrieb:
>> Wie machen das die Profis?
>
Profi ist die Definition für "ich nehme geld dafür".
> Profis™ klatschen gefühlt nochmal 2-3 Abstraktionsebenen dazwischen.
Du hast ein - zwischen deiner 23 ;-)
> Deine beiden Ansätze oder eine Kombination daraus (z.B. Switch-Case und
> Neue-Daten-Sind-Da-Flag für periodische Task) sind tausendfach genau so
> implementiert.
Yup. Wenn damit alle Anforderungen erfüllt sind braucht es nichts 
anderes.

von Programmierer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Rudolph R. schrieb:
> Für eine Relais-Karte? Eher nicht so, die Botschaft darf eh nicht so oft
> kommen.

Wenn aber eine andere Nachricht zu oft kommt, wird die langsame 
Relais-Botschaft überrannt und wird nicht mehr ausgewertet.

Ich würde das immer über Interrupts und Auswertung des Filter-Index 
machen...

von Marc V. (Firma: Vescomp) (logarithmus)


Bewertung
-1 lesenswert
nicht lesenswert
Rudolph R. schrieb:
> Der komplette CAN-Interrupt mit dem Abladen der 8 Bytes in die 8 Latches
> über einen Port und vier Adress-Leitungen hat mit dem 16MHz Controller
> knapp 1µs gedauert.

 Sicher.
 Zähle mal die benötigten Takte zusammen.

von Rudolph R. (rudolph)


Bewertung
0 lesenswert
nicht lesenswert
Programmierer schrieb:
> Rudolph R. schrieb:
>> Für eine Relais-Karte? Eher nicht so, die Botschaft darf eh nicht so oft
>> kommen.
>
> Wenn aber eine andere Nachricht zu oft kommt, wird die langsame
> Relais-Botschaft überrannt und wird nicht mehr ausgewertet.
>
> Ich würde das immer über Interrupts und Auswertung des Filter-Index
> machen...

Wie ich schrieb, würde ich das mit Interrupt machen, würde ich die 
Botschaft direkt im Interrupt verarbeiten.
Aber ich habe das Problem mit dem 3-fach FIFO ja gar nicht.

Oder man packt die anderen Botschaften in den zweiten FIFO, die 
Relais-Botschaft zu oft zu senden macht sowieso keinen Sinn.
Und wenn man die Relais-Botschaft mit 10ms zyklisch sendet läuft auch 
ein 3-fach FIFO nicht über den man alle 5ms abfragt.

von Rudolph R. (rudolph)


Bewertung
0 lesenswert
nicht lesenswert
Marc V. schrieb:
> Rudolph R. schrieb:
>> Der komplette CAN-Interrupt mit dem Abladen der 8 Bytes in die 8 Latches
>> über einen Port und vier Adress-Leitungen hat mit dem 16MHz Controller
>> knapp 1µs gedauert.
>
>  Sicher.
>  Zähle mal die benötigten Takte zusammen.

Du hast Recht, das sind nur 16 Takte, da war die Erinnerung zu nebulös.
Die 1µs stimmen aber und was das Ding gemacht hat, das war also eher die 
Zeit die es gedauert hat die Latches zu füllen, so ohne den Interrupt an 
sich und ohne das Auslesen der Daten aus dem CAN-Controller.

Ich weiss noch das ich gemessen habe wie lange das Setzen der Latches 
dauert und das ich dann den Code aus der Hauptschleife in den Interrupt 
verschoben habe.

Such...
Das war so um 2012:
1
void handle_mob1(void) // Relais
2
{
3
  CANPAGE = (1<<4); // select MOB1
4
  CANSTMOB &= ~(1<<RXOK); // clear interrupt flag
5
6
  buffer_relais[0] = CANMSG;
7
  buffer_relais[1] = CANMSG;
8
  buffer_relais[2] = CANMSG;
9
  buffer_relais[3] = CANMSG;
10
  buffer_relais[4] = CANMSG;
11
  buffer_relais[5] = CANMSG;
12
  buffer_relais[6] = CANMSG;
13
  buffer_relais[7] = CANMSG;
14
15
  send_relais = 42;
16
17
  // Daten für TB-1 untere Reihe Laden
18
  PORTC = buffer_relais[1];
19
  PORTG |= (1 << PG4);
20
21
  // Daten für TB-1 obere Reihe Laden
22
  PORTC = buffer_relais[0];
23
  PORTG &= ~(1 << PG4);
24
25
  // Daten für TB-2 untere Reihe Laden
26
  PORTC = buffer_relais[3];
27
  PORTG |= (1 << PG3);
28
29
  // Daten für TB-2 obere Reihe Laden
30
  PORTC = buffer_relais[2];
31
  PORTG &= ~(1 << PG3);
32
33
  // Daten für TB-3 untere Reihe Laden
34
  PORTC = buffer_relais[5];
35
  PORTD |= (1 << PD4);
36
37
  // Daten für TB-3 obere Reihe Laden
38
  PORTC = buffer_relais[4];
39
  PORTD &= ~(1 << PD4);
40
41
  // Daten für TB-4 untere Reihe Laden
42
  PORTC = buffer_relais[7];
43
  PORTD |= (1 << PD7);
44
45
  // Daten für TB-4 obere Reihe Laden
46
  PORTC = buffer_relais[6];
47
  PORTD &= ~(1 << PD7);
48
49
  CANCDMOB = (1<<CONMOB1) | (1<<DLC3); // set to receive, 8-bytes in message
50
}

Dazu kommt noch die eigentliche Interrupt Funktion die feststellt das in 
Message-Objekt 1 eine Botschaft angekommen ist und diese Funktion 
aufruft.
In Summe sind das so <5µs.

Der eigentliche Punkt ist aber, eine Handvoll I/Os direkt zu setzen 
kostet nicht nennenswert Zeit.
Und im Zweifel misst man wie lange das dauert und entscheidet sich dann.

von Dirk (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ich mache es identisch bei einem System ohne Messagesystem. Ich glaube 
Du musst noch die richtige Priorisierung reinbringen. Du musst auch kein 
InteractionLayer oder Networkmanagement reinbringen oder?

https://de.wikipedia.org/wiki/Controller_Area_Network#Arbitrierung,_Priorit%C3%A4t

Hier ein Auszug:

>Die Nachricht mit dem niedrigsten Identifier darf immer übertragen werden.
>Für die Übertragung von zeitkritischen Nachrichten kann also ein Identifier 
>hoher Priorität (= niedrige ID, z. B. 0x001; 0x000 für Netzmanagement –
>NMT) vergeben werden

von Bad U. (bad_urban)


Bewertung
0 lesenswert
nicht lesenswert
Rudolph R. schrieb:
> Wie ich schrieb, würde ich das mit Interrupt machen, würde ich die
> Botschaft direkt im Interrupt verarbeiten.

Das ist gar nicht notwendig. Ich versuche auch die ISR möglichst kurz zu 
halten.

Ich arbeite mit einem Timer der den uC alle 10ms weckt. Dabei wird 
einmal die Mainloop durchlaufen und auch auf Nachrichten geprüft und 
abgearbeitet.

Über den CAN-Interrupt geschieht nix anderes, als dass der uC azyklisch 
geweckt wird und die Schleife einmal durchläuft und dabei auch die 
Nachrichten verarbeitet.

von Dirk (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>Jetzt habe ich mehrere Botschaften, als Beispiel 0x10, 0x11, 0x12.

Hab Dich etwas falsch verstanden, ganz so mache ich es nicht. Warum hast 
Du soviele Identifier?

Im IT CAN RX Callback nehme ich einfach die ID Payload und gebe es auf 
meine Ausgangsregister bzw. auf zwei 16Bit Portexpander mit I2C Bus, 
weil ich nicht genug I/O's am STM fei hatte. Der Wert des Payloads gibt 
an, ob die Relais geschaltet werden oder nicht. Ich sende danach vom STM 
zum CAN Bus noch den Zustand der Relais, weil ich weitere Teilnehmer 
dran hab, welche den Zustand benötigen.
1
RXDATA[8]
2
    
3
if (RxHeader.StdId==0x10)
4
{
5
    PORTA = RXDATA[0]; //PORTA ist der erste PORT vom 16Bit I2C Expander
6
    PORTB = RXDATA[1]; //PORTB ist der zweite PORT vom 16Bit I2C Expander
7
}

Reicht das nicht auch aus in deinem Fall?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.