Forum: Mikrocontroller und Digitale Elektronik Viele Flags effizient speichern und schreiben/Byte in Bitfeld?


von Fragererer (Gast)


Lesenswert?

Ein C-programmierter Controller bekommt über Bussysteme Datenpakete 
zugeschickt, die u.A. zig Flags enthalten (Überstrom, Unterstrom, 
Übertemperatur etc). Diese Flags sollen lokal in einer Datenstruktur 
gespeichert werden, um den Zustand der sendenden Geräte abzubilden.
Es wäre am lesbarsten, die Datenstrukturen im Controller logisch 
abzubilden, so wie im Datenblatt beschrieben (zB durch verschachtelte 
Structs und Bitfelder). Das ist natürlich langsam. Die Auswertung später 
im Code wäre aber einfach, das Flag kann direkt abgefragt werden.
1
//Beispiel:
2
Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));
3
Device.Parameters.Signals.Undercurrent = (packet & (1 << 5));
4
Device.Parameters.Signals.Overtemp = (packet & (1 << 7));
5
6
if(Device.Parameters.Signals.Overtemp)
7
{
8
   //oh boy, do something
9
}

Alternativ kann ich die Pakete wie empfangen byteweise speichern, das 
braucht wenig Takte. Dafür müsste ich später mit Masken arbeiten und die 
Auswertung wäre deutlich kryptischer (Bitgeschubse eben).
1
//Beispiel:
2
Device.Parameters.Signals = packet;
3
4
if(Device.Parameters.Signals)
5
{
6
   for(uint8_t i = 0; i<8; i++)
7
   {
8
      //maskieren
9
      //bit 1? -> Position speichern
10
      //schieben
11
   } 
12
}

Ideal wäre es, wenn man ein empfangenes byte in einem Rutsch schreiben 
könnte, aber dann wie ein Bitfeld abfragen. Meines Wissens nach 
funktioniert das aber nicht, weil zB nicht definiert ist, wie die Bits 
in einem Bitfeld angelegt werden.
1
//Beispiel:
2
Device.Parameters.Signals = packet;
3
4
if(Device.Parameters.Signals.Overtemp)
5
{
6
   //oh boy, do something
7
}

Gibt es weitere Möglichkeiten, die ich übersehe? Wie löst man das am 
geschicktesten?

von Theor (Gast)


Lesenswert?

Wenn ich Dein Problem richtig verstehe, dann scheint mir die "union" das 
passende Mittel zu sein. Das Thema ist umfangreich, weil es ein paar 
Konsequenzen hat, ist aber beherrschbar - nur eben nicht in Kürze und 
gleichzeitig erschöpfend abzuhandeln.

Im wesentlichen aber läuft es (skizzenhaft) auf folgende beispielhafte 
Deklaration hinaus:
1
typedef union packet {
2
    struct {
3
        unsigned int bit0  : 1;
4
        unsigned int bit1  : 1;
5
        unsigned int bit2  : 1;
6
        unsigned int bit3  : 1;
7
        unsigned int bit4  : 1;
8
        unsigned int bit5  : 1;
9
        unsigned int bit6  : 1;
10
        unsigned int bit7  : 1;
11
        unsigned int bit8  : 1;
12
        unsigned int bit9  : 1;
13
        unsigned int bit10 : 1;
14
        unsigned int bit11 : 1;
15
        unsigned int bit12 : 1;
16
        unsigned int bit13 : 1;
17
        unsigned int bit14 : 1;
18
        unsigned int bit15 : 1;
19
    } bits;
20
21
    unsigned int data;
22
} packet_t;

Bitte lies mal in Deinem C Buch und ergänzend im C11-Standard darüber 
nach.

von Fragererer (Gast)


Lesenswert?

Theor schrieb:
> Wenn ich Dein Problem richtig verstehe, dann scheint mir die "union" das
> passende Mittel zu sein. Das Thema ist umfangreich, weil es ein paar
> Konsequenzen hat, ist aber beherrschbar - nur eben nicht in Kürze /und/
> gleichzeitig erschöpfend abzuhandeln.
>
> Im wesentlichen aber läuft es (skizzenhaft) auf folgende beispielhafte
> Deklaration hinaus:
>
> Bitte lies mal in Deinem C Buch und ergänzend im C11-Standard darüber
> nach.

Danke, das scheint mir das richtige Werkzeug zu sein.
Ich benutze C99, da sagt der Standard: "The order of allocation of 
bit-fields within a unit (high-order to low-order or low-order to 
high-order) is implementation-defined. The alignment of the addressable 
storage unit is unspecified."
Bedeutet das in diesem Kontext nicht, dass je nach Architektur der Wert 
von 'data' unterschiedlich ist?

von Theor (Gast)


Lesenswert?

Fragererer schrieb:
> Theor schrieb:
>> Wenn ich Dein Problem richtig verstehe, ...
>
> Danke, das scheint mir das richtige Werkzeug zu sein.

Bitteschön.

> Ich benutze C99, da sagt der Standard ...

> Bedeutet das in diesem Kontext nicht, dass je nach Architektur der Wert
> von 'data' unterschiedlich ist?

Das Thema ist, wie gesagt umfangreich. Und man muss bei solchen Themen, 
den philologischen Ehrgeiz eines Juristen haben. Der zitierte Satz sagt 
nur, dass die Reihenfolge, (eigentlich, genauer die Zuordnung von erstem 
Bit in der Reihenfolge zu entweder Low oder High-Byte eines 
Architektur-Wortes) in C99 nicht garantiert ist, nicht das die 
Reihenfolge je nach Architektur unterschiedlich ist .

Ich empfehle folgende Lektüre: Den Standard der zu Deinem Compiler 
passt. Da sich da Veränderungen ergeben habe, eben weil im 
Embedded-Bereich gewisse absolute Festlegungen zweckmäßig sind, auch den 
neueren Standard C11 und ggf. den Wechsel auf eine neuere 
Compilerversion. Ebenso und aus dem selben Grund die Compiler-Manuals.

von Theor (Gast)


Lesenswert?

Ah. Jetzt ist mir auch ein Flüchtigkeitsfehler unterlaufen. Tut mir 
leid.

"implementation-defined" sagt nicht - wie ich oben schrieb, dass die 
Reihenfolge nicht garantiert ist, sondern das sie 
"implementierungsabhängig" ist.

Hast Du wahrscheinlich selbst gemerkt, dass das nicht sein kann, aber 
ich schreibs nochmal sicherheitshalber.

Der Satz von mir oben, muss also lauten:

"Der zitierte Satz sagt nur, dass die Reihenfolge, (eigentlich, genauer 
die Zuordnung von erstem
Bit in der Reihenfolge zu entweder Low oder High-Byte eines
Architektur-Wortes) in C99 von der Implementierung abhängig ist, nicht 
das die Reihenfolge je nach Architektur unterschiedlich ist."

von Wilhelm M. (wimalopaan)


Lesenswert?

Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB 
ist, obgleich sie wahrscheinlich oft funktioniert.

1) Es wird die Komponente bits beschrieben
2) es wird data gelesen.

Damit ist bits das active member und von data lesen induziert UB.

Die Ausnahme

---
6.5.2.3/6

One special guarantee is made in order to simplify the use of unions: if 
a union contains several structures that share a common initial sequence 
(see below), and if the union object currently contains one of these 
structures, it is permitted to inspect the common initial part of any of 
them anywhere that a declaration of the completed type of the union is 
visible. Two structures share a common initial sequence if corresponding 
members have compatible types (and, for bit-fields, the same widths) for 
a sequence of one or more initial members.
---

zieht auch nicht leider.

Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.

von Possetitjel (Gast)


Lesenswert?

Fragererer schrieb:

> Ideal wäre es, wenn man ein empfangenes byte in einem
> Rutsch schreiben könnte, aber dann wie ein Bitfeld
> abfragen.

Warum "wie ein Bitfeld abfragen"? Was gefaellt Dir an
Deinem Vorschlag "(packet & (1 << 6));" nicht?

> Meines Wissens nach funktioniert das aber nicht, weil
> zB nicht definiert ist, wie die Bits in einem Bitfeld
> angelegt werden.

Kannst Du nicht versuchen, aus dem empfangenen Bytestrom
Bloecke konstanter Laenge (z.B. 32bit = 4 Byte) zu machen?
Idee ist, immer nur vollstaendige Datenfelder in so einem
Doppelwort zu haben, wobei die Anzahl der Datenfelder
unterschiedlich sein wird. Dazu muesstest Du Fuellbytes
einfuegen bzw. nach Bedarf auch Empfangsbytes doppelt speichern.

Die Bits dieser Doppelworte kannst Du wie gewohnt mit AND
und SHIFT abfragen.

Die Endianess muesstest Du nur einmal beim Konvertieren
beruecksichtigen.

von Fragererer (Gast)


Angehängte Dateien:

Lesenswert?

Theor schrieb:

> Ich empfehle folgende Lektüre: Den Standard der zu Deinem Compiler
> passt. Da sich da Veränderungen ergeben habe, eben weil im
> Embedded-Bereich gewisse absolute Festlegungen zweckmäßig sind, auch den
> neueren Standard C11 und ggf. den Wechsel auf eine neuere
> Compilerversion. Ebenso und aus dem selben Grund die Compiler-Manuals.

IAR EWARM. Ich habe das entsprechende Dokument angehängt.
S.347 "Pragma directives": bitfields - Controls the order of bitfield 
members.
S.349f "Descriptions of pragma directives" beschreibt die Möglichkeiten. 
Default ist:
1
joined_types
2
Bitfield members are placed depending on the byte
3
order. Storage containers of bitfields will overlap other
4
structure members. For more information, see
5
Bitfields, page 322

Alternativ gibt es 'disjoint_types'
1
Bitfield members are placed from the least significant
2
bit to the most significant bit in the container type.
3
Storage containers of bitfields with different base
4
types will not overlap.
 und 'reversed_disjoint_types'
1
Bitfield members are placed from the most significant
2
bit to the least significant bit in the container type.
3
Storage containers of bitfields with different base
4
types will not overlap.

Ich werde mal austesten, wie sich das auf den Maschinencode auswirkt.



Wilhelm M. schrieb:
> 1) Es wird die Komponente bits beschrieben
> 2) es wird data gelesen.

Wenn ich dich richtig verstehe ist es genau andersherum, d.h. es wird 
data geschrieben und bits gelesen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Fragererer schrieb:
>
> Wilhelm M. schrieb:
>> 1) Es wird die Komponente bits beschrieben
>> 2) es wird data gelesen.
>
> Wenn ich dich richtig verstehe ist es genau andersherum, d.h. es wird
> data geschrieben und bits gelesen.

Ja, wie auch immer. UB entsteht, wenn man das nicht-aktive Element 
verwendet.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB
> ist, obgleich sie wahrscheinlich oft funktioniert.

Ja, das ist sie. Man darf eben immer nur das Element lesen, das als 
letztes geschrieben wurde. Die Verwendung (bzw. der Missbrauch) von 
Unions zur Konvertierung verstößt gegen diese Regel.
Ich hab eh noch nie verstanden, warum Unions dafür so beliebt sind. Es 
ist umständlicher als ein Cast oder ein memcpy, weil man sich extra nur 
für die Konvertierung einen eigenen Datentyp definieren muss.

> Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.

Ja, auch hier ist es besser, sich diese Abhängigkeit zu ersparen.

von Fragerer (Gast)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB
>> ist, obgleich sie wahrscheinlich oft funktioniert.
>
> Ja, das ist sie. Man darf eben immer nur das Element lesen, das als
> letztes geschrieben wurde. Die Verwendung (bzw. der Missbrauch) von
> Unions zur Konvertierung verstößt gegen diese Regel.
> Ich hab eh noch nie verstanden, warum Unions dafür so beliebt sind. Es
> ist umständlicher als ein Cast oder ein memcpy, weil man sich extra nur
> für die Konvertierung einen eigenen Datentyp definieren muss.
>
>> Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.
>
> Ja, auch hier ist es besser, sich diese Abhängigkeit zu ersparen.

Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
Ist doch so nicht mehr undefined?

von Wilhelm M. (wimalopaan)


Lesenswert?

Fragerer schrieb:

>
> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
> Ist doch so nicht mehr undefined?

UB und implementation-defined sind zwei unterschiedliche Sachen!

von Fragerer (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Fragerer schrieb:
>
>>
>> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
>> Ist doch so nicht mehr undefined?
>
> UB und implementation-defined sind zwei unterschiedliche Sachen!

Okay, das war unsauber.
Trotzdem ist das Verhalten damit doch definiert.

von Rolf M. (rmagnus)


Lesenswert?

Fragerer schrieb:
> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
> Ist doch so nicht mehr undefined?

Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt, 
dass jeder Compiler es machen kann, wie er will, aber es muss 
dokumentiert sein. Das obige ist der Auszug aus der Doku eines ganz 
spezifischen Compilers. Der nächste Compiler kann es dann ganz anders 
machen.

von Fragerer (Gast)


Lesenswert?

Rolf M. schrieb:
> Fragerer schrieb:
>> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
>> Ist doch so nicht mehr undefined?
>
> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,
> dass jeder Compiler es machen kann, wie er will, aber es muss
> dokumentiert sein. Das obige ist der Auszug aus der Doku eines ganz
> spezifischen Compilers. Der nächste Compiler kann es dann ganz anders
> machen.

Schon klar. Fürs Erste soll mir das reichen.
Nochmal Danke für die vielen hilfreichen Antworten!

von A. S. (Gast)


Lesenswert?

Unions mit 1-4 Byte Zugriff macht man eigentlich nur für Register des 
µC, um die einzelnen Bits manipulieren zu können und dann in einem 
Rutsch zu lesen und zu schreiben.

Telegramme, vor allem wenn sie mehr als 4 Byte enthalten, schreibt man 
eigentlich in einer (verschachtelten) Struktur runter und betrachtet 
dann die ganze Struktur fürs Senden und beim Empfangen als Stream, also 
als void-Pointer mit Längenangabe.



Beispiel:
1
typedef struct
2
{
3
    Theader head;
4
    uint16  cnt;
5
    tXyZ    xyZ;
6
    t...    t...
7
}Tmsg1;
8
9
Tmsg1 msg1, RcvMsg1;
10
11
12
typedef struct
13
{
14
    Theader head;
15
    tAbc    abc;
16
    tefg    efg;
17
    t...    t...
18
}Tmsg2;
19
20
Tmsg2 msg2, RcvMsg2;
21
22
typedef enum
23
{
24
TYP_MSG_unknown,
25
TYP_MSG_1,
26
TYP_MSG_2,
27
}TTypMsg;
28
29
30
/* empfangsroutine */
31
void receiveMsg(TTypMsg typ, void *msg, int len)
32
{
33
     if(typ == TYP_MSG_1 && len == sizeof(RcvMsg1))
34
     {
35
         memcpy(RcvMsg1, *msg, len);    
36
     }
37
}
38
39
/* senden (geschieht auch mit memcpy)*/ 
40
sndMsg(TYP_MSG_2, &msg2, sizeof(msg2));

von Jan K. (jan_k)


Lesenswert?

Achim S. schrieb:
> Unions mit 1-4 Byte Zugriff macht man eigentlich nur für Register
> des
> µC, um die einzelnen Bits manipulieren zu können und dann in einem
> Rutsch zu lesen und zu schreiben.
>
Dachte genau das soll man nicht machen, da nicht portabel?
Gibts hier einige Threads zu, z.B. 
Beitrag "Bitfeld und union" oder 
Beitrag "Wie auf Union mit Bitfeldern direkt zugreifen?"

> Telegramme, vor allem wenn sie mehr als 4 Byte enthalten, schreibt man
> eigentlich in einer (verschachtelten) Struktur runter und betrachtet
> dann die ganze Struktur fürs Senden und beim Empfangen als Stream, also
> als void-Pointer mit Längenangabe.
>
> Beispiel:typedef struct
> {
>     Theader head;
>     uint16  cnt;
>     tXyZ    xyZ;
>     t...    t...
> }Tmsg1;
>
> Tmsg1 msg1, RcvMsg1;
>
> typedef struct
> {
>     Theader head;
>     tAbc    abc;
>     tefg    efg;
>     t...    t...
> }Tmsg2;
>
> Tmsg2 msg2, RcvMsg2;
>
> typedef enum
> {
> TYP_MSG_unknown,
> TYP_MSG_1,
> TYP_MSG_2,
> }TTypMsg;
>
> /* empfangsroutine */
> void receiveMsg(TTypMsg typ, void *msg, int len)
> {
>      if(typ == TYP_MSG_1 && len == sizeof(RcvMsg1))
>      {
>          memcpy(RcvMsg1, *msg, len);
>      }
> }
>
> /* senden (geschieht auch mit memcpy)*/
> sndMsg(TYP_MSG_2, &msg2, sizeof(msg2));

Auch das ist nicht portabel. Leider.

Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da 
schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe 
per Bitfields (sind nicht gut...), portables Serialisieren und 
Übertragen von Daten (Endianess, union Problematik [gelesene Variable != 
geschriebene ist implementation defined]). Die Fragen treten immer und 
immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun 
portabel und möglichst elegant implementiert werden sollte.

Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?

Schöne Grüße,
Jan

: Bearbeitet durch User
von Fragerer (Gast)


Lesenswert?

Jan K. schrieb:
> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?

Konkret für mich und in diesem Projekt ist das "nice to have", aber 
erstmal muss die Kiste schnell laufen und beweisen ob etwas 
funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.

von Bernd K. (prof7bit)


Lesenswert?

Rolf M. schrieb:
> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,
> dass jeder Compiler es machen kann, wie er will,

"machen was er will" wäre undefined.

Der Compiler wird sich auch an das jeweilige Plattform ABI halten 
müssen, da stehen die meisten implementation defined Sachen schwarz auf 
weiß drin, nur was danach immer noch übrig bleibt fällt wirklich unter 
"undefined".

Und im ABI wiederum sind die meisten Sachen nicht willkürlich festgelegt 
sondern folgen einer gewissen Logik die sich oftmals geradezu aufdrängt, 
z.B. Alignment und Padding von Structs, der Aufbau von Unions und 
dergleichen.

Und wenn man es gut meint mit seinem Nachfolger oder seinem eigenen 
zukünftigen Ich oder seinen Enkelkindern kann ja noch ein paar 
Compile-Time Asserts an den wackeligen Stellen einbauen die 
Compilerfehler werfen a la: "#error: Guten Tag mein sehr junger Padawan, 
Du wirst Dich nicht mehr an mich erinnern, ich bin Dein Urgroßvater. Wie 
ich sehe willst Du also wirklich diesen alten Code auf einer anderen 
Plattform wiederbeleben? Dann finde als erstes den Compilerschalter der 
xy bewirkt.".

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan K. schrieb:

> Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da
> schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe
> per Bitfields (sind nicht gut...), portables Serialisieren und
> Übertragen von Daten (Endianess, union Problematik [gelesene Variable !=
> geschriebene ist implementation defined]). Die Fragen treten immer und
> immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun
> portabel und möglichst elegant implementiert werden sollte.

M.E: wäre das eine lohnenswerte Aufgabe.

> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?

Auf gar keinen Fall (jedenfalls bei mir). Mir ist es sogar sehr wichtig, 
denn ich muss auch sehr oft Code aus dem non-µC-Bereich für viele 
Plattformen (e.g. Linux, *BSD, Windows, Solaris, HP-UX) schreiben. Und 
da lernt man Plattformneutralität bzw. Portabilität sehr zu schätzen - 
und zwar von Anfang an.

von Wilhelm M. (wimalopaan)


Lesenswert?

Fragerer schrieb:
> Jan K. schrieb:
>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
>
> Konkret für mich und in diesem Projekt ist das "nice to have", aber
> erstmal muss die Kiste schnell laufen und beweisen ob etwas
> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.

Dann wird es wohl nie geschehen ...

von Fragerer (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Fragerer schrieb:
>> Jan K. schrieb:
>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
>>
>> Konkret für mich und in diesem Projekt ist das "nice to have", aber
>> erstmal muss die Kiste schnell laufen und beweisen ob etwas
>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.
>
> Dann wird es wohl nie geschehen ...

Das ist definitiv möglich - wenn es bei der Plattform bleibt und 
funktioniert, warum nicht...

von Wilhelm M. (wimalopaan)


Lesenswert?

Fragerer schrieb:
> Wilhelm M. schrieb:
>> Fragerer schrieb:
>>> Jan K. schrieb:
>>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
>>>
>>> Konkret für mich und in diesem Projekt ist das "nice to have", aber
>>> erstmal muss die Kiste schnell laufen und beweisen ob etwas
>>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.
>>
>> Dann wird es wohl nie geschehen ...
>
> Das ist definitiv möglich - wenn es bei der Plattform bleibt und
> funktioniert, warum nicht...

Das war nur eine Bemerkung aus der Erfahrung in SW-Projekten ;-) "Das 
machen wir später" bedeutet leider manchmal eben: nie. Vor allem 
natürlich, wenn da kein gescheites Projektmanagement beteiligt ist ...

von Jan K. (jan_k)


Lesenswert?

Fragerer schrieb:
> Wilhelm M. schrieb:
>> Fragerer schrieb:
>>> Jan K. schrieb:
>>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
>>>
>>> Konkret für mich und in diesem Projekt ist das "nice to have", aber
>>> erstmal muss die Kiste schnell laufen und beweisen ob etwas
>>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.
>>
>> Dann wird es wohl nie geschehen ...
>
> Das ist definitiv möglich - wenn es bei der Plattform bleibt und
> funktioniert, warum nicht...

Das fiese ist halt, falls man irgendwann auf einmal doch mit einem 
anderen System reden muss, das z.B. eine andere Endianess hat ist man 
geliefert. All deine Binärstreams müssen auseinander genommen werden. 
Sowas erneut zu implementieren ist mega aufwendig und fehleranfällig.

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan K. schrieb:

> Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da
> schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe
> per Bitfields (sind nicht gut...), portables Serialisieren und
> Übertragen von Daten (Endianess, union Problematik [gelesene Variable !=
> geschriebene ist implementation defined]). Die Fragen treten immer und
> immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun
> portabel und möglichst elegant implementiert werden sollte.

Ganz klasse wäre eigentlich ein clang static-analyzer dafür ...

von eProfi (Gast)


Lesenswert?

> if(Device.Parameters.Signals.Overtemp)
> {
>    //oh boy, do something
> }
> Gibt es weitere Möglichkeiten, die ich übersehe?
> Wie löst man das am geschicktesten?

So:
#define sigUndrcurr (1<<5)  //in dieser Reihenfolge, sonst 
unübersichtlich
#define sigOvercurr (1<<6)
#define sigOvertemp (1<<7)

if(Device.Parameters.Signals & sigOvertemp)
{
   //oh boy, do something
}

von W.S. (Gast)


Lesenswert?

Fragererer schrieb:
> Ein C-programmierter Controller bekommt über Bussysteme Datenpakete
> zugeschickt, die u.A. zig Flags enthalten (Überstrom, Unterstrom,
> Übertemperatur etc). Diese Flags sollen lokal in einer Datenstruktur
> gespeichert werden, um den Zustand der sendenden Geräte abzubilden.
> Es wäre am lesbarsten, die Datenstrukturen im Controller logisch
> abzubilden, so wie im Datenblatt beschrieben (zB durch verschachtelte
> Structs und Bitfelder). Das ist natürlich langsam.

Du machst einen Generalfehler, indem du versuchst, Dinge in die 
Ausdrucksmöglichkeiten von C hineinzupressen, die von sonstwo kommen und 
dort keinen definierten Platz haben. Bitfelder zum Beispielsind ne 
Krätze, weil sie eben nur innerhalb eines kompilierten Projektes ihre 
volle Gültigkeit haben. Schon dann, wenn dir ein anderes Gerät ein 
Datenpaket zuschickt, was mit einem anderen Compiler oder gar anderer 
HW-Plattform erzeugt wurde, kannst du dir nicht sicher sein, daß du 
auf die richtigen Dinge zugreifst.

Mein Rat:
1. definiere dir ein hardwareunabhängiges Protokoll, also wie denn nun 
ganz genau deine Informationen im Datenpaket dargestellt sein müssen. 
Das brauchst du dringendst, damit Sender aller Art und Empfänger aller 
Art etwas haben, worauf sie sich verlassen können. Endianess ist ein 
Thema, also beschränke dich auf Bytes, am besten Textzeichen in deinen 
Datenpaketen.

2. kapsele dieses Zeugs in einer Art Objekt. Also anstatt
{c]
Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));
[/c]
definiere dir einen Satz von Funktionen zum Setzen und Abfragen, also 
etwa sowas in de Art:
1
 bool isOvercurrent(void);
2
 void setOvercurrent(bool yesno);
um die Innereien gegenüber deiner sonstigen Firmware zu kapseln. Und 
knicke den Gedanken an Bitfelder. Das hat sich schon seit langem als 
prächtige Fehlerquelle erwiesen, wenn man nicht immerzu höllisch 
aufpaßt.

W.S.

von Rolf M. (rmagnus)


Lesenswert?

Bernd K. schrieb:
> Rolf M. schrieb:
>> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,
>> dass jeder Compiler es machen kann, wie er will,
>
> "machen was er will" wäre undefined.

Nein, undefined heißt, dass er sich nicht drum kümmern muss, was 
passiert. Der Compilerhersteller darf davon ausgehen, dass das nicht 
vorkommt, und wenn doch, passiert halt irgendwas ggf. unvorhergesehenes 
("nasal demons" für diejenigen, die das noch kennen).
Ich glaube, wir haben einfach eine unterschiedliche Definition von 
"machen was er will". Für mich heißt es, dass ISO C nicht festlegt, wie 
es umzusetzen ist, sondern dass das im Aufgabenbereich der 
Compiler-Hersteller liegt.

> Der Compiler wird sich auch an das jeweilige Plattform ABI halten
> müssen, da stehen die meisten implementation defined Sachen schwarz auf
> weiß drin, nur was danach immer noch übrig bleibt fällt wirklich unter
> "undefined".

Klar muss er sich um die Einhaltung eines ABI kümmern, wenn er dieses 
korrekt umsetzen will. Läuft für mich dennoch unter "machen was er 
will", denn ISO C zwingt ihn ja nicht dazu, ein bestimmtes ABI 
umzusetzen. Wir sprechen schließlich davon, was "implementation-defined" 
nach ISO-C bedeutet.

> Und im ABI wiederum sind die meisten Sachen nicht willkürlich festgelegt
> sondern folgen einer gewissen Logik die sich oftmals geradezu aufdrängt,
> z.B. Alignment und Padding von Structs, der Aufbau von Unions und
> dergleichen.

Allerdings ist das ABI in der Regel mindestens von der 
Prozessorarchitektur abhängig, und somit wird es schon der gleiche 
Compiler in seinen Ausprägungen für unterschiedliche Architekturen 
wahrscheinlich unterschiedlich machen - vor allem, wenn die ABIs das 
jeweils so verlangen. Ggf. gibt's auch mal mehr als ein ABI für eine 
Architektur, und der Compiler kann per Kommandozeile umgeschaltet 
werden.

> Und wenn man es gut meint mit seinem Nachfolger oder seinem eigenen
> zukünftigen Ich oder seinen Enkelkindern kann ja noch ein paar
> Compile-Time Asserts an den wackeligen Stellen einbauen die
> Compilerfehler werfen a la: "#error: Guten Tag mein sehr junger Padawan,
> Du wirst Dich nicht mehr an mich erinnern, ich bin Dein Urgroßvater. Wie
> ich sehe willst Du also wirklich diesen alten Code auf einer anderen
> Plattform wiederbeleben? Dann finde als erstes den Compilerschalter der
> xy bewirkt.".

Das gehört für mich zum Minimum dessen, was man tun sollte.

Wilhelm M. schrieb:
> Das war nur eine Bemerkung aus der Erfahrung in SW-Projekten ;-) "Das
> machen wir später" bedeutet leider manchmal eben: nie. Vor allem
> natürlich, wenn da kein gescheites Projektmanagement beteiligt ist ...

In der Regel ist "später" auch weder Zeit, noch Budget für sowas da. 
Deshalb leben Provisorien so lange.

von Fragerer (Gast)


Lesenswert?

W.S. schrieb:
> Mein Rat:
> 1. definiere dir ein hardwareunabhängiges Protokoll, also wie denn nun
> ganz genau deine Informationen im Datenpaket dargestellt sein müssen.
> Das brauchst du dringendst, damit Sender aller Art und Empfänger aller
> Art etwas haben, worauf sie sich verlassen können. Endianess ist ein
> Thema, also beschränke dich auf Bytes, am besten Textzeichen in deinen
> Datenpaketen.
Ist exakt definiert, die Sender sind fertige Geräte mit Datenblättern 
usw. Warum glaubst du dass nichtmal das Protokoll definiert ist?
> 2. kapsele dieses Zeugs in einer Art Objekt. Also anstatt
> {c]
> Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));
> [/c]
> definiere dir einen Satz von Funktionen zum Setzen und Abfragen, also
> etwa sowas in de Art:
>
1
>  bool isOvercurrent(void);
2
>  void setOvercurrent(bool yesno);
3
>
> um die Innereien gegenüber deiner sonstigen Firmware zu kapseln. Und
> knicke den Gedanken an Bitfelder. Das hat sich schon seit langem als
> prächtige Fehlerquelle erwiesen, wenn man nicht immerzu höllisch
> aufpaßt.

Das würde einen riesigen undefinierten Haufen von Funktionen und 
Variablen bedeuten, denn ich bekomme nicht nur Flags von den Sendern, 
sondern alle möglichen verschiedenen Werte und Datentypen.
So habe ich eine Datenstruktur, die alle verfügbaren Daten sehr 
übersichtlich und verständlich auflistet - so wie sie im Datenblatt des 
Senders beschrieben sind. Die Daten werden beim Empfang sauber in die 
Struktur geschrieben und sind danach selbsterklärend - das ist doch 
fehlerunanfälliger als deine Variante?

von Bernd K. (prof7bit)


Lesenswert?

Fragerer schrieb:
> Die Daten werden beim Empfang sauber in die
> Struktur geschrieben und sind danach selbsterklärend - das ist doch
> fehlerunanfälliger als deine Variante?

Es gibt auch noch die Möglichkeit Code automatisch zu generieren zu 
lassen, gerade dann wenn eine einzige Änderung an einem Datenfeld oder 
ein neues Datenfeld immer gleich an drei oder vier Stellen 
(structeintrag, getter, setter, etc.) penibelst synchron mitzupflegenden 
Boilerplate bedingt.

So lass ich zum beispiel die Arrays für USB-Descriptoren zur Compilezeit 
per Python-Script erzeugen, all die penibelst anzupassenden 
Längenangaben, indices, UTF16-Strings, etc. in so einer monströsen 
Byte-Array-Wüste bei der auch keine Structs mehr helfen werden so 
automatisch erzeugt aus einer einzigen Quelle in der nur das nötigste so 
kompakt und übersichtlich wie möglich hingeschrieben wird.

Meist endet bei solchen Vorhaben schnell die Ausdrucksfähigkeit des 
eingebauten C-Präprozessors (von der Debugbarkeit ganz zu schweigen), 
jedoch wenn man sich stattdessen dazu durchringt die Codeerzeugung in 
einer gut lesbaren Scriptsprache zu implementieren kann man nach 
Herzenslust aus einer simplen Beschreibung der Datenstruktur tonnenweise 
100% korrekten C-Boilerplate erzeugen ohne sich auch nur im Geringsten 
verrenken zu müssen.

von Possetitjel (Gast)


Lesenswert?

W.S. schrieb:

> Also anstatt
> Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));
> [...]

Hm. Ich habe das meiste hier im Thread gelesen und immer
noch nicht verstanden, was an meinem Vorschlag von oben
falsch sein soll.

Die Bitordnung innerhalb eines Bytes ist ja hoffentlich
eindeutig. Wenn man die empfangenen Bytes durch Schieben
und Verodern zu z.B. maximal 32bit langen Worten zusammen-
baut, sollte die Ordnung ja immer noch eindeutig sein,
und wenn man NUR mit der 32bit-Variablen rechnet, ist
auch die Endianess wurscht.
Probleme gibt es nach meinem Verstaendnis nur bei
Mischung unterschiedlicher Zugriffe auf denselben
Speicherbereich.

(packet & (1 << 19)) liefert halt immer das Bit mit
der Wertigkeit 2^19, voellig egal, wo dieses Bit im
Speicher steht.

Was ist daran falsch?

von W.S. (Gast)


Lesenswert?

Possetitjel schrieb:
> Was ist daran falsch?

Ganz einfach: der TO hat mit keiner einzigen Silbe davon gesprochen, wie 
denn nun genau seine ominösen Datenpakete aufgebaut sind und wie sie 
denn eintreffen.

Da kann man ohne nähere Kenntnis keine Vorschläge a la "(packet & (1 << 
19))" machen. Das ist der Punkt.

Der TO hat offensichtlich ebenso übersehen, daß das von ihm so 
abgelehnte "Bitgeschubse" ja so oder so fällig ist. Entweder indem man 
in einem LowLevel-Treiber die Datenpakete auseinander nimmt und die 
Bruchstücke in ein struct eigener Wahl hineinschreibt oder indem man das 
Paket so läßt wie es ist und mittels spezieller Zugriffs-Funktionen (a 
la bool isOvercurrent(void);) die gewünschte Information aus dem Paket 
herauspickt.

Ich bin allemal für die letztere Methode, weil sie die Datenpakete 
besser kapselt und im übergeordneten Programm für mehr Les- und 
Wartbarkeit sorgt.

W.S.

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.