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
case0x10:
4
{
5
//Heizung[0]=RxData;
6
message_10();
7
break;
8
}
9
case0x11:
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?
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.
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
intfnc=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!
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).
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.
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.
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.
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.
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...
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.
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.
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.
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
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.
>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