Forum: Mikrocontroller und Digitale Elektronik Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)


von Markus S. (markussch)


Lesenswert?

Hallo,

ich habe folgende Struct:

struct  {
  uint8_t bit7:1;
  uint8_t bit6:1;
  uint8_t bit5:1;
  uint8_t bit4:1;
  uint8_t bit3:1;
  uint8_t bit2:1;
  uint8_t bit1:1;
  uint8_t bit0:1;
} Input;

Hierüber kann ich ja auf jedes einzelne Bit mit Beispiel:
if (Input.bit0==1) zugreifen, soweit klar.
Jetzt möchte ich aber z.B. alle 8 Bits gleichzeitig aus einer uint8_t 
lesen, wie mache ich das?
uint_8 Data = 0b10101010;
Input = Data; funktioniert so natürlich nicht.

Wie muss ich das machen?

von Jim M. (turboj)


Lesenswert?

In C gibt es union:
1
typedef union{
2
struct  {
3
  uint8_t bit7:1;
4
  uint8_t bit6:1;
5
  uint8_t bit5:1;
6
  uint8_t bit4:1;
7
  uint8_t bit3:1;
8
  uint8_t bit2:1;
9
  uint8_t bit1:1;
10
  uint8_t bit0:1;
11
} Input;
12
13
uint8_t Byte;
14
} Input_ut;

Aber Vorsicht, Bitfields sind weder portabel noch atomar - man es 
vermeiden diese direkt für Register Zugriffe zu verwenden.

von Markus S. (markussch)


Lesenswert?

Mit deinem Code funktioniert Input.bit0 usw. nicht mehr...

von Alexander (alecxs)


Lesenswert?

Da im Betreff steht ESP32 versuche `__attribute__((_packed_))` gegen 
das Padding und dann memcpy oder byte-ptr. Ist zwar verboten aber wenn 
es nur für Dich ist, geht's.

: Bearbeitet durch User
von N. M. (mani)


Lesenswert?

Markus S. schrieb:
> Mit deinem Code funktioniert Input.bit0 usw. nicht mehr...

Das geht schon. Nur kenne ich die Reihenfolge der Bits andersrum.
1
#include<stdio.h>
2
3
typedef union{
4
struct  {
5
  uint8_t bit0:1;
6
  uint8_t bit1:1;
7
  uint8_t bit2:1;
8
  uint8_t bit3:1;
9
  uint8_t bit4:1;
10
  uint8_t bit5:1;
11
  uint8_t bit6:1;
12
  uint8_t bit7:1;
13
} Input;
14
uint8_t Byte;
15
} Input_ut;
16
17
int main()
18
{
19
    Input_ut Test;
20
    Test.Byte=0;
21
    //Bit 0 setzen und ausgeben
22
    Test.Input.bit0 =1;
23
    printf("%d\n", Test.Byte);
24
    //Ausgabe: 1
25
26
    //Bit 7 setzen und ausgeben
27
    Test.Input.bit6 =1;
28
    printf("%d\n", Test.Byte);
29
    //Ausgabe: 65 (64+1)
30
31
    return 0;
32
}

von Harald K. (kirnbichler)


Lesenswert?

N. M. schrieb:
> Das geht schon.

Nein, da muss noch ein zusätzliches .Input verwendet werden.

Ganz am Anfang:
1
struct { ... } Input;
2
3
Input.bit0 = 1;

Deine Union:
1
Input_ut Input;
2
3
Input.Input.bit0 = 1;

Wenn man die Struktur "Input" in Deiner Union namenlos lässt, geht's 
aber:
1
typedef union
2
{
3
  struct  
4
  {
5
    uint8_t bit0:1;
6
    uint8_t bit1:1;
7
    uint8_t bit2:1;
8
    uint8_t bit3:1;
9
    uint8_t bit4:1;
10
    uint8_t bit5:1;
11
    uint8_t bit6:1;
12
    uint8_t bit7:1;
13
  };
14
  uint8_t Byte;
15
} Input_ut;


(abgesehen davon hast Du den Threadstarter vermutlich schon damit 
verwirrt, daß Du die union als typedef angelegt und nicht direkt 
instanziiert hast)

von N. M. (mani)


Lesenswert?

Harald K. schrieb:
> Wenn man die Struktur "Input" in Deiner Union namenlos lässt, geht's
> aber:

Genau so steht es ja auch in dem Beispiel das ich gemacht hatte.
Ich hatte nur das Beispiel von turboj weiter verwendet.

Harald K. schrieb:
> Nein, da muss noch ein zusätzliches .Input verwendet werden.

Sollte der TO damit überfordert sein, dann sollte er dringend vorher 
(noch)mal ein C/CPP Buch lesen.

Harald K. schrieb:
> Wenn man die Struktur "Input" in Deiner Union namenlos lässt, geht's
> aber

Und wenn er der struct noch einen Konstruktor verpasst kann er die 
Struktur auch gleich noch initialisieren.
Er verwendet ja sowieso einen CPP Compiler.

: Bearbeitet durch User
von Marco H. (damarco)


Lesenswert?

Ganz einfach, dann sind Bitfelder ungeeignet. Dann verwendet man Masken 
und dann gibt es auch keine Überraschungen.

von Harald K. (kirnbichler)


Lesenswert?

N. M. schrieb:
> Genau so steht es ja auch in dem Beispiel das ich gemacht hatte.

Nö, ist nicht der Fall. Sieh mal genau hin. Sowohl "Jim" als auch Du 
geben der Struktur innerhalb der Union den Namen "Input".

von N. M. (mani)


Lesenswert?

Harald K. schrieb:
> Nö, ist nicht der Fall. Sieh mal genau hin. Sowohl "Jim" als auch Du
> geben der Struktur innerhalb der Union den Namen "Input".

Wie gesagt ja, da hast du Recht.
Aber das Beispiel macht richtige Zugriffe, übersetzt und macht richtige 
Ausgaben.

von Wilhelm M. (wimalopaan)


Lesenswert?

N. M. schrieb:
> Er verwendet ja sowieso einen CPP Compiler.

Und schwupp: UB ;-)

von Harald K. (kirnbichler)


Lesenswert?

N. M. schrieb:
> Aber das Beispiel macht richtige Zugriffe, übersetzt und macht richtige
> Ausgaben.

Ja, aber es muss anders geschrieben werden als das Original.

Bleibt die Struktur namenlos, ist das nicht der Fall.

Dann nämlich kann man schreiben
1
Input_ut In;
2
3
In.bit0 = 1;
4
5
In.Byte = 0xff;

Hat die Struktur einen Namen, muss man das hingegen so schreiben:

1
Input_ut In;
2
3
In.Input.bit0 = 1; // <-- Da!
4
5
In.Byte = 0xff;

von Wilhelm M. (wimalopaan)


Lesenswert?

Harald K. schrieb:
>
1
> Input_ut In;
2
> 
3
> In.bit0 = 1;
4
> 
5
> In.Byte = 0xff;
6
>

Warum macht man so einen Mist. In C++ ist das UB!

Und was ist darin einfacher, als
1
uint8_t v = ...;
2
3
bit<0>(v) = true;

In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine 
union.

von Frank O. (fop)


Lesenswert?

N. M. schrieb:
> Nur kenne ich die Reihenfolge der Bits andersrum.

Das darf der Compilerprogrammierer machen, wie er Bock hat oder wie er 
es für einfacher hält. Das ist gemeint, wenn hier jemand von 
undefiniertem Verhalten schreibt (undefined behavior = UB). Man kann 
sich noch nichtmal beschweren, wenn er seine Meinung von Compilerversion 
zu Compilerversion ändert. Wer den Compiler oder gar noch den 
Zielprozessor wechselt hat noch größere Chancen, dass Unerwartetes 
geschieht. Wer will, darf die Mitglieder der Struktur auch in der 
.h(pp)-Datei anders anordnen als in der .c(pp)-Datei, oder von 8 auf 40 
Mitglieder aufstocken. Irgendwer gräbt vielleicht noch einen Compiler 
aus, der die Namen der Mitglieder nach dem Alphabet anordnet oder in der 
Reihenfolge des ersten Lesezugriffs, selbstverständlich abhängig von der 
Reihenfolge, in der die Dateien übersetzt werden.

Langer Rede kurzer Sinn : verlasse Dich niemals darauf, wie ein Compiler 
etwas hinter den Kulissen umsetzt.
1
Input.bit0 = (Data >> 0U) & 1U;
2
Input.bit1 = (Data >> 1U) & 1U;
3
Input.bit2 = (Data >> 2U) & 1U;
4
Input.bit3 = (Data >> 3U) & 1U;
5
Input.bit4 = (Data >> 4U) & 1U;
6
Input.bit5 = (Data >> 5U) & 1U;
7
Input.bit6 = (Data >> 6U) & 1U;
8
Input.bit7 = (Data >> 7U) & 1U;

Und zurück :
1
Data = ((uint8_t)(Input.bit0) << 0U) |
2
  ((uint8_t)(Input.bit1) << 1U) |
3
 ((uint8_t)(Input.bit2) << 2U) |
4
 ((uint8_t)(Input.bit3) << 3U) |
5
 ((uint8_t)(Input.bit4) << 4U) |
6
 ((uint8_t)(Input.bit5) << 5U) |
7
 ((uint8_t)(Input.bit6) << 6U) |
8
 ((uint8_t)(Input.bit7) << 7U);

Das mit dem "Funktionstemplate bit<>()" ist noch besser, aber halt auch 
höhere C++ Kunst und sollte, wie Wilhelm M. (wimalopaan) schrieb, intern 
so wie die Beispiele hier funktionieren.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jim M. schrieb:
> In C gibt es union:
>
> typedef union{
> struct  {
>   uint8_t bit7:1;
>   uint8_t bit6:1;

Anonyme Komposite sind eine Erweiterung von GNU-C, oder ab C11:

https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html

von Wilhelm M. (wimalopaan)


Lesenswert?

Frank O. schrieb:
> Das darf der Compilerprogrammierer machen, wie er Bock hat oder wie er
> es für einfacher hält. Das ist gemeint, wenn hier jemand von
> undefiniertem Verhalten schreibt (undefined behavior = UB).

Das ist "nur" implementation-defined ;-) Machts aber auch nicht besser.

Was in C++ UB ist, ist der Zugriff auf ein non-active-member der union. 
In C ist diese Art von type-punning explizit erlaubt. In C++ geht das 
wegen ggf. DT mit nicht-trivialen Konstruktoren / Destruktoren nicht 
(ohne weiteres, möglich ist es schon ...).

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
>
> Anonyme Komposite sind eine Erweiterung von GNU-C, oder ab C11:
>
> https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html

Das war vor 12 Jahren! Der aktuelle default im gcc ist gnu17.

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
> union.

kannst du das näher ausführen ELI5

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Wilhelm M. schrieb:
>> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
>> union.
>
> kannst du das näher ausführen ELI5

Templates bekommt man erst mit sechs in der Schule ;-)

von Alexander (alecxs)


Lesenswert?

Ich hab sehr viele structs die ich füllen muss, deswegen habe ich 
memcpy() genutzt. funktioniert mit `__attribute__((packed))` auch ganz 
gut. Allerdings habe ich ein Logikproblem, habe nun festgestellt dass es 
bei krummen Datentypen (ungerade Bitzahl) doch nicht so funktioniert wie 
ich dachte.

funktioniert (das `__attribute__((packed))` bitte dazu denken)
1
struct  {
2
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
3
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
4
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
5
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
6
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
7
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
8
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
9
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
10
} Input;
11
12
memcpy(&Input, Data, len);

funktioniert auch, wenn man das struct byteweise aufteilt
1
struct  {
2
// byte 1
3
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
4
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
5
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
6
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
7
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
8
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
9
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
10
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
11
// bytes 2, 3, 4
12
  uint8_t var1  : 8;  // OFFSET 8, LENGTH 8
13
  uint8_t var2  : 8; // OFFSET 16, LENGTH 8
14
  uint8_t var3  : 8; // OFFSET 24, LENGTH 8
15
// byte 5
16
  uint8_t var4  : 3; // OFFSET 37, LENGTH 3
17
  bool bit36    : 1; // OFFSET 36, LENGTH 1
18
  bool bit35    : 1; // OFFSET 35, LENGTH 1
19
  bool bit34    : 1; // OFFSET 34, LENGTH 1
20
  bool bit33    : 1; // OFFSET 33, LENGTH 1
21
  bool          : 1;
22
// byte 6
23
  uint8_t var5  : 8; // OFFSET 40, LENGTH 8
24
// byte 7
25
  uint8_t var66 : 2; // OFFSET 54, LENGTH 2
26
  uint8_t var64 : 2; // OFFSET 52, LENGTH 2
27
  uint8_t var62 : 2; // OFFSET 50, LENGTH 2
28
  bool          : 1;
29
  bool bit48    : 1; // OFFSET 48, LENGTH 1
30
// byte 8
31
  uint8_t var7  : 8; // OFFSET 56, LENGTH 8
32
} Input2;
33
34
memcpy(&Input2, Data, len);

funktioniert bedingt, aber var1 + var3 benötigen nachher noch 
Konvertierung little endian <-> big endian. lässt sich lösen, kein 
Problem.
1
struct  {
2
  uint8_t var0  : 8; //  OFFSET 0, LENGTH 8
3
  uint16_t var1 :16; //  OFFSET 8, LENGTH 16
4
  uint16_t var3 :16; // OFFSET 24, LENGTH 16
5
  uint8_t var5  : 8; // OFFSET 40, LENGTH 8
6
} Input3;
7
8
memcpy(&Input3, Data, len);

aber
1
struct  {
2
// byte 1
3
  uint8_t       : 3;
4
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
5
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
6
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
7
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
8
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
9
// byte 2
10
  uint8_t       : 3;
11
  bool bit12    : 1; // OFFSET 12, LENGTH 1
12
  bool bit11    : 1; // OFFSET 11, LENGTH 1
13
  bool bit10    : 1; // OFFSET 10, LENGTH 1
14
  bool bit9     : 1;  // OFFSET 9, LENGTH 1
15
  bool bit8     : 1;  // OFFSET 8, LENGTH 1
16
// byte 3
17
  bool bit23    : 1; // OFFSET 23, LENGTH 1
18
  bool bit22    : 1; // OFFSET 22, LENGTH 1
19
  uint8_t       : 6;
20
// byte 4
21
  bool bit28    : 1; // OFFSET 28, LENGTH 1
22
  bool bit27    : 1; // OFFSET 27, LENGTH 1
23
  bool bit26    : 1; // OFFSET 26, LENGTH 1
24
  bool bit25    : 1; // OFFSET 25, LENGTH 1
25
  bool bit24    : 1; // OFFSET 24, LENGTH 1
26
// byte 4 (rest) + byte 5
27
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
28
} Input4;
29
30
memcpy(&Input4, Data, len);

funktioniert nicht. für bit24 ... bit28 oder var5 völlig falsche Bits. 
die bools sind versetzt, lässt sich lösen. aber keinen blassen Schimmer 
wie man var5 füllt (und endianess korrigiert). Ich brauche aber die 
structs für den Zugriff = Input4.var5

Außerdem sind die structs autogeneriert, daher müsste ich später ein 
neues Script schreiben. Aber erstmal brauche ich die Logik, selbst wenn 
ich es mit Bitmasken und  Verschiebung machen würde ist es mir nicht 
klar welche Bits wohin kommen. Kann jemand das Muster erkennen?

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Aber erstmal brauche ich die Logik, selbst wenn
> ich es mit Bitmasken und  Verschiebung machen würde ist es mir nicht
> klar welche Bits wohin kommen.

Ja, wenn Dir das nicht klar ist, wie soll Dir dann geholfen werden.

Beschreibe erstmal klar die Position der Bits in Data!

von Alexander (alecxs)


Lesenswert?

hier ein Beispiel:

Data (Bit 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)

Input3.var0 (Bit 1, 2, 3, 4, 5, 6, 7, 8)

Input3.var1 (Bit 17, 18, 19, 20, 21, 22, 23, 24, 9, 10, 11, 12, 13, 14, 
15, 16)

Input3.var3 (Bit 33, 34, 35, 36, 37, 38, 39, 40, 25, 26, 27, 28, 29, 30, 
31, 32)

Input3.var5 (Bit 41, 42, 43, 44, 45, 46, 47, 48)

gleiche Logik sollte für Input4 gelten, aber ich komm nicht drauf.

von Wilhelm M. (wimalopaan)


Lesenswert?

Das sind doch alles ganze Bytes.

var0 = Byte0
var1 = [high = Byte2, low = Byte1];
var2 = ?
var3 = [high = Byte4, low = Byte3];
var4 = ?
var5 = Byte5

von Alexander (alecxs)


Lesenswert?

Ich habe immer nur StructName + MemberName + MemberOffset + MemberLänge 
in Textform gegeben, keine konkreten Infos über den Datentyp. Das können 
ganze Bytes sein oder Bitfelder. Sollte doch möglich sein anhand der 
ersten drei Beispiele die Logik für das vierte abzuleiten. Das Muster 
ist erkennbar, an den krummen Datentypen scheitert die Anwendung. Die 
Endianess könnte auch andersrum sein, war nur geraten.

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Ich habe jetzt var5 geteilt, damit werden die Bits korrekt zugeordnet 
und die gleiche Logik angewendet wie in den anderen drei Beispielen.

Nur wusste ich nicht wie man den Split synchronisieren kann. Ich habe 
mich bei ChatGPT angemeldet, heraus kam das.

Eine Überladung für das Schreiben, und eine für das Lesen.

Nun könnte ich ja aber mehrere uint16_t in dem Struct haben, und ChatGPT 
konnte mir nicht die Frage beantworten wie ich die zweite Überladung 
spezifisch für var5 definieren kann.
1
// byte 4
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8
9
  uint8_t byte5 : 8;
10
11
// byte 4 (rest) + byte 5
12
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
13
14
  // Überladung des Zuweisungsoperators "=" für var5
15
  Input4& operator=(const uint16_t value) {
16
    var5 = value;
17
    rest4 = (value >> 8) & 0b00000111;
18
    byte5 = value & 0b11111111;
19
    return *this;
20
  }
21
  // Überladung des Type Casting Operators für uint16_t
22
  operator uint16_t() const {
23
    var5 = (rest4 << 8) | byte5;
24
  }
25
} Input4;
Könnte man einfach eine weitere Zeile einfügen `var7 = (rest6 << 8) | 
byte7;` und was nehme ich dann als Return-Wert?

P.S. und bevor die Frage kommt, das brauche ich dann zum Schluss auch 
noch (oder ein union)

Wilhelm M. schrieb:
> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
> union.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Nun könnte ich ja aber mehrere uint16_t in dem Struct haben, und ChatGPT
> konnte mir nicht die Frage beantworten wie ich die zweite Überladung
> spezifisch für var5 definieren kann.

Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw. 
welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein 
Muster erkennen ...).

Jedoch: überladen kann man nur, wenn die Signatur der Funktion (Name und 
Parameterliste) sich unterscheidet, Rückgabetyp der Funktion gehört (bei 
C++) nicht zur Signatur. Warum Du allerdings den 
Kopierzuweisungsoperator in der Art überladen willst, ist mir 
schleierhaft.

Die Bytes werden ja eine Bedeutung haben, daher wären geeignet benannte 
non-const Funktionen (aka: Mutatoren) besser:
1
struct Input {
2
    auto& foo(const uint16_t v) {...}
3
    auto& bar(const uint16_t v) {...}
4
};
5
...
6
Input i;
7
i.foo(42).bar(13); // <1>

statt
1
i = 42; // <2>

Denn bei <1> erkennt man sofort, was los ist, während man bei <2> 
rätselt oder eben das falsche vermutet.

Alexander schrieb:
> P.S. und bevor die Frage kommt, das brauche ich dann zum Schluss auch
> noch (oder ein union)
>
> Wilhelm M. schrieb:
>> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
>> union.

Wie schon gesagt: Du möchtest über das eine Element der union einen Wert 
zuweisen. Dies ist dann das sog. "active-member". Dann möchtest Du von 
einem anderen Element lesen, was nicht das "active-member" ist: das ist 
in i.a. C++ UB (in C hingegen explizit erlaubt: type-punning).

Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Eine Überladung für das Schreiben, und eine für das Lesen.

Das eine ist ein Kopierzuweisungsoperator, das andere ein 
Typumwandlungsoperator: nix Überladung.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Marco H. schrieb:
> Ganz einfach, dann sind Bitfelder ungeeignet. Dann verwendet man Masken
> und dann gibt es auch keine Überraschungen.

Anbei mal eine Lib für Bitpointer mit Masken aus meinen Anfängen (1995) 
mit dem Keil C51.

von Harald K. (kirnbichler)


Lesenswert?

Welches Problem soll hier eigentlich gelöst werden? Woher kommen die 
Daten, was soll mit ihnen geschehen?

von Wilhelm M. (wimalopaan)


Lesenswert?

Harald K. schrieb:
> Welches Problem soll hier eigentlich gelöst werden? Woher kommen die
> Daten, was soll mit ihnen geschehen?

Das weiß der TO ja auch nicht so genau.
Jedenfalls konnte er es auf Nachfrage nicht erläutern, und hofft nun, 
dass wir dieses Rätsel für ihn lösen ;-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Interessant, was hier für ein Aufwand getrieben wird, um Bitfelder zu 
retten.

@TO:

Schmink Dir die Bitfelder einfach ab. Das Zeugs ist höchst unportabel. 
Es gab vielleicht in den 70er Jahren mal einen Grund dafür, nämlich für 
schlecht optimierende C-Compiler höchst effizienten Code zu schreiben. 
Diese Zeiten sind aber längst vorbei.

Verwende von vornherein ein Byte und maskiere die gewünschten Bits, die 
Du brauchst. Das ist trivial und funktioniert auf jeder Plattform 
gleich. Ein C-Compiler wird Deine Masken-Operationen analog zu 
Bitfeldern umsetzen. Dabei entsteht im Vergleich zu Bitfeldern keine 
Performance-Einbuße. Gleichzeitig hast Du aber die Garantie der 
Portabilität.

Beispiel:
1
  uint8_t value = 0;
2
3
  value |= (1<<3);     // Bit 3 setzen
4
  value &= ~(1<<3);    // Bit 3 löschen
5
  if (value & (1<<3))  // Auf Bit 3 testen
Wenn Du den Bits auch noch Namen gibst, wird es noch klarer. Hier ein 
Beispiel:
1
#define LED_MASK_RED   (1<<2) // Maske fuer rote LED, an Pin#2
2
#define LED_MASK_GREEN (1<<3) // Maske fuer gruene LED, an Pin#3
3
...
4
  uint8_t led = 0;
5
6
  led |= LED_MASK_RED;       // rote LED setzen
7
  led &= ~LED_MASK_GREEN;    // grüne LED löschen
8
  outport (led);             // Das Byte an Port ausgeben

: Bearbeitet durch Moderator
von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw.
> welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein
> Muster erkennen ...).

Im Gegensatz zum TE der nur lesen möchte, möchte ich Data in ein Struct 
schreiben. Aktuell klappt das ganz gut mit memcpy() aber da kannte ich 
Union noch nicht.

Das Muster hast Du ja schon genannt, nur dass es eben nicht immer ganze 
Bytes sind.

Wilhelm M. schrieb:
> var0 = Byte0
> var1 = [high = Byte2, low = Byte1];
> var3 = [high = Byte4, low = Byte3];
> var5 = Byte5

Manchmal hat eine Variable eine krumme Anzahl Bits. Das habe ich nun so 
gelöst, indem ich die Variable in zwei 8 Bit Variablen gesplittet habe. 
Am Ende des Structs habe ich nun eine zusätzliche Variable eingefügt.

Frank M. schrieb:
> Wenn Du den Bits auch noch Namen gibst, wird es noch klarer. Hier ein
> Beispiel:
>
1
> #define LED_MASK_RED   (1<<2) // Maske fuer rote LED, an Pin#2
2
> #define LED_MASK_GREEN (1<<3) // Maske fuer gruene LED, an Pin#3
3
>

Mir ist noch nicht ganz klar wie ich das in die Structs reinkriege. Ich 
brauche ja eine Gruppierung.

Wilhelm M. schrieb:
> Warum Du allerdings den Kopierzuweisungsoperator in der Art überladen willst,
> ist mir schleierhaft.

Ich habe noch nie zuvor von einer Überladung gehört und musste ChatGPT 
auch erst mal befragen. Die zweite Überladung würde angeblich jedesmal 
ausgeführt wenn ich value = Input4.var5; irgendwo auslese, da beim Lesen 
angeblich intern immer ein Typecast ausgeführt würde. Die erste 
Überladung würde bei jedem Schreiben ausgeführt, Input4.var5 = 
0b11111111111; so dass im Endeffekt immer mit Input4.rest4 = 0b111; 
Input4.byte5 = 0b11111111; in beide Richtungen synchronisiert würde.

Von außen sollte sich das ganze wie ein normales Struct verhalten, wobei 
mich außen immer nur Input4.var5 interessiert, und innen immer nur 
Input4.rest4 + Input4.byte5. Input4.var5 steht aber zusätzlich am Ende 
des Structs und ist kein Teil von Data. Getestet habe ich noch nicht.

Wilhelm M. schrieb:
> Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.

Ich werde nun wieder ChatGPT befragen da ich Deinen Vorschlag mit 
Mutatoren auch nicht verstehe (trotz Beispielcode) aber vielleicht ist 
das ja das entscheidende Keyword. ChatGPT wird ja immer gelobt es könne 
gut mit Programmiersprachen umgehen, ist eine Verzweiflungstat.

Harald K. schrieb:
> Welches Problem soll hier eigentlich gelöst werden? Woher kommen die
> Daten, was soll mit ihnen geschehen?

Die Daten kommen vom CAN-Bus eines Fahrzeuges. Data ist immer die CAN 
Message, jeweils mit unterschiedlichen Längen. Je nach CAN-ID soll Data 
in ein anderes Struct kopiert werden. Das ganze 
Arduino-Nutzer-freundlich, und bidirektional.

Hier sind vier Beispiele für vier verschiedene CAN Messages, mit 
unterschiedlichen Längen.
Beitrag "Re: Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)"

Der Clou soll sein, dass man sich überhaupt nicht um CAN Message und 
Aufbau kümmern muss, und auch nicht wie man ein Struct füllt. Es wird 
einfach die Header-Datei eingebunden und ein Struct mit memcpy() gefüllt 
(oder mit Zuweisung Input = Data; wenn das geht)

Evtl. könnte ich das auch noch vereinfachen, indem ich gleich die CAN 
Message komplett als erstes Member in das Struct mit aufnehme. Und die 
Verteilung der Bitfelder erfolgt dann komplett mit Shifts und Maskierung 
innerhalb des Structs, da könnte ich auf memcpy() verzichten. Wichtig 
bleibt am Ende der Zugriff über die Klarnamen der Bits Input.LED_GREEN.

Das ganze ist aber nur ein Teilproblem, da die Structs selbst auch nur 
generiert sind. Ich arbeite an einem Script dass die Stucts erstellt. 
Ich bin mir nicht sicher ob es zielführend wäre hier so tief in das 
Problem einzusteigen.

Bisher hat es so gut funktioniert, nun brauche ich aber das erste mal 
einen dieser krummen Datentypen (ungerade Bitzahl) da ist mir der Fehler 
erst aufgefallen.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Wilhelm M. schrieb:
>> Zwar weiß ich immer noch überhaupt nicht was Du erreichen willst bzw.
>> welche Bytes wohin sollen (s.o. meine Frage: Du meintest, man müsste ein
>> Muster erkennen ...).

> Das Muster hast Du ja schon genannt, nur dass es eben nicht immer ganze
> Bytes sind.

Geht es um die Payload eines CAN-BUS Pakets? Dann beschreibe doch mal 
den Aufbau.

> Wilhelm M. schrieb:
>> var0 = Byte0
>> var1 = [high = Byte2, low = Byte1];
>> var3 = [high = Byte4, low = Byte3];
>> var5 = Byte5
>
> Manchmal hat eine Variable eine krumme Anzahl Bits. Das habe ich nun so
> gelöst, indem ich die Variable in zwei 8 Bit Variablen gesplittet habe.
> Am Ende des Structs habe ich nun eine zusätzliche Variable eingefügt.

Wie Du erkannt hast, ist das Hantieren mit einer "krummen" Anzahl von 
Bits unpraktisch. Also schreibt man sich ein (oder mehrere Klassen), an 
die man die jeweils relevanten Daten zum "Auseinandernehmen" mit 
Bit-Shift oder einfachem Maskieren übergibt.

> Wilhelm M. schrieb:
>> Warum Du allerdings den Kopierzuweisungsoperator in der Art überladen willst,
>> ist mir schleierhaft.
> Ich habe noch nie zuvor von einer Überladung gehört und musste ChatGPT
> auch erst mal befragen.

Dann hast Du noch nie in C++ programmiert.
Dann ist aber dieses Projekt kein guter Startpunkt, damit anzufangen.

Bleibe bei reinem C (egal ob als C oder C++-Code).

Das spielt aber auch gar keine Rolle: statt Elementfunktionen 
aufzurufen, schreibst Du einfach weiterhin (freie) Funktionen und 
übergibst die Objekte (ggf. dann als Output-Parameter).

> Wilhelm M. schrieb:
>> Den Rest Deines Beitrages verstehe ich leider wieder gar nicht.
>
> Ich werde nun wieder ChatGPT befragen da ich Deinen Vorschlag mit
> Mutatoren auch nicht verstehe (trotz Beispielcode) aber vielleicht ist
> das ja das entscheidende Keyword.

Non-const Elementfunktionen (aka Mutatoren) sind im Gegensatz zu 
const-Elementfunktionen welche, die den Objektzustand ändern können.

Aber wie gesagt: die Frage zeigt auch wieder, dass Du keine Ahnung von 
C++ hast. Deswegen bleibe bei C (s.o.).


>ChatGPT wird ja immer gelobt es könne
> gut mit Programmiersprachen umgehen, ist eine Verzweiflungstat.

Ja, machst es nicht besser.

> Harald K. schrieb:
>> Welches Problem soll hier eigentlich gelöst werden? Woher kommen die
>> Daten, was soll mit ihnen geschehen?
>
> Die Daten kommen vom CAN-Bus eines Fahrzeuges. Data ist immer die CAN
> Message, jeweils mit unterschiedlichen Längen. Je nach CAN-ID soll Data
> in ein anderes Struct kopiert werden. Das ganze
> Arduino-Nutzer-freundlich, und bidirektional.

Nochmal: geht es um die Payload oder um das gesamte Paket? Hört sich so 
an, als hättest Du keine CAN-Bus-Controller.

> Der Clou soll sein, dass man sich überhaupt nicht um CAN Message und
> Aufbau kümmern muss, und auch nicht wie man ein Struct füllt.

Ja, je nach CAN-ID und unterschiedlciher PayLoad nimmst Du eine andere 
Klasse.

> Das ganze ist aber nur ein Teilproblem, da die Structs selbst auch nur
> generiert sind. Ich arbeite an einem Script dass die Stucts erstellt.
> Ich bin mir nicht sicher ob es zielführend wäre hier so tief in das
> Problem einzusteigen.

Welche Art von PDL ist das denn? Oder machst Du das zu Fuß?

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Im Gegensatz zum TE der nur lesen möchte, möchte ich Data in ein Struct
> schreiben.

Ups, das Du nicht der TE bist, ist mir noch gar nicht aufgefallen ...

von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Geht es um die Payload eines CAN-BUS Pakets? Dann beschreibe doch mal
> den Aufbau.

Ja. Es gibt nicht "den" Aufbau. Vier Beispiele habe ich gezeigt. Hier 
geht es aber nur darum, auf alle Bits eines Bitfelds gleichzeitig 
zuzugreifen.

Wilhelm M. schrieb:
> Dann hast Du noch nie in C++ programmiert.

richtig. Ich nutze Arduino Libraries.

Wilhelm M. schrieb:
> Ja, je nach CAN-ID und unterschiedlicher PayLoad nimmst Du eine andere
> Klasse.

Könnte schwierig werden in reinem C. aktuell nutze ich Strukturen.

Wilhelm M. schrieb:
> Welche Art von PDL ist das denn?

keine.

Wilhelm M. schrieb:
> Oder machst Du das zu Fuß?

Ja. mit shell script. oder Python.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Ja. Es gibt nicht "den" Aufbau. Vier Beispiele habe ich gezeigt. Hier
> geht es aber nur darum, auf alle Bits eines Bitfelds gleichzeitig
> zuzugreifen.

Du hast die Payload als Array von Bytes (uint8_t) nehme ich an.

In C++ würde man nun das ganze Array einem Konstruktor einer Klasse 
übergeben, und der Konstruktor (oder eine andere Elementfunktion) popelt 
aus den vielen Bytes der Payload durch Shifts und Maskieren und ggf. 
wieder zu größeren Datentypen wie Zusammenfügen die einzelnen Elemente 
heraus.

Das kann man natürlich auch in C durch eine Initialisierungsfunktion 
machen:
1
typedef struct Data1 {
2
    uint16_t temperature;
3
    uint8_t sensor;
4
    ...
5
} data1_t;
6
typedef struct Data2 {
7
    ...
8
} data2_t;
9
10
void initData(data_t* const d, const uint8_t* const msg, const uint8_t length) {
11
    if (msg[0] == 0x01) {
12
        data1_t* const data = (data1_t*)d;
13
        data->temperature = ((uint16_t)msg[4] << 3) | ((msg[5] & 0xe0) >> 5);
14
        data->sensor = msg[6] & 0x0f;
15
    }
16
    else if (msg[0] == 0x04) {
17
        data2_t* const data = (data2_t*)d;
18
        ...
19
    }
20
    ...
21
}

Das obiger soll nur das Prinzip zeigen. Ob du das dispatch in der 
Funktion oder außerhalb machst, musst Du entscheiden, wo es sinnvoller 
ist.

Natürlich rate ich Dir, statt reines C ein rein-prozedurales C++ mit den 
C++-Goodies zu verwenden: also, statt rohes C-Arrays etwa std::array<>, 
...

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Eine Initialisierungsfunktion brauche ich schon noch, nur soll diese 
nicht viel machen. Das war ja der Sinn der Structs, die Arbeit 
abzunehmen. Da soll nur rein:
1
  switch(id) {
2
    case CANID:
3
//      memcpy(&Input, Data, len);
4
      Input = Data; // mit Union?
5
      break;
6
  }

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Eine Initialisierungsfunktion brauche ich schon noch, nur soll diese
> nicht viel machen. Das war ja der Sinn der Structs, die Arbeit
> abzunehmen. Da soll nur rein:
>
1
>   switch(id) {
2
>     case CANID:
3
> //      memcpy(&Input, Data, len);
4
>       Input = Data; // mit Union?
5
>       break;
6
>   }
7
>

Wir drehen uns im Kreis.
Ja sicher, das geht in C.
Es ist aber gehupft wie gesprungen - und es bleibt nicht portabel. 
Besser mit Shifts, ...

von Alexander (alecxs)


Lesenswert?

Der Kreis schließt sich hier. Weiß nur nicht wie man das in ein Struct 
packt. Dachte Dein "Funktionstemplate bit<>()" wäre die Lösung. höhere 
C++ Kunst und so..
Beitrag "Re: Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)"

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Der Kreis schließt sich hier. Weiß nur nicht wie man das in ein Struct
> packt. Dachte Dein "Funktionstemplate bit<>()" wäre die Lösung. höhere
> C++ Kunst und so..
> Beitrag "Re: Hilfe um 8 Bitfelder in uint8_t umzuwandeln (ESP32)"

Aber auch dafür musst Du wissen, wohin(!) jedes Bit abgebildet werden 
soll. Dieses Wissen scheinst Du aber nicht zu haben.

von Klaus H. (klummel69)


Lesenswert?

Wilhelm M. schrieb:
> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
> union.

Kurze Frage: Ich kenn nur std::bitset<N> aus dem <bitset> Header.
Dort gibt es IMHO aber kein bit<x>(v) sonder Zugriff Über v[x].
Welches Modul meinst Du?

von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Aber auch dafür musst Du wissen, wohin(!) jedes Bit abgebildet werden
> soll. Dieses Wissen scheinst Du aber nicht zu haben.

Doch, habe ich. Ich möchte nur wissen wie ich es umsetze. Die Überladung 
funktioniert nicht (wird nicht wie behauptet ausgeführt). Wenn ich es in 
die Initialisierungsfunktion packe funktioniert es. Ich will aber eine 
Funktion im Struct.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Die Überladung
> funktioniert nicht (wird nicht wie behauptet ausgeführt). Wenn ich es in
> die Initialisierungsfunktion packe funktioniert es.

Du meinst, einen Kontruktor? Oder eine freie Funktion? Beides geht mit 
Shifts. Wie schon mehrfach gesagt, ist das Verwenden von unions wie Du 
es willst in C++ UB.

> Ich will aber eine
> Funktion im Struct.

Du bist bemerkenswert beratungsresistent: wenn Du damit meinst, dass Du 
in C++ eine Elementfunktion benutzen möchtest zusammen mit Deinem union 
Ansatz: es ist UB!

Ich hatte Dir oben einen Ansatz mit Shift, Mask, Compose ausformuliert: 
benutze den.

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus H. schrieb:
> Wilhelm M. schrieb:
>> In dem Funktionstemplate bit<>() benutzt man natürlich Shifts statt eine
>> union.
>
> Kurze Frage: Ich kenn nur std::bitset<N> aus dem <bitset> Header.

Ist unbenutzbar für diesen Zweck, weil keine Garantien über das Layout 
möglich sind.

> Dort gibt es IMHO aber kein bit<x>(v) sonder Zugriff Über v[x].
> Welches Modul meinst Du?

Gar kein Modul aus der stdlibc++.

Ich hatte mir in etwa folgendes vorgestellt:
1
// -->: in eigene Library-Header
2
template<uint8_t N, typename T> requires (N < (sizeof(T) * 8)) auto bit(T& v);
3
4
namespace detail {
5
    template<uint8_t N, typename T> requires (N < (sizeof(T) * 8))
6
    struct Bit {
7
        friend auto bit<N, T>(T& v);
8
9
        template<typename B> requires (std::is_same_v<B, bool>)
10
        void operator=(const B b) {
11
            if (b) {
12
                d = d | std::remove_cvref_t<T>(1U << N);
13
            }
14
            else {
15
                d = d & std::remove_cvref_t<T>(~(1U << N));
16
            }
17
        }
18
    private:
19
        explicit Bit(T& v) : d{v}{}
20
        T& d;
21
    };
22
}
23
24
template<uint8_t N, typename T> requires (N < (sizeof(T) * 8))
25
auto bit(T& v) {
26
    return detail::Bit<N, T>{v};    
27
}
28
29
// <-- Ende Library-Header
30
31
// Beispiel zur Verwendung
32
std::byte v;
33
34
int main() {    
35
    bit<3>(v) = true;
36
    bit<7>(v) = false;
37
    
38
    // folgendes ist nicht möglich
39
    // bit<8>(v) = false; 
40
    // bit<0>(v) = 1;
41
}

von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Ich hatte Dir oben einen Ansatz mit Shift, Mask, Compose ausformuliert:
> benutze den.

Meinst du deine Funktion initData()? Davon rede ich doch. Wie? Die ist 
doch in main() manuell ausgeführt. Ich brauche es aber automatisch, und 
die Funktion soll in das Struct verschoben werden.

So funktioniert es derzeit. Logik gefunden (BIG ENDIAN)
1
  ...
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8
9
  uint8_t byte5 : 8; // Ende vom Struct
10
11
  // wird nicht von memcpy() gefüllt
12
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
13
} Input4;
14
15
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
16
  switch(id) {
17
    case CANID:
18
      memcpy(&Input4, Data, len);
19
      Input4.var5 = (Input4.rest4 << 8) | Input4.byte5; // <-- DAS
20
      break;
21
  }
22
}
23
void loop() {
24
  initData(msg.id, msg.buf, msg.len);
25
  if ( Input4.var5 == 2047 ) {
26
    ...
27
  }
28
  Input4.var5 = 1024;
29
  Input4.rest4 = (Input4.var5 >> 8) & 0b00000111; // <--
30
  Input4.byte5 = Input4.var5 & 0b11111111;
31
}

So soll es werden. Auf das Union kann ich auch verzichten, aber dann 
brauche ich memcpy() oder ich nehme Data komplett in jedes Struct mit 
auf. Ob es nun eine Klasse oder Struct wird, Elementfunktion, Kontruktor 
oder Mutator heißt ist mir egal, Hautpsache die Synchronisation wird 
automatisch ausgeführt. Und was UB heißt weiß ich nicht, das ist alles 
fachchinesisch. Es muss nicht portabel sein.
1
  ...
2
  uint8_t rest4 : 3;
3
  bool bit28    : 1; // OFFSET 28, LENGTH 1
4
  bool bit27    : 1; // OFFSET 27, LENGTH 1
5
  bool bit26    : 1; // OFFSET 26, LENGTH 1
6
  bool bit25    : 1; // OFFSET 25, LENGTH 1
7
  bool bit24    : 1; // OFFSET 24, LENGTH 1
8
9
  uint8_t byte5 : 8;
10
11
  uint16_t var5 :11; // OFFSET 29, LENGTH 11
12
13
  Input4& operator==(const uint16_t value) {
14
      var5 = (rest4 << 8) | byte5; // <-- SOLL HIER HIN
15
    return *this;
16
  }
17
  Input4& operator=(const uint16_t value) {
18
    var5 = value;
19
    rest4 = (value >> 8) & 0b00000111; // <--
20
    byte5 = value & 0b11111111;
21
    return *this;
22
  }
23
} Input4;
24
25
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
26
  switch(id) {
27
    case CANID:
28
      memcpy(&Input4, Data, len);
29
      // hier soll nichts weiter stehen
30
      break;
31
  }
32
}
33
void loop() {
34
  initData(msg.id, msg.buf, msg.len);
35
  if ( Input4.var5 == 2047 ) {
36
    ...
37
  }
38
  Input4.var5 = 1024;
39
  // hier auch nicht
40
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
>   Input4& operator==(const uint16_t value) {
>       var5 = (rest4 << 8) | byte5; // <-- SOLL HIER HIN
>     return *this;
>   }

Das ist kein Zuweisungsoperator, sondern ein Vergleichsoperator.

Mir scheint, Du versuchst mit try-n-error zum Ziel zu kommen, aber das 
wird Dir nicht gelingen.

Und nochmals: das Du ja C++ programmierst ("Arduino" ist C++), vergiss 
den Ansatz über die union, das ist UB (undefined behaviour).

Versuche:
1
struct Input {
2
    Input(const uint8_t id, const uint8_t* const msg, const uint8_t length) {
3
        // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte herausholen
4
    }
5
private:
6
    uint8_t var1;
7
    uint16_t var2;
8
};

von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Das ist kein Zuweisungsoperator, sondern ein Vergleichsoperator.

yep. der ist zum Lesen, nicht zum Schreiben. siehe hier.
1
if ( Input4.var5 == 2047 ) {

Wilhelm M. schrieb:
> Mir scheint, Du versuchst mit try-n-error zum Ziel zu kommen, aber das
> wird Dir nicht gelingen.

Was wäre die Alternative? Ein Buch lesen? Im Forum fragen?

von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Wilhelm M. schrieb:
>> Das ist kein Zuweisungsoperator, sondern ein Vergleichsoperator.
>
> yep. der ist zum Lesen, nicht zum Schreiben. siehe hier.
>
1
if ( Input4.var5 == 2047 ) {

Nö.

> Wilhelm M. schrieb:
>> Mir scheint, Du versuchst mit try-n-error zum Ziel zu kommen, aber das
>> wird Dir nicht gelingen.
>
> Was wäre die Alternative? Ein Buch lesen? Im Forum fragen?

Dir die Grundlagen von C++ aneignen. Buch macht kluch.

von Joe L. (joelisa)


Lesenswert?

Alexander schrieb:
> Und was UB heißt weiß ich nicht, ...

Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht 
wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun. 
Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

von Alexander (alecxs)


Lesenswert?

Wilhelm M. schrieb:
> Versuche:
>
1
struct Input {
2
    Input(const uint8_t id, const uint8_t* const msg, const uint8_t 
3
length) {
4
        // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte 
5
herausholen
6
    }
7
private:
8
    uint8_t var1;
9
    uint16_t var2;
10
};

Das sieht doch gut aus. Dann kann ich das memcpy ersetzen mit einer 
Zeile wo ich das aufrufe. Nur brauche ich es bidirektional. Aber so 
komme ich erstmal weiter, danke.

Joe L. schrieb:
> Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht
> wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun.
> Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

Und was heißt UB (undefined behaviour) nun? Dass es auf meinem Arduino 
Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht? Spielt 
wie gesagt keine Rolle, da es für Arduino ist. Ich schrieb, es muss 
nicht portabel sein.

Die Bitfelder hatte ich übrigens ursprünglich doppelt generieren lassen, 
habe ich dann aber wegoptimiert da nicht benötigt.
1
typedef struct Input_t {
2
  #if __BYTE_ORDER == __LITTLE_ENDIAN
3
    bool bit0 : 1;
4
    bool bit1 : 1;
5
    bool bit2 : 1;
6
    bool bit3 : 1;
7
  #elif __BYTE_ORDER == __BIG_ENDIAN
8
    bool bit3 : 1;
9
    bool bit2 : 1;
10
    bool bit1 : 1;
11
    bool bit0 : 1;
12
  #endif
13
} __attribute__((__packed__)) Input_t;

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander schrieb:
> Wilhelm M. schrieb:
>> Versuche:
>>
1
struct Input {
2
>     Input(const uint8_t id, const uint8_t* const msg, const uint8_t
3
> length) {
4
>         // ... hier mit Shift, Mask, ... aus den Bytes von msg die Werte
5
> herausholen
6
>     }
7
> private:
8
>     uint8_t var1;
9
>     uint16_t var2;
10
> };
>
> Das sieht doch gut aus. Dann kann ich das memcpy ersetzen mit einer
> Zeile wo ich das aufrufe.

Das nennt man dann Konstruktor ;-)

Etwas besser wäre dann noch:
1
struct Input {
2
    template<auto N>
3
    Input(const uint8_t id, const std::array<uint8_t, N>& msg) {}
4
};

Dann bist Du diese rohen C-Arrays los.

> Nur brauche ich es bidirektional. Aber so
> komme ich erstmal weiter, danke.

Dann ggf. so:
1
struct Input {
2
    template<auto N>
3
    Input(const uint8_t id, const std::array<uint8_t, N>& msg) {}
4
5
    auto toArray() const {
6
        std::array<uint8_t, 42> data;
7
        ...
8
        return data;
9
    }
10
};


> Joe L. schrieb:
>> Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht
>> wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun.
>> Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...
>
> Und was heißt UB (undefined behaviour) nun?

Ist Google mal wieder kaputt?

> Dass es auf meinem Arduino
> Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht? Spielt
> wie gesagt keine Rolle, da es für Arduino ist. Ich schrieb, es muss
> nicht portabel sein.

Das es nirgendwo eine Garantie gibt, dass es funktioniert.

> Die Bitfelder hatte ich übrigens ursprünglich doppelt generieren lassen,
> habe ich dann aber wegoptimiert da nicht benötigt.

Besser ist es: s.o. union und UB.

von Arm (arm_arm)


Lesenswert?

In C++20 ist std::bit_cast constexpr, damit kann man per static_assert 
prüfen, ob die bits in einem bitfield so angelegt sind, wie man es 
erwartet.

von Wilhelm M. (wimalopaan)


Lesenswert?

Arm schrieb:
> In C++20 ist std::bit_cast constexpr, damit kann man per static_assert
> prüfen, ob die bits in einem bitfield so angelegt sind, wie man es
> erwartet.

Das ist zwar richtig und sinnvoll, jedoch beweifel ich auch hier, dass 
der TO überhaupt etwas damit anfangen kann, oder weiß, was ein 
constexpr-Kontext ist. Und streng genommen müsste er sich die Arbeit 
dann zweimal machen ...

von Alexander (alecxs)


Lesenswert?

Ja das ging nicht, ich glaube Arduino 1.8.9 ist C++17 oder C++11. Ich 
hab das nun mal für das erste Beispiel so übernommen. Muss ich noch 
testen.
1
typedef struct message_t {
2
  uint32_t id = 0;
3
  uint8_t len = 8;
4
  uint8_t buf[8] = { 0 };
5
} message_t;
6
7
typedef struct Input_t {
8
  template<auto N>
9
  receive(const std::array<uint8_t, N>& msg) {
10
    bit0 = (msg[0] >> 0U) & 1U;
11
    bit1 = (msg[0] >> 1U) & 1U;
12
    bit2 = (msg[0] >> 2U) & 1U;
13
    bit3 = (msg[0] >> 3U) & 1U;
14
    bit4 = (msg[0] >> 4U) & 1U;
15
    bit5 = (msg[0] >> 5U) & 1U;
16
    bit6 = (msg[0] >> 6U) & 1U;
17
    bit7 = (msg[0] >> 7U) & 1U;
18
  }
19
  auto transmit() const {
20
    std::array<uint8_t, 8> data;
21
    data[0] = ((uint8_t)(bit0) << 0U) |
22
              ((uint8_t)(bit1) << 1U) |
23
              ((uint8_t)(bit2) << 2U) |
24
              ((uint8_t)(bit3) << 3U) |
25
              ((uint8_t)(bit4) << 4U) |
26
              ((uint8_t)(bit5) << 5U) |
27
              ((uint8_t)(bit6) << 6U) |
28
              ((uint8_t)(bit7) << 7U);
29
    return data;
30
  }
31
  bool bit0     : 1;  // OFFSET 0, LENGTH 1
32
  bool bit1     : 1;  // OFFSET 1, LENGTH 1
33
  bool bit2     : 1;  // OFFSET 2, LENGTH 1
34
  bool bit3     : 1;  // OFFSET 3, LENGTH 1
35
  bool bit4     : 1;  // OFFSET 4, LENGTH 1
36
  bool bit5     : 1;  // OFFSET 5, LENGTH 1
37
  bool bit6     : 1;  // OFFSET 6, LENGTH 1
38
  bool bit7     : 1;  // OFFSET 7, LENGTH 1
39
} Input_t;
40
41
#define CANID 0x0003
42
struct Input_t Input;
43
struct Input_t Output;
44
struct message_t message;
45
std::array<uint8_t, 8> buf;
46
47
void initData(unsigned int id, const uint8_t *Data, uint8_t len) {
48
  switch(id) {
49
    case CANID:
50
      Input.receive(Data);
51
      break;
52
  }
53
}
54
void readMsg(message_t &msg) {
55
  // read Data
56
  ...
57
  initData(reinterpret_cast <uint32_t> (msg.id), msg.buf, msg.len);
58
}
59
void sendMsg(const message_t &msg) {
60
  // send Data
61
  ...
62
}
63
void loop() {
64
  readMsg(message);
65
  Serial.println("empfange Input.bit0 = ", Input.bit0);
66
67
  Output.receive(Input.transmit());
68
  Output.bit0 = 1;
69
  Serial.println("sende Output.bit0 = ", Output.bit0);
70
  
71
  buf = Output.transmit();
72
  for (int i = 0; i < message.len; i++) {
73
    message.buf[i] = buf[i];
74
  }
75
  sendMsg(message);
76
  delay(200);
77
}
Das ist nur PseudoCode- aber wenn man es noch vereinfachen kann dann 
bitte her damit. message_t readMsg() sendMsg() sind Vorgabe aus einer 
Library, da kann ich nichts ändern.

: Bearbeitet durch User
von Joe L. (joelisa)


Lesenswert?

Alexander schrieb:
> Joe L. schrieb:
>> Was die Abbreviatur "UB" genau heißt -- das musst Du unstreitig nicht
>> wissen. Vereinfacht gesagt hat das was mit 'Lernresistenz' zu tun.
>> Näheres hierzu siehe https://www.stupidedia.org/stupi/Lernresistenz ...

> Und was heißt UB (undefined behaviour) nun? Dass es auf meinem Arduino
> Teensy oder ESP32 funktioniert, aber auf deinem Compiler nicht?

Das heißt, dass Du dich aufführst wie ein Vollpfosten!

Lies selber (https://64.github.io/cpp-faq/undefined-behaviour/):
"Undefined Behaviour (also known as UB) occurs when you violate certain 
language rules. [...] When undefined behaviour occurs, the C and C++ 
standards do not place any restrictions on what your program might do. 
In other words, your program may crash, or continue execution, or call 
some seemingly unrelated piece of code, or print 42 and open xkcd in 
your web browser [...]"

von Alexander (alecxs)


Lesenswert?

Joe L. schrieb:
> Lies selber

Danke für's googlen. Mit 42 kann ich leben.

In practice, you may be able to reason about how your compiler will 
respond to UB, and in some cases compilers will guarantee that certain 
operations are well-defined, but for maximum portability you should aim 
to keep your programs UB-free.

von Harald K. (kirnbichler)


Lesenswert?

Alexander schrieb:
> but for maximum portability you should aim
> to keep your programs UB-free.

Wenn maximale Portabilität nicht das primäre Ziel ist ... kann sich die 
Perspektive ändern.

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.