Ich programmiere gerade einen ATSAMC21 Microcontroller, um ihn CAN-FD
fähig zu machen.
Um den Code übersichtlich zu halten verwende ich gerne "union" in C. So
habe ich das Interrupt-Flag Register wie folgt geschrieben:
1
typedefunion{
2
struct{
3
…// 32 bits muss ich hier glaube ich nicht auflisten
4
uint32_tRF0N:1;//Bit 10: Neue CAN Nachricht im FIFO 0 Buffer-Array abgelegt
5
uint32_tRF1N:1;//Bit 11: Neue CAN Nachricht im FIFO 1 Buffer-Array abgelegt
6
…
7
}bit;
8
uint32_treg;
9
}CAN_IR_TypecanIrType;
Auf diese weiße kann man einfach einzelne Bits mit
1
if(canIrType.RF0N==1){...}
abfragen oder mit
1
canIrType.RF1N=0;
setzen.
Sobald ein Interrupt ausgelöst wird, muss man ja bei den meisten
Microcontrollern das jeweilige Flag löschen, indem man dem Bit des
jeweiligen Flags erneut eine 1 zuweist. Hier funktioniert die typedef
union Methode allerdings nicht mehr.
Folgender code funktioniert einwandfrei:
1
CAN_Handler(){
2
if(canIrType.RF0N){
3
//... do something you want :)
4
canIrType.reg=1<<10;//Bit löschen, so funktioniert es einwandfrei
5
}
6
}
Wenn ich die Struktur der "typedef union" weiter anwenden will,
funktioniert es allerdings nicht mehr:
1
CAN_Handler(){
2
if(canIrType.RF0N){
3
//... do something you want :)
4
canIrType.RF0N=1;// Bit löschen, funktioniert nicht wie erwartet
5
}
6
}
Diese Weise löscht alle bits und ist vermutlich folgendem Code sehr
ähnlich.
1
CAN_Handler(){
2
if(canIrType.RF0N){
3
//... do something you want :)
4
canIrType.reg|=(1<<10);
5
// löscht auch alle anderen bits da canIrType.reg = canIrType.reg | (1<<10); alle bits, die bereits auf 1 sind werden wieder auf 1 gesetzt.
6
7
}
8
}
.
.
.
Kann mir irgendjemand erklären, wieso dem so ist?
Wieso wird nicht einfach das einzelne Bit mit
1
canIrType.RF0N=1;
gelöscht?
p.S.: vermutlich liegt der Grund in der Funktionsweise von C bzw typedef
union und wäre vermutlich im Thema Software&Code besser aufgehoben. Ich
habe bewusst diesen Thread gewählt, da dieses Problem hier
möglicherweise schon mehreren den Tag vermiest hat.
Rupert B. schrieb:> Um den Code übersichtlich zu halten verwende ich gerne "union" in C.
Sinn und Zweck einer Union ist die gemeinsame Verwendung von Speicher
für verschiedene Objekte die nicht alle zur selben Zeit gebraucht
werden.
Dafür sollte man es auch nutzen. Mit Übersichtlichkeit hat das nichts zu
tun.
Rupert B. schrieb:> canIrType.reg |= (1<<10);
Und das funktioniert?
Rupert B. schrieb:> Wieso wird nicht einfach das einzelne Bit mitcanIrType.RF0N = 1;> gelöscht?
Weil der Prozessor keine einzelnen Bit lesen/schreiben kann. Der
C-Compiler baut dir einen Code, welcher das ganze Register liest, das
eine Bit auf 1 setzt, und das Ergebnis zurückschreibt. Dabei werden ggf.
auch andere Bits auf 1 gesetzt, was die Peripherie ggf. nicht mag.
Insbesondere kann es sein, dass seit dem Anfang der ISR weitere
Interrupt-Bits auf 1 gesetzt wurden, die du somit löschst und verpasst!
Mit union und typedef hat das ganze übrigens nichts zu tun, nur mit
Bitfields.
Niklas G. schrieb:> Rupert B. schrieb:>> canIrType.reg |= (1<<10);>> Und das funktioniert?
Warum nicht?
Rupert B. schrieb:> canIrType.reg = 1<<10; //Bit löschen, so funktioniert es einwandfrei
Kommt mir allerdings komisch vor.
Rupert B. schrieb:> canIrType.RF0N = 1; // Bit löschen, funktioniert nicht wie erwartet
Sollte das laut deinem struct nicht canIrType.bit.RF0N sein?
Volker S. schrieb:> Warum nicht?
Weil es praktisch äquivalent zum Bitfield-Fall sein sollte, und weil man
normalerweise kein Read-Modify-Write auf Interrupt-Registern macht.
Volker S. schrieb:> Kommt mir allerdings komisch vor.
Nö, ganz normal bei solchen Interrupt-Flag-Registern.
Rupert B. schrieb:> Um den Code übersichtlich zu halten verwende ich gerne "union" in C. So> habe ich das Interrupt-Flag Register wie folgt geschrieben:
...
> Auf diese weiße kann man einfach einzelne Bits mit
weiße ?
Also: du kannst ein Bit in einem Hardware-Register nicht auf irgend eine
Weise schreiben, sondern du kannst das Hardwareregister nur korrekt
benutzen oder wenn nicht, einen Fehler machen.
Also lies das Refmanual nochmal gründlich. Bei manchen Cores muß man
nämlich sehr drauf achten, in welcher Reihenfolge und auf welche Weise
man etwas ausliest oder beschreibt. Achte besonders auf Dinge wie
Write1only Bits, also solche, die etwas bewirken, wenn sie mit einer 1
geschrieben werden.
Oftmals ist es gerade bei Status-Bits so, daß man sie nach dem Lesen
dadurch löscht, indem man auf die im Register gelesene 1 eine 1
draufschreibt.
Vermutlichst hast du es bei deinem Bemühen, irgendwas auf eine dir am
besten gefallende Art in einem Struct, Union o.ä. zu formulieren, getan
wie der Elefant im Porzellanladen: Was meinst du, wie so ein bitweiser
Zugriff auf ein Bit in einem struct stattfindet? Laden, Maskieren,
Setzen oder nicht, Zurückschreiben. Das kann bei HW-Registern ziemlich
in die Hose gehen. Also lies im Refman nach.
W.S.
Ja ich habe das Refmanual gelesen und da heißt es, dass man das
jeweilige Bit im Register erneut mit 1 belegen muss, eine Zuweisung von
0 auf dieses Bit bewirkt nichts, aus diesem Grund funktioniert … =
(1<<10);
Ja es müsste canIrType.bit.RF1N heißen :D sry :)
Das ist auch alles kein Problem, es funktioniert ja auch alles bei mir,
nur ging das mit den unions nicht.
Ich verstehe auch, wofür man unions verwendet ich weiß allerdings nicht
wie sie hinter der Fassade funktionieren. Ich müsste vermutlich mal in
Assembler nachsehen, wie Unions vom Compiler verarbeitet werden, wenn
ich ein einzelnes Bit ändern will.
Vielen Dank für die vielen Antworten.
Rupert B. schrieb:> Das ist auch alles kein Problem, es funktioniert ja auch alles bei mir,> nur ging das mit den unions nicht.
Nochmal: Das richtige Stichwort ist: Bitfields.
Rupert B. schrieb:> Kann mir irgendjemand erklären, wieso dem so ist?
Ein Compiler kann nicht wissen, daß Interruptbits auf eine völlig
verrückte und unlogische Weise gelöscht werden müssen. Er setzt ein Bit,
wie auf jeder ganz normalen SRAM-Adresse mit Read+Or+Write. Daß es
fehlschlägt, liegt nicht am Compiler, sondern an der Hardware.
Rupert B. schrieb:> Ich programmiere gerade einen ATSAMC21 Microcontroller, um ihn CAN-FD> fähig zu machen.>> Um den Code übersichtlich zu halten verwende ich gerne "union" in C. So> habe ich das Interrupt-Flag Register wie folgt geschrieben:>>
1
>typedefunion{
2
>struct{
3
>…// 32 bits muss ich hier glaube ich nicht auflisten
4
>uint32_tRF0N:1;//Bit 10: Neue CAN Nachricht im FIFO 0
5
>Buffer-Arrayabgelegt
6
>uint32_tRF1N:1;//Bit 11: Neue CAN Nachricht im FIFO 1
7
>Buffer-Arrayabgelegt
8
>…
9
>}bit;
10
>uint32_treg;
11
>}CAN_IR_TypecanIrType;
12
>
Bitfields für SFR zu benutzen, ist eine schlechte Idee. Denn die
Abbildung der Bits in eine Byte-Position ist implementation-defined.
Zwar wechseln Compiler so schnell nicht ihre interne Darstellung, aber
trotzdem muss Du erstmal prüfen, ob das ABI so ist, wie Du glaubst.
Bei ARM ganz besonders doof, weil Du ihn als Little- oder Big-Endian
betreiben kannst. Die einzig korrekte Art, auf spezielle Bits in einem
SFR zuzugereifen sind die (1<<N)-Shifts, die Du auch benutzt und die
auch funktionieren.
Peter D. schrieb:> Ein Compiler kann nicht wissen, daß Interruptbits auf eine völlig> verrückte und unlogische Weise gelöscht werden müssen.
Eine read-modify-write Technik zum Zurücksetzen von Interrupt Flags mag
vielleicht natürlich und logisch erscheinen. Aber tatsächlich wäre sie
ein ernster Konstruktionsfehler.
Wilhelm M. schrieb:> Zwar wechseln Compiler so schnell nicht ihre interne Darstellung, aber> trotzdem muss Du erstmal prüfen, ob das ABI so ist, wie Du glaubst.
das ist hier doch gar nicht das Problem.
Hier geht's darum, dass geschriebene Bits in Hardware-Registern
bisweilen eine völlig andere Bedeutung haben als gelesene.
Geht man hier mit Bitfeldern drauf los, wird der Compiler das gesamte
Register lesen, das gewünschte Bit modifizieren und den Registerinhalt
wieder zurückschreiben. Schlicht davon ausgehend, dass es keine
Auswirkung haben wird, um das "eigentlich gemeinte" Bit herum genau die
Werte zurück zu schreiben, die vorher gelesen wurden.
Die Annahme ist hier fatal. Schreiben und Lesen lösen u.U. völlig
unterschiedliche Aktionen aus.
Markus F. schrieb:> Wilhelm M. schrieb:>> Zwar wechseln Compiler so schnell nicht ihre interne Darstellung, aber>> trotzdem muss Du erstmal prüfen, ob das ABI so ist, wie Du glaubst.>> das ist hier doch gar nicht das Problem.>> Hier geht's darum, dass geschriebene Bits in Hardware-Registern> bisweilen eine völlig andere Bedeutung haben als gelesene.
Das wurde oben doch schon gesagt.
Es kommt das Problem hinzu, dass ich ohne Angabe aus dm ABI gar nicht
weiß, an welcher Stelle das Bit des Bitfields überhaupt ist.
Wilhelm M. schrieb:> Es kommt das Problem hinzu, dass ich ohne Angabe aus dm ABI gar nicht> weiß, an welcher Stelle das Bit des Bitfields überhaupt ist.
Nun ja.
Wenn Du nicht weisst, wie man mit Bitfeldern das gewünschte Bit trifft,
solltest Du vielleicht lieber was anderes machen. Gärtner zum Beispiel
ist ja auch ein schöner Beruf...
Markus F. schrieb:> Wilhelm M. schrieb:>> Es kommt das Problem hinzu, dass ich ohne Angabe aus dm ABI gar nicht>> weiß, an welcher Stelle das Bit des Bitfields überhaupt ist.>> Nun ja.
So etwas heißt im C-Standard eben implementation-defined, daher ABI.
> Wenn Du nicht weisst, wie man mit Bitfeldern das gewünschte Bit trifft,> solltest Du vielleicht lieber was anderes machen. Gärtner zum Beispiel> ist ja auch ein schöner Beruf...
Und Du solltest vielleicht mal lesen lernen ;-)
Damit Du siehst, welche unsinnige Idee es ist, Bitfileds für Bits in SFR
zu benutzen, empfehle ich Dir, den Code einmal mit -mbig-endian und
einmal mit mlittle-endian (gcc / g++) zu kompilieren.
Schau mal:
https://godbolt.org/z/XBqt4A
Hinzukommt natürlich die schon angesprochene Umsetzung der Zuweisungen
zu RMW-And/Or-Sequenzen.
Wilhelm M. schrieb:> Damit Du siehst, welche unsinnige Idee es ist, Bitfileds für Bits in SFR> zu benutzen, empfehle ich Dir,
mal in die von Microchip gelieferten Headerfiles für die PICs zu
schauen. Da gibt es tausende solcher Bitfelder für hunderte von PICs,
sowohl für den XC8 als auch für den XC16/gcc, die sicher auch von den
Programmierern genutzt werden.
Das klappt natürlich nicht bei queeren Prozessoren, die spontan ihre
Endianess ändern.
MfG Klaus
Klaus schrieb:> Wilhelm M. schrieb:>> Damit Du siehst, welche unsinnige Idee es ist, Bitfileds für Bits in SFR>> zu benutzen, empfehle ich Dir,>> mal in die von Microchip gelieferten Headerfiles für die PICs zu> schauen. Da gibt es tausende solcher Bitfelder für hunderte von PICs,> sowohl für den XC8 als auch für den XC16/gcc, die sicher auch von den> Programmierern genutzt werden.
Sicher, weiß ich, aber es bleibt eben implementation-defined, und damit
vom Compiler-ABI abhängig. Nur weil MicroChip das macht, ist es deswegen
nicht besser.
Die Implementierungen von Bitfeldern durch Compiler sind primär von der
Bytereihenfolge des Prozessors abhängig, sowie vom erforderlichem
Alignment, und können sich auch darin unterscheiden, wie mit Wortgrenzen
umgegangen wird.
In Code für Mikrocontroller, der runter auf die I/O-Register geht, ist
eine Plattformabhängigkeit inhärent gegeben. Wortübergreifende Felder
gibt es da nicht und die Bytereihenfolge ist vorgegeben, bimodale
Systeme sind selten, selbst wenn manche Prozessorarchitekturen
grundsätzlich bimodal ausgelegt sind. Portabilität des Lowlevel-Codes
auf andere Plattformen ist meist nicht erforderlich.
Ergo: Man muss nicht päpstlicher sein als der Papst.
Ich würde das komplett bleiben lassen.
Sobald mehr als 1 Bit gleichzeitig gesetzt werden soll wird der Code
umständlicher und aufgeblasener, denn da die Register als volatile
definiert werden müssen (was Du übrigens versäumt hast) würde er für
jedes einzelne Bit einmal den ganzen read-modify-write Tanz aufführen
anstatt einfach das gewünschte Bitmuster komplett in einem Rutsch
reinzuschreiben.
Und im Code sieht es auch kein bisschen sauberer oder aufgeräumter aus,
nur ein klein bisschen anders aber keineswegs einfacher und auch nicht
kürzer und auch nicht übersichtlicher.
Also nimm die Originalheader und benutze sie genau so wie alle anderen
das auch tun und wie es vorgesehen ist, das gibt die wenigsten Probleme
und kostet am wenigsten Zeit.
A. K. schrieb:> Eine read-modify-write Technik zum Zurücksetzen von Interrupt Flags mag> vielleicht natürlich und logisch erscheinen. Aber tatsächlich wäre sie> ein ernster Konstruktionsfehler.
Warum?
Z.B. beim 8051 funktioniert es einwandfrei. Der Befehlssatz muß nur
AND/OR auf SFRs unterstützen, damit die Operation atomar ist.
Bei vielen ARM-CPUs sind spezielle Set- und Clear-Adressen zum atomaren
Setzen verfügbar. Da wäre es auch kein Problem, das Interrupt löschen
logisch richtig zu implementieren.
Peter D. schrieb:> A. K. schrieb:>> Eine read-modify-write Technik zum Zurücksetzen von Interrupt Flags mag>> vielleicht natürlich und logisch erscheinen. Aber tatsächlich wäre sie>> ein ernster Konstruktionsfehler.>> Warum?
Weil es bei den Flag-Registern einfach falsch ist: dort würden dann alle
die gesetzten Flags durch das erneute Setzen zurückgesetzt.(s.a. oben
den Compiler-Explorer, den ich verlinkt hatte). Man möchte aber nur
eines der Bit auf 1 setzen, damit es zurück gesetzt wird. Das geht durch
eine simple Zuweisung. Deswegen haben die HW-Hersteller das ja so
gebaut.
Wilhelm M. schrieb:> Weil es bei den Flag-Registern einfach falsch ist
Aber nur deshalb, weil man es unlogisch in der Hardware implementiert
hat.
Wie gesagt, beim 8051 gibt es mit logischer Implementierung keine solche
Fallgruben.
Peter D. schrieb:> Der Befehlssatz muß nur> AND/OR auf SFRs unterstützen, damit die Operation atomar ist.
Im Unterschied zu RAM-Inhalten müssen r-m-w Operationen auf Int-Flags
nicht nur auf Befehlsebene atomar sein, sondern auf Taktebene. Sobald
der Prozessor-Kern sie in getrennte ausgeführte Buszyklen auflöst,
besteht ein potentielles Problem, wenn die Logik solcher Register
unabhängig von Befehlszyklen operiert.
> Bei vielen ARM-CPUs sind spezielle Set- und Clear-Adressen zum atomaren> Setzen verfügbar. Da wäre es auch kein Problem, das Interrupt löschen> logisch richtig zu implementieren.
Genau so eine spezielle Bauart haben Register, die neutral sind, wenn
eine 0 geschrieben wird, und löschen, wenn es eine 1 ist. Der Pin wird
auf 0 gesetzt, wenn ins Löschregister eine 1 geschrieben wird. Exakt
das, was auch solch ein Int-Flag Register tut.
Natürlich könnte man an Stelle eines Registers zwei bauen. Eines
readonly für den Status und eines writeonly fürs Löschen, das immer 0
liest. Dann könnten aus Lesefaule r-m-w Befehle verwenden.
Bernd K. schrieb:> Sobald mehr als 1 Bit gleichzeitig gesetzt werden soll wird der Code> umständlicher und aufgeblasener, denn da die Register als volatile> definiert werden müssen
Ganz genau! Der generierte Assembler-Code von mehreren
aufeinanderfolgenden Bit-Schreib-Operationen sieht ganz anders aus als
man erst denkt.
Peter D. schrieb:> Der Befehlssatz muß nur> AND/OR auf SFRs unterstützen, damit die Operation atomar ist.
Das bildet C aber nicht wirklich ab, auch wenn manche Compiler das
"zufällig" können. ARM kann nur auf Prozessor-Registern atomar arbeiten.
Peter D. schrieb:> Bei vielen ARM-CPUs sind spezielle Set- und Clear-Adressen zum atomaren> Setzen verfügbar
Aber meist nur für einige wenige GPIO-Register. Allerdings hat das
CAN_IR Register eine gewisse Form der Atomizität, aber keine die für C
transparent ist. Im Datasheet heißt es:
"A flag is cleared by writing a 1 to the corresponding bit field.
Writing a 0 has no effect."
Nur bietet C eben keine Möglichkeit das dem Compiler mitzuteilen,
weshalb die "normalen" Bit-Setze-Operationen ("reg |= bitmask",
bitfield) immer versuchen, die anderen Bits beizubehalten, was dann
natürlich schief geht.
Diese Form von Atomizität hat aber einen anderen netten Vorteil: Man
kann die ISR in dieser Art formulieren:
1
voidCAN_ISR(void){
2
uint32_tIR=CAN->IR;// Flags abfragen
3
while(IR!=0){
4
if(IR&CAN_IR_RF0N){
5
// Behandeln ...
6
}
7
if(IR&CAN_IR_RF1N){
8
// Behandeln ...
9
}
10
CAN->IR=IR;// Abgehandelte Flags löschen
11
IR=CAN->IR;// Neue Flags abfragen
12
}
13
}
Dadurch kann man mehrere gleichzeitig auftretende Interrupts sowie
während der ISR neu ankommende Interrupts sofort abhandeln. Dabei kann
man garantiert keine Interrupts verpassen, da man immer nur die Flags
löscht, die man soeben behandelt hat. Das lässt sich mit Bitfields nicht
vernünftig abbilden.
Peter D. schrieb:> Aber nur deshalb, weil man es unlogisch in der Hardware implementiert> hat.
Es ist also sehr wohl ziemlich logisch!
Peter D. schrieb:> Wie gesagt, beim 8051 gibt es mit logischer Implementierung keine solche> Fallgruben.
Atomares AND/OR auf per Prozessor-Bus angesprochenem Speicher (nichts
anderes sind SFR's bei ARM) skaliert nicht gut. Da ARM
aufwärts-kompatibel zu Mehrkern-Prozessoren mit Caches, DMA, mehreren
Prozessor-Bussen ist, kann man das nicht vernünftig umsetzen.
A. K. schrieb:> Genau so eine spezielle Bauart haben Register, die neutral sind, wenn> eine 0 geschrieben wird, und löschen, wenn es eine 1 ist. Der Pin wird> auf 0 gesetzt, wenn ins Löschregister eine 1 geschrieben wird. Exakt> das, was auch solch ein Int-Flag Register tut.
Mein Reden ...
Und deswegen (und den o.g. anderen Gründen, v.a. bei ARM) lässt man das
mit den Bitfields in C/C++ und der union des TO ganz sein. Am besten
abstrahiert man ein Flagregister gescheit in einer entsprechenden Klasse
...
Niklas G. schrieb:>> Der Befehlssatz muß nur>> AND/OR auf SFRs unterstützen, damit die Operation atomar ist.>> Das bildet C aber nicht wirklich ab, auch wenn manche Compiler das> "zufällig" können. ARM kann nur auf Prozessor-Registern atomar arbeiten.
Nicht nur das. Assignops wie
x |= y;
sind im Standard als identisch zu
x = x | y;
definiert (Seiteneffekte wie bei *p++ ausgenommen) und setzen in der
"abstract virtual machine" für x eine Lese- und eine Schreiboperation
voraus. Wird das auch bei einem als "volatile" deklarierten SFR durch
einen einzigen 8051 OR-Befehl umgesetzt, sind das keine getrennten
Operationen mehr, da diese 8051 Befehle auf SFRs sehr speziell arbeiten.
Genau genommen liegt eine solche Umsetzung von assignops durch 8051
Compiler also m.E. ausserhalb des C Standards. ;-)
Wilhelm M. schrieb:> Mein Reden ...>> Und deswegen (und den o.g. anderen Gründen, v.a. bei ARM) lässt man das> mit den Bitfields in C/C++ und der union des TO ganz sein.
Du meinst also, ich (nicht der TO) soll die von Microchip für ihre PICs
mitgelieferten Header nicht benutzen.
Schade, war bisher so schön. Man konnte einem Bit aus dem Bitfeld eines
Ports einfach mit #define den gleichen Namen geben, wie das Netz im
Schaltplan. Und mit if(NETZNAME) kann man den Portpin abfragen oder mit
NETZNAME=1 setzen. Ich finde das gut lesbar. Ich mache das seit vielen
Jahren über einige Versionen der Compiler so. Bisher konntest du mich
nicht davon überzeugen, es zu lassen.
MfG Klaus
Klaus schrieb:> Wilhelm M. schrieb:>> Mein Reden ...>>>> Und deswegen (und den o.g. anderen Gründen, v.a. bei ARM) lässt man das>> mit den Bitfields in C/C++ und der union des TO ganz sein.>> Du meinst also, ich (nicht der TO) soll die von Microchip für ihre PICs> mitgelieferten Header nicht benutzen.>> Schade, war bisher so schön. Man konnte einem Bit aus dem Bitfeld eines> Ports einfach mit #define den gleichen Namen geben, wie das Netz im> Schaltplan. Und mit if(NETZNAME) kann man den Portpin abfragen oder mit> NETZNAME=1 setzen. Ich finde das gut lesbar. Ich mache das seit vielen> Jahren über einige Versionen der Compiler so. Bisher konntest du mich> nicht davon überzeugen, es zu lassen.
Wenn Du mit der implementation-defined Eigenschaft leben kannst, ist es
ja ok.
Bei den sog. Flag-Registern ist es allerdings einfach falsch. Aber
vielleicht verwendest Du die ja auch nicht entsprechend.
Rupert B. schrieb:> typedef union{> struct{> … // 32 bits muss ich hier glaube ich nicht auflisten> uint32_t RF0N:1; //Bit 10: Neue CAN Nachricht im FIFO 0> Buffer-Array abgelegt> uint32_t RF1N:1; //Bit 11: Neue CAN Nachricht im FIFO 1> Buffer-Array abgelegt> …> } bit;> uint32_t reg;> } CAN_IR_Type canIrType;
... unabhängig von den ganzen themen die hier erwähnt werden ... und
oft doch nur müll sind, mußt du in c bit-fields anders angehen, eher so
struct {
unsigned :10;
unsigned RF0N :1; //Bit 10: Neue CAN Nachricht im FIFO 0
unsigned RF1N :1; //Bit 11: Neue CAN Nachricht im FIFO 1
unsigned :12;
} canIr;
und dann natürlich noch mappen auf die entsprechende sfr addr!
mt
Klaus schrieb:> Du meinst also, ich (nicht der TO) soll die von Microchip für ihre PICs> mitgelieferten Header nicht benutzen.> ... Bisher konntest du mich nicht davon überzeugen, es zu lassen.
Da passt das schon, ich benutze die auch gerne. Der Zugriff auf diese
Register ändert sich ja durch die vordefinierten Bitfelder nicht. Es ist
immer ein RMW erforderlich, wenn nur ein Teil geändert werden soll.
Aber im hier in diesem Fall, für diese Art von Register passt das eben
nicht.
Wilhelm M. schrieb:> Wenn Du mit der implementation-defined Eigenschaft leben kannst, ist es> ja ok.
Mir ist das egal, ist Hobby. Aber den professionellen Kunden von
Microchip sicher nicht. Wenn Microchip mit denen weiter Geschäfte machen
will, werden die da nichts ändern und die alte Codebasis obsolet machen.
Ich fühle mich da auf der sicheren Seite.
MfG Klaus
Apollo M. schrieb:> Rupert B. schrieb:>> typedef union{>> struct{>> … // 32 bits muss ich hier glaube ich nicht auflisten>> uint32_t RF0N:1; //Bit 10: Neue CAN Nachricht im FIFO 0>> Buffer-Array abgelegt>> uint32_t RF1N:1; //Bit 11: Neue CAN Nachricht im FIFO 1>> Buffer-Array abgelegt>> …>> } bit;>> uint32_t reg;>> } CAN_IR_Type canIrType;>> ... unabhängig von den ganzen themen die hier erwähnt werden ... und> oft doch nur müll sind, mußt du in c bit-fields anders angehen, eher so>>> struct {> unsigned :10;> unsigned RF0N :1; //Bit 10: Neue CAN Nachricht im FIFO 0> unsigned RF1N :1; //Bit 11: Neue CAN Nachricht im FIFO 1> unsigned :12;> } canIr;>
Nein, und nochmals nein.
Bei diesem speziellen Flag-Register würden auf diese Weise alle(!)
gesetzten Flags resettet (wegen rmw). Und das ist hier eben falsch.
Und wer es immer noch nicht glaubt, schaut sich bitte den
Assembler-Output von
1
#include<avr/io.h>
2
3
structBits{
4
uint8_tbit0:1;
5
uint8_tbit1:1;
6
uint8_tbit2:1;
7
uint8_tbit3:1;
8
uint8_tbit4:1;
9
uint8_tbit5:1;
10
uint8_tbit6:1;
11
uint8_tbit7:1;
12
};
13
14
intmain(){
15
PORTA.INTFLAGS=0x01;// korrekt
16
autop=reinterpret_cast<volatileBits*>(&PORTA);
17
p->bit0=1;// falsch
18
}
zusammen mit der Registerbeschreibung im DB der avr0-Serie. Ich hatte
zwar oben schon mal einen Compilerexplorer verlinkt, aber das ist
natürlich für viele hier viel zu umständlich. Deswegen hier für den ganz
einfachen avr:
Und jetzt stelle man sich vor, dass auch das Bit-1 gesetzt ist. Man
liest also 0x03, odered mit 0x01, ergibt 0x03 und schreibt zurück. Und
siehe da, es ist sowohl Bit-1 als auch (!) Bit-0 ge-resettet. Ups.
Und der Grund steht auch im C-Standard: die kleineste adressierbare
Einheit ist das byte, nicht das bit.
Ich will mal ganz kurz auf die vielen Antworten eingehen. Es waren doch
einige Themen dabei, über die ich mir bisher kaum Gedanken gemacht habe.
Als Fazit für die die nur wegen der eigentlichen Frage da sind:
Da die kleinste adressierbare Einheit das Byte! ist und nicht das Bit,
muss der Compiler zum verändern einzelner Bits in einem Bitfeld(union)
das vollständige Byte auslesen, verändern und wieder zurück kopieren.
Aus diesem Grund wird jede gesetzte 1 erneut zurückgeschrieben und vom
Microcontroller als zu löschendes Bit interpretiert.
Sehr interessant finde ich, dass man bei Little- bzw. Big-endian
aufpassen muss. Damit habe ich mich bisher nur beim Programmieren von
Netzanwendungen beschäftigt. Wenn man seine Header also möglichst
Plattformunabhängig gestalten will, sollte man auf Bitfelder eventuell
verzichten. Da ich allerdings meine Header ausschließlich für einen
Mikrocontoller und mit Atmel Studio und den da mit installierten
Compiler nutze, spielt das in meinem Fall keine Rolle.
Schlussendlich möchte ich mich für diese große Teilnahme an diesem
Thread bedanken. Das hätte ich nicht erwartet.
Rupert B. schrieb:> Da die kleinste adressierbare Einheit das Byte! ist und nicht das Bit,> muss der Compiler zum verändern einzelner Bits in einem Bitfeld(union)> das vollständige Byte auslesen, verändern und wieder zurück kopieren.
Nein, musser nich. Bei den AVRs z.B. gibts dafür sbi und cbi, und ein
ordentlicher Compiler verwendet die auch korrekt.
Karl K. schrieb:> Rupert B. schrieb:>> Da die kleinste adressierbare Einheit das Byte! ist und nicht das Bit,>> muss der Compiler zum verändern einzelner Bits in einem Bitfeld(union)>> das vollständige Byte auslesen, verändern und wieder zurück kopieren.>> Nein, musser nich. Bei den AVRs z.B. gibts dafür sbi und cbi, und ein> ordentlicher Compiler verwendet die auch korrekt.
... und das geht nur bei SFRs mit Adresse < 0x20. Ist also nicht
allgemeingültig und eine kleine Optimierung des AVR-Backends. Bei
neueren AVRs liegen fast alle SFRs außerhalb dieses Bereiches. Und zudem
bezog sich die Frage auf ARM, da ist das eben nicht so. Es gilt dann der
allgemeine Fall wie oben beschrieben.
Anders ausgedrückt: lässt man Bitfields weg und nimmt Shifts, ist es
immer zu 100% richtig. Setzt man Bitfields ein, ist es zu 99,99% falsch.
Rupert B. schrieb:> canIrType.RF0N = 1; // Bit löschen, funktioniert nicht wie erwartet
Da sich bei den meisten MP & MC einzelne Bits nicht Lesen und Schreiben
lassen, muss sichergestellt sein, dass die restlichen Bits nicht
verändert werden.
Das erfolgt durch LESEN des gesamten Registers, ändern des betroffenen
Bits und Rückschreiben des gesamten Registers. (geschieht auch bei C-51
Kompiler, obwohl hier auch einzelne Bits manipuliert werden können)
Dies funktioniert aber nur unter drei Bedingungen:
1.) die Regster müssen rücklesbar sein
2.) der Schreibvorgang darf keine zusätzlichen Funktionen auslösen, z.B
Rücksetzvorgänge
3.) es darf sich kein Interrupt zwischen dem Lesen und Schreiben
dazwischen drängen (Register könnte von diesem verändert werden)
Wilhelm M. schrieb:> Setzt man Bitfields ein, ist es zu 99,99% falsch.
Bezieht sich das jetzt nur auf ARM oder nur auf diese speziellen
Flag-Register in denen eine 1 gelöscht wird, indem man wieder eine 1
rein schreibt oder...?
Volker S. schrieb:> Wilhelm M. schrieb:>> Setzt man Bitfields ein, ist es zu 99,99% falsch.>> Bezieht sich das jetzt nur auf ARM oder nur auf diese speziellen> Flag-Register in denen eine 1 gelöscht wird, indem man wieder eine 1> rein schreibt oder...?
Hast Du das obige gelesen? Was hast Du nicht verstanden?
Affenarm schrieb:> Manche Cortex M0 können auch bit-banding.
Das bringt aber auch nichts, da der Effekt der gleiche ist wie bei
Bitfields (nur etwas schneller). Bitbanding lässt den Prozessor genauso
einen Read-Modify-Write-Zyklus durchführen. Der Prozessorbus kann damit
immer noch keine bitweisen Zugriffe durchführen. Auch hierbei können
versehentlich genau in diesem Moment aufgetretene Interrupts "vergessen"
werden, weil sie mit gelöscht werden.
Im Cortex-M3 Technical Reference Manual heißt es dazu:
> Writing to a word in the alias region has the same effect as a read-> modify-write operation on the targeted bit in the bit-band region.
Der Artikel hier im Wiki suggeriert dass die Zugriffe atomic seien:
> Wenn eine solche Befehlssequenz im Hauptprogramm steht und in einem> Interrupt Handler ein anderes Bit des gleichen Peripherieregisters> verändert wird, dann kann es vorkommen, dass der Handler zwischen dem> Load- und dem Store-Befehl dieser Sequenz ausgeführt wird.
Und
> ARM Controller mit den Cores Cortex-M3 und -M4 sowie auch manche Cortex> M0+ besitzen jedoch die Fähigkeit, einzelne Bits im RAM und im> Peripheriebereich direkt adressieren zu können
Das verhindert aber lediglich, dass zwischen den Lese-Schreib-Zyklen ein
Interrupt ausgeführt wird, der den Wert zwischendurch verändert. Dass
die Peripherie selbst, eine DMA-Master (oder ein anderer Core bei
Multi-Core-Prozessoren) dazwischenfunkt lässt sich aber nicht
verhindern!
Wilhelm M. schrieb:> Hast Du das obige gelesen? Was hast Du nicht verstanden?
Na, auf was sich deine 99,99% beziehen. Ist das so schlecht formuliert?
(Im konkret vorliegenden Fall sind es ja 100%)
Volker S. schrieb:> Wilhelm M. schrieb:>> Hast Du das obige gelesen? Was hast Du nicht verstanden?>> Na, auf was sich deine 99,99% beziehen. Ist das so schlecht formuliert?> (Im konkret vorliegenden Fall sind es ja 100%)
Richtig wird es nur dann, wenn:
* die endianess passt
* die bits gepacked werden
* keine rmw-Zykles verwendet werden, also spezielle Bit-Manip-Ops, die
atomar sind
* keine load-or-store Folgen, weil die andere Bits der Flagregister
ebenfalls resetten
Im konkreten Fall übersetzt der ARM-Compiler das zu rmw als
load-or-store, damit also doppelt falsch.
Wilhelm M. schrieb:> Anders ausgedrückt: lässt man Bitfields weg und nimmt Shifts, ist es> immer zu 100% richtig.
Da mache ich doch direkt mal Werbung für meine µSer-Bibliothek, welche
die 100% richtigen Shifts mittels C++ übersichtlich verpackt:
Beitrag "Artikel und Bibliothek zu Serialisierung"
Ist allerdings nicht für solche Peripherieregister gemacht, ersetzt aber
klassische Anwendungsfälle von Bitfields.
Niklas G. schrieb:> Affenarm schrieb:>> Manche Cortex M0 können auch bit-banding.>> Das bringt aber auch nichts, da der Effekt der gleiche ist wie bei> Bitfields (nur etwas schneller). Bitbanding lässt den Prozessor genauso> einen Read-Modify-Write-Zyklus durchführen.> [...]
Ah. OK. Dann bringt bit-banding nichts.
Hmm. Dann würde ich versuchen, ob ich nicht eine Bitmaske (und ggf. eine
zusätzliche Oder-Verknüpfung) ausfuchsen kann, die zwischen read und
write angewendet wird und verhindert, dass zwischenzeitlich veränderte
gelesene Bits ungewollte Effekte haben (also andere Int-Flags
zurücksetzen als beabsichtigt).
Danke für die Erinnerung, Niklas.
Affenarm schrieb:> Hmm. Dann würde ich versuchen, ob ich nicht eine Bitmaske (und ggf. eine> zusätzliche Oder-Verknüpfung) ausfuchsen kann
Braucht man gar nicht, einfach das Register auslesen, auswerten und 1:1
zurückschreiben:
Beitrag "Re: Hilfe, Interrupt flags lassen sich mit 'typedef union' nicht einzeln löschen"
Ich vermute stark, das ist genau die intendierte Nutzungsweise dieser
Art von Register (genau so funktioniert das bei vielen ARMs /
Peripheriemodulen).
Niklas G. schrieb:> Wilhelm M. schrieb:>> Anders ausgedrückt: lässt man Bitfields weg und nimmt Shifts, ist es>> immer zu 100% richtig.>> Da mache ich doch direkt mal Werbung für meine µSer-Bibliothek, welche> die 100% richtigen Shifts mittels C++ übersichtlich verpackt:>> Beitrag "Artikel und Bibliothek zu Serialisierung">> Ist allerdings nicht für solche Peripherieregister gemacht, ersetzt aber> klassische Anwendungsfälle von Bitfields.
Ja, genau.
Und mir ist echt schleierhaft, wieso sich diese Bitfield-Geschichte so
lange kolportiert wird.
Man kann sich für diese (Flag-)Register auch Templates schreiben, die
als Spezialisierung für bestimmte Prozessoren und andere Voraussetzungen
(z.B. bei AVR, ob die Registeradresse < 0x20 ist) auch die RMW-Zyklen
extra verwenden (etwa: register |= 1), in dem Wissen, dass dies in
diesem Spezialfall zu einem atomaren Bit-Manip_op (etwa sbi) umgesetzt
wird. Im generellen Fall werden die normalen Zuweisungen für die
Set-Register (etwa outset, dirset, ...) benutzt. So mache ich das
jedenfalls in meiner C++ Bibliothek. Aber die Frage war ja nach C ...
Niklas G. schrieb:> Affenarm schrieb:>> Hmm. Dann würde ich versuchen, ob ich nicht eine Bitmaske (und ggf. eine>> zusätzliche Oder-Verknüpfung) ausfuchsen kann>> Braucht man gar nicht, einfach das Register auslesen, auswerten und 1:1> zurückschreiben:>> Beitrag "Re: Hilfe, Interrupt flags lassen sich mit 'typedef union' nicht einzeln löschen">> Ich vermute stark, das ist genau die intendierte Nutzungsweise dieser> Art von Register (genau so funktioniert das bei vielen ARMs /> Peripheriemodulen).
Genau! Die sind deswegen erfunden worden, um immer richtig zu
funktionieren, wenn man keine(!) Bitfields verwendet.
Wilhelm M. schrieb:> Genau! Die sind deswegen erfunden worden, um immer richtig zu> funktionieren, wenn man keine(!) Bitfields verwendet.
Nicht weil ich es toll fände, sondern nur um zu beweisen, dass
fanatische Verfechter von irgendwas (egal ob sie Bitfelder, Rekursion,
Globalisierung oder Jetstreams verteufeln) absolut nicht immer recht
haben:
Niklas G. schrieb:> Affenarm schrieb:>> Hmm. Dann würde ich versuchen, ob ich nicht eine Bitmaske (und ggf. eine>> zusätzliche Oder-Verknüpfung) ausfuchsen kann>> Braucht man gar nicht, einfach das Register auslesen, auswerten und 1:1> zurückschreiben:> [...]
Ja. Richtig.
Auf diese Weise, indem Du am Anfang die Bits liest und am Ende lediglich
genau die gesetzten Bits schreibst, werden zwischenzeitlich zusätzlich
geänderte Bits nicht berührt.
Zweiter Dank für die weitere Erinnerung, Niklas.
Ausrede: Habe ich bei nem STM auch schon so gemacht, aber ist schon ne
Weile her. Lach. :-)
Wilhelm M. schrieb:> Und mir ist echt schleierhaft, wieso sich diese Bitfield-Geschichte so> lange kolportiert wird.
Sicher nur von Leuten, die Architekturen und Compiler verwenden,
bei denen das in den Prozessorheadern schon drin ist.
Die kommen evtl. nicht sofort darauf, warum man das nicht tun sollte ;-)
Es macht unter Umständen (<= 0,01%) gar keinen Unterschied ob Bitfeld
oder shiftoperator:
1
!INTCONbits.GIE=1;// enable global interrupts
2
0x164E:BSFINTCON,7,ACCESS
3
!INTCON|=1<<7;
4
0x1650:BSFINTCON,7,ACCESS
5
!GIE=1;
6
0x1652:BSFINTCON,7,ACCESS
Also wenn es passt, finde ich Bitfelder angenehmer.
Vor allem, wenn die schon mitgeliefert werden.
Hier habe ich auf jeden Fall was neues gelernt.
z.B. über die Auswirkungen des Rücksetzmechanismus, auf die
Bitfeldmethode
Auch darüber, was eigentlich passiert, wenn gerade ein Flag über einen
RWM Mechanismus (weil es gar keine andere Möglichkeit gibt) gelöscht
wird und ein anderer IR eintrifft habe ich vorher noch nie
nachgedacht...
Markus F. schrieb:> Wilhelm M. schrieb:>> Genau! Die sind deswegen erfunden worden, um immer richtig zu>> funktionieren, wenn man keine(!) Bitfields verwendet.>> Nicht weil ich es toll fände, sondern nur um zu beweisen, dass> fanatische Verfechter von irgendwas (egal ob sie Bitfelder, Rekursion,> Globalisierung oder Jetstreams verteufeln) absolut nicht immer recht> haben:>>
1
>#include<stdint.h>
2
>typedefstructbits{
3
>uint32_tb0:1;
4
>uint32_tb1:1;
5
>uint32_tb2:1;
6
>uint32_tb3:1;
7
>uint32_tb4:1;
8
>uint32_trest:27;
9
>}BITS;
10
>voidreset_flag_4(BITS*b){
11
>*b=(BITS){.b4=1};
12
>}
13
>
>> 100% Bitfelder. 100% richtig.
Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?
Mich würde überraschen, wenn das jemand so benutzt.
Volker S. schrieb:> Auch darüber, was eigentlich passiert, wenn gerade ein Flag über einen> RWM Mechanismus (weil es gar keine andere Möglichkeit gibt) gelöscht> wird und ein anderer IR eintrifft habe ich vorher noch nie> nachgedacht...
Ja, bei Daten (Registern) die von verschiedenen Aktoren
(Prozessor-Kerne, Peripherie, DMA) gleichzeitig verändert wird muss man
sehr genau aufpassen.
Wilhelm M. schrieb:> Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?>
Wo?
Wilhelm M. schrieb:> Markus F. schrieb:>> Wilhelm M. schrieb:>>> Genau! Die sind deswegen erfunden worden, um immer richtig zu>>> funktionieren, wenn man keine(!) Bitfields verwendet.>>>> Nicht weil ich es toll fände, sondern nur um zu beweisen, dass>> fanatische Verfechter von irgendwas (egal ob sie Bitfelder, Rekursion,>> Globalisierung oder Jetstreams verteufeln) absolut nicht immer recht>> haben:>>>>> #include <stdint.h>>> typedef struct bits {>> uint32_t b0 : 1;>> uint32_t b1 : 1;>> uint32_t b2 : 1;>> uint32_t b3 : 1;>> uint32_t b4 : 1;>> uint32_t rest : 27;>> } BITS;>> void reset_flag_4(BITS *b) {>> *b = (BITS) { .b4 = 1 };>> }>> >>> 100% Bitfelder. 100% richtig.>> Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?>
Wo ist da eine "uint32_t-Wertzuweisung" - ich seh' da nur ein Bitfeld.
> Mich würde überraschen, wenn das jemand so benutzt.
zugegeben, mich auch.
Markus F. schrieb:>> Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?>>>> Wo ist da eine "uint32_t-Wertzuweisung" - ich seh' da nur ein Bitfeld.
Es ist eine Struct, die so lang ist wie ein uint32_t, also 4 Bytes.
Markus F. schrieb:> Wilhelm M. schrieb:>> Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?>>>> Wo?
Es wird ganz einfach auf den underlying-type (hier uint32_t) abgebildet.
>> Wilhelm M. schrieb:>> Markus F. schrieb:>>> Wilhelm M. schrieb:>>>> Genau! Die sind deswegen erfunden worden, um immer richtig zu>>>> funktionieren, wenn man keine(!) Bitfields verwendet.>>>>>> Nicht weil ich es toll fände, sondern nur um zu beweisen, dass>>> fanatische Verfechter von irgendwas (egal ob sie Bitfelder, Rekursion,>>> Globalisierung oder Jetstreams verteufeln) absolut nicht immer recht>>> haben:>>>>>>> #include <stdint.h>>>> typedef struct bits {>>> uint32_t b0 : 1;>>> uint32_t b1 : 1;>>> uint32_t b2 : 1;>>> uint32_t b3 : 1;>>> uint32_t b4 : 1;>>> uint32_t rest : 27;>>> } BITS;>>> void reset_flag_4(BITS *b) {>>> *b = (BITS) { .b4 = 1 };>>> }>>> >>>> 100% Bitfelder. 100% richtig.>>>> Dies ist eine uint32_t Wertzuweisung. Was soll daran überraschen?>>>> Wo ist da eine "uint32_t-Wertzuweisung" - ich seh' da nur ein Bitfeld.
s.o. underlying type
>>> Mich würde überraschen, wenn das jemand so benutzt.> zugegeben, mich auch.
Ist auch nicht das, das der TO machen wollte.
Karl K. schrieb:> Nein, musser nich. Bei den AVRs z.B. gibts dafür sbi und cbi, und ein> ordentlicher Compiler verwendet die auch korrekt.
Aber auch diese bewirken intern ein Read-Modify-Write, d.h.
Interruptflags werden gelöscht, wenn man im selben Register ein anderes
Bit ändert:
"Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt
can be disabled. This also applies if the SBI and CBI instructions are
used."
Man kann somit auf ADIF pollen und mit Setzen des ADSC (Start
Conversion) wird es automatisch gelöscht. Ich würde so einen Code
allerdings nicht erstellen, da diese Wirkung nicht offensichtlich ist.
Man pollt daher besser auf ADSC und läßt das ADIF links liegen.
Peter D. schrieb:> Karl K. schrieb:>> Nein, musser nich. Bei den AVRs z.B. gibts dafür sbi und cbi, und ein>> ordentlicher Compiler verwendet die auch korrekt.>> Aber auch diese bewirken intern ein Read-Modify-Write, d.h.> Interruptflags werden gelöscht, wenn man im selben Register ein anderes> Bit ändert:> "Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt> can be disabled. This also applies if the SBI and CBI instructions are> used."
Das kenne ich aus dem DB des 328PB, und da ist es einfach Blödsinn.
sbi/cbi kann man nicht auf adcsra anwenden.
Volker S. schrieb:> Also wenn es passt, finde ich Bitfelder angenehmer.
Von allen Kopfschmerzen die man pro Tag in der Praxis bei der
Registerprogrammierung von komplexen Controllern bekommt ist der Anteil
der Schmerzen der vom Zwang zu manuellen Bitoperationen herrührt
verschwindend gering, fast überhaupt nicht spürbar. Die meisten
Schmerzen bekommt man regelmäßig von der Herstellerdokumentation oder
einfach von der schieren Komplexität der Hardware selbst. Ob man das
rettende Bit nach dem man den ganzen Tag gesucht hat jetzt reinodert
oder mit ner Zuweisung reinschreibt macht dann keinen großen Unterschied
mehr.
> Vor allem, wenn die schon mitgeliefert werden.
Dann ist man natürlich stets versucht sie auch zu verwenden wodurch man
zwar in der Praxis(!) des rauhen Alltags kaum bis null spürbare
Erleichterung hat, nur eine leichte Geschmacksverschiebung, aber dafür
wird der generierte Code an vielen Stellen bloatiger weil man jetzt nie
mehr als 1 Bit gleichzeitig setzen kann, es sei denn man macht die ganze
scheinbare Erleichterung wieder doppelt zunichte indem man in solchen
Fällen wieder umständlich darum herum wurschteln muß mit
fingerbrechenden compound literals oder dergleichen oder Stilbruch
begehen muß und doch wieder Bits von Hand zusammenodert.
Bernd K. schrieb:> Volker S. schrieb:>> Also wenn es passt, finde ich Bitfelder angenehmer.>> Von allen Kopfschmerzen die man pro Tag in der Praxis bei der> Registerprogrammierung von komplexen Controllern bekommt ist der Anteil> der Schmerzen der vom Zwang zu manuellen Bitoperationen herrührt> verschwindend gering, fast überhaupt nicht spürbar.
Nur die Fehler, die man damit einbaut, spürt man gewaltig. Siehe unseren
TO. Was ohne Nebenläufigkeit bei "normalen" Registern zufälligerweise
funktioniert hat, geht nun bei Flagregistern auf einmal nicht mehr.
Schon sucht man lange nach dem Grund ...
Wilhelm M. schrieb:> Das kenne ich aus dem DB des 328PB, und da ist es einfach Blödsinn.> sbi/cbi kann man nicht auf adcsra anwenden.
Es stammt aus dem Datenblatt des ATmega8 und da geht es.
Peter D. schrieb:> Wilhelm M. schrieb:>> Das kenne ich aus dem DB des 328PB, und da ist es einfach Blödsinn.>> sbi/cbi kann man nicht auf adcsra anwenden.>> Es stammt aus dem Datenblatt des ATmega8 und da geht es.
Typischer copy-n-paste Fehler von Atmel ...
atmega8 und ältere: "Some of the Status Flags are cleared by writing a
logical one to them. Note that the CBI and SBI instructions will operate
on all bits in the I/O Register, writing a one back into any flag read
as set, thus clearing the flag."
attiny2313 und neuere wie atmega328: "Some of the status flags are
cleared by writing a logical one to them. Note that, unlike most other
AVRs, the CBI and SBI instructions will only operate on the specified
bit, and can therefore be used on registers containing such status
flags."
A. K. schrieb:> Ich meine mich zu erinnern, dass sich in der Historie der AVR Cores das> Verhalten der CBI/SBI Befehle in Bezug auf solche Feinheiten geändert> hat.
Der Programmierer soll ja nicht einfach C&P machen, sondern gefälligst
bei jedem Typ neu die Fehler suchen müssen. Typübergreifend verwendbare
Libs werden völlig überbewertet.
Mir ist eingefallen dass o.g. Schema noch ein Problem hat: Wenn während
der Verarbeitung ein bereits eingetretener Interrupt noch mal auftritt,
wird das Flag am Ende gelöscht ohne verarbeitet zu werden. Daher sollte
man vor der Verarbeitung löschen:
1
voidCAN_ISR(void){
2
uint32_tIR=CAN->IR;// Flags abfragen
3
while(IR!=0){
4
CAN->IR=IR;// Abgehandelte Flags löschen
5
if(IR&CAN_IR_RF0N){
6
// Behandeln ...
7
}
8
if(IR&CAN_IR_RF1N){
9
// Behandeln ...
10
}
11
IR=CAN->IR;// Neue Flags abfragen
12
}
13
}
Die Behandlung sollte sofern möglich dazu in der Lage sein, mehrfache
Interrupts zu verarbeiten, z.B. bei Empfangs-Interrupts von
FIFO-basierten Kommunikationsinterfaces. Wenn hier bei der Behandlung
neue Interrupts auftauchen, werden die im nächsten Schleifendurchlauf
behandelt. Wenn sie zwischen Abfragen und Löschen auftreten, sollte die
Behandlung das Mehrfach-Auftreten verstehen. Das klappt natürlich nur
solange der FIFO groß genug bzw. der Prozessor schnell genug ist.