Forum: Mikrocontroller und Digitale Elektronik Hilfe, Interrupt flags lassen sich mit 'typedef union' nicht einzeln löschen


von Rupert B. (mr_dojo0)


Lesenswert?

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
typedef union{
2
   struct{
3
          // 32 bits muss ich hier glaube ich nicht auflisten
4
         uint32_t RF0N:1; //Bit 10: Neue CAN Nachricht im FIFO 0 Buffer-Array abgelegt
5
         uint32_t RF1N:1; //Bit 11:  Neue CAN Nachricht im FIFO 1 Buffer-Array abgelegt
6
          
7
   } bit;
8
   uint32_t reg;
9
} CAN_IR_Type canIrType;

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Volker S. (vloki)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Volker S. (vloki)


Lesenswert?

Niklas G. schrieb:
> Nö, ganz normal bei solchen Interrupt-Flag-Registern.

Ja hast recht. Ist keine gute Idee mit dem Bitfeld hier.

von Rupert B. (mr_dojo0)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
> typedef union{
2
>    struct{
3
>           // 32 bits muss ich hier glaube ich nicht auflisten
4
>          uint32_t RF0N:1; //Bit 10: Neue CAN Nachricht im FIFO 0 
5
> Buffer-Array abgelegt
6
>          uint32_t RF1N:1; //Bit 11:  Neue CAN Nachricht im FIFO 1 
7
> Buffer-Array abgelegt
8
>          
9
>    } bit;
10
>    uint32_t reg;
11
> } CAN_IR_Type canIrType;
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.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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...

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Ste N. (steno)


Lesenswert?

Die Bits 0-9 vor RF0N hast Du aber schon definiert, oder?
1
typedef union{
2
   struct{
3
          // 32 bits muss ich hier glaube ich nicht auflisten
4
         uint32_t RF0N:1; //Bit 10: Neue CAN Nachricht im FIFO 0 Buffer-Array abgelegt
5
         uint32_t RF1N:1; //Bit 11:  Neue CAN Nachricht im FIFO 1 Buffer-Array abgelegt
6
          
7
   } bit;
8
   uint32_t reg;
9
} CAN_IR_Type canIrType;

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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
void CAN_ISR (void) {
2
  uint32_t IR = 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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 
...

von (prx) A. K. (prx)


Lesenswert?

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. ;-)

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

von Volker S. (vloki)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Und wer es immer noch nicht glaubt, schaut sich bitte den 
Assembler-Output von
1
#include <avr/io.h>
2
3
struct Bits {
4
    uint8_t bit0:1;
5
    uint8_t bit1:1;
6
    uint8_t bit2:1;
7
    uint8_t bit3:1;
8
    uint8_t bit4:1;
9
    uint8_t bit5:1;
10
    uint8_t bit6:1;
11
    uint8_t bit7:1;
12
};
13
14
int main() {
15
    PORTA.INTFLAGS = 0x01; // korrekt
16
    auto p = reinterpret_cast<volatile Bits*>(&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:
1
main:
2
ldi r30,0                ;  tmp43
3
ldi r31,lo8(4)   ; ,
4
ldi r24,lo8(1)   ;  tmp44,
5
std Z+9,r24      ;  MEM[(struct PORT_t *)1024B].INTFLAGS, tmp44
6
ld r24,Z                 ;  MEM[(volatile struct Bits *)1024B].bit0, MEM[(volatile struct Bits *)1024B].bit0
7
ori r24,lo8(1<<0)        ;  MEM[(volatile struct Bits *)1024B].bit0,
8
st Z,r24                 ;  MEM[(volatile struct Bits *)1024B].bit0, MEM[(volatile struct Bits *)1024B].bit0
9
ldi r25,0                ;
10
ldi r24,0                ;
11
ret
12
.size   main, .-main

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.

: Bearbeitet durch User
von Rupert B. (mr_dojo0)


Lesenswert?

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.

von Karl K. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von GEKU (Gast)


Lesenswert?

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)

von Affenarm (Gast)


Lesenswert?

Manche Cortex M0 können auch bit-banding. Weiß allerdings nicht, ob es 
der vom TO genannte Typ auch kann.

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

von Volker S. (vloki)


Lesenswert?

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...?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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!

von Volker S. (vloki)


Lesenswert?

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%)

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Affenarm (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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).

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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
3
typedef struct bits {
4
    uint32_t b0 : 1;
5
    uint32_t b1 : 1;
6
    uint32_t b2 : 1;
7
    uint32_t b3 : 1;
8
    uint32_t b4 : 1;
9
    uint32_t rest : 27;
10
} BITS;
11
12
13
void reset_flag_4(BITS *b) {
14
    *b = (BITS) { .b4 = 1 };
15
}

100% Bitfelder. 100% richtig.

von Affenarm (Gast)


Lesenswert?

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. :-)

von Volker S. (vloki)


Lesenswert?

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: BSF INTCON, 7, ACCESS
3
!    INTCON |= 1<<7;
4
0x1650: BSF INTCON, 7, ACCESS
5
!    GIE = 1;
6
0x1652: BSF INTCON, 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...

Beitrag #5955755 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

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
> typedef struct bits {
3
>     uint32_t b0 : 1;
4
>     uint32_t b1 : 1;
5
>     uint32_t b2 : 1;
6
>     uint32_t b3 : 1;
7
>     uint32_t b4 : 1;
8
>     uint32_t rest : 27;
9
> } BITS;
10
> void reset_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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

Wilhelm M. schrieb:
> Ist auch nicht das, das der TO machen wollte.

Das ist genau das, was der TO machen wollte.

von Peter D. (peda)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

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.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Peter D. (peda)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von (prx) A. K. (prx)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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."

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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
void CAN_ISR (void) {
2
  uint32_t IR = 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.

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.