Forum: Mikrocontroller und Digitale Elektronik 16 bit struct in 2 unsigned char umwandeln


von Andreas G. (andy1024)


Lesenswert?

Hallo Ihr,

ich hab da mal wider ein kleines Problem. Ich hab ein 16 bit großes 
struct:

struct {
     unsigned char AB      : 1 ;   // Kanal selektion
     unsigned char DC      : 1 ;   // don't care
     unsigned char GA      : 1 ;   // Gain
     unsigned char SHDN    : 1 ;   // shutdown
     unsigned int  dac_val : 10 ;  // dac value
     unsigned char empty    : 2 ;   // don't care
       } mcp4812;

Die ersten 4 bit sind fest:
        mcp4812.AB = 0;
  mcp4812.DC = 0;
  mcp4812.GA = 1;
  mcp4812.SHDN = 1;

"dac_val" steht der 10 bit Wert den der ADC ausgeben soll.
Diese 16 bit möchte ich so zerlegen das ich sie über die SPI 
Schnittstelle
ausgeben kann. Beim MEGA 328 sind die Register bekanntlich 8 bit breit. 
Von daher meine Frage wie zerlege ich den 16 bit "struct" in 2 8 bit 
Häpchen die ich augeben kann.

Gruß
Andy

von Sina A. (sinapse)


Lesenswert?

1
struct {
2
     unsigned char AB      : 1 ;   // Kanal selektion
3
     unsigned char DC      : 1 ;   // don't care
4
     unsigned char GA      : 1 ;   // Gain
5
     unsigned char SHDN    : 1 ;   // shutdown
6
     union{
7
       unsigned char dac_bytes[2];  // dac value bytes
8
       unsigned int  dac_val : 10;  // dac value
9
     }
10
     unsigned char empty    : 2 ;   // don't care
11
} mcp4812;

je nach dem welchen C dialekt du verwendest musst du dem union ggf. nen 
namen verpassen.

lg

von Karl H. (kbuchegg)


Lesenswert?

Andreas Geissler schrieb:

> Von daher meine Frage wie zerlege ich den 16 bit "struct" in 2 8 bit
> Häpchen die ich augeben kann.

Die Frage taucht alle 2 Wochen erneut im Forum auf.

Kleines Vorgepänkel. So etwas:
1
struct {
2
unsigned char AB : 1 ; // Kanal selektion
3
unsigned char DC : 1 ; // don't care
4
unsigned char GA : 1 ; // Gain
5
unsigned char SHDN : 1 ; // shutdown
6
unsigned int dac_val : 10 ; // dac value
7
unsigned char empty : 2 ; // don't care
8
} mcp4812;

ist meistens EXTREM unklug. Man macht keine anonyme struct! Denn dann 
kannst du diesen Datentyp zu nichts anderem mehr verwenden und sich im 
restlichen Code nicht mehr darauf beziehen. Es gibt zwar Ausnahmen, aber 
die sind rar. Also gib deiner struct einen Namen, und alles ist gut.
1
struct myChanelData {
2
  unsigned char AB : 1 ; // Kanal selektion
3
  unsigned char DC : 1 ; // don't care
4
  unsigned char GA : 1 ; // Gain
5
  unsigned char SHDN : 1 ; // shutdown
6
  unsigned int dac_val : 10 ; // dac value
7
  unsigned char empty : 2 ; // don't care
8
} mcp4812;

Zb kannst du mit einer union das Gewünschte erreichen
1
union convert
2
{
3
  struct myChanelData Data;
4
  unsigned char Bytes[2];
5
};

In einer union liegen die Member quasi üereinander, belegen also 
denselben Speicherbereich. D.h. du kannst über Data den Speicher 
beschreiben und dir über Bytes denselben Speicher in Byte-Form auslesen.
1
union convert conv;
2
3
   conv.Data = mcp4812;
4
   spi_out( conv.Bytes[0] );
5
   spi_out( conv.Bytes[1] );

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Eine andere Möglichkeit wäre es, einen entsprechenden Pointer zurecht zu 
casten.
1
struct myChanelData {
2
  ...
3
} mcp4812;
4
5
6
unsigned char* pData = (unsigned char*)&mcp4812;
7
8
  spi_out( pData[0] );
9
  spi_out( pData[1] );
10
...

von Sina A. (sinapse)


Lesenswert?

kleine korrektur zu meinem posting oben... dort musst du noch die empty 
bits aus dem struct streichen, sonst ist der struct 18bit gross

lg

von Andreas G. (andy1024)


Lesenswert?

Hallo Ihr,


danke für die schnelle Antwort.

Gruß
Andy

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> Eine andere Möglichkeit wäre es, einen entsprechenden Pointer zurecht zu
> casten.

Ich würde genau diese Pointer-Cast-Variante immer dem union-Trick 
vorziehen. Das mit der union ist ausdrücklich lt. C-Standard nicht 
garantiert.

Mein Rat: Den Tipp mit der union hier im Forum nicht mehr zu geben, 
sondern stattdessen ausschließlich die Pointer-Variante zu propagieren. 
Da kommt der Compiler nicht dran vorbei ;-)

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:
> Karl Heinz schrieb:
>> Eine andere Möglichkeit wäre es, einen entsprechenden Pointer zurecht zu
>> casten.
>
> Ich würde genau diese Pointer-Cast-Variante immer dem union-Trick
> vorziehen. Das mit der union ist ausdrücklich lt. C-Standard nicht
> garantiert.

Ich denke es war der C99 Standard (weiss nicht mehr genau), welcher 
diesen union Trick für chars (und damit auch unsigned chars) legitimiert 
hat.

Zuvor war die Situation so, dass es keinen gerantierten Weg gab, weder 
durch union, noch durch umcasten von Zeiger-Typen.
Denn auch in dem Moment, in dem du einen Pointer Typ umcastest, landest 
du automatisch in 'undefined behaviour' land. Und wieder 
(konsequenterweise): einzige Ausnahme: umcasten zu einem char-Pointer.

> Mein Rat: Den Tipp mit der union hier im Forum nicht mehr zu geben,

Mein Tipp: die neueren C-Standards beherzigen. Musste ich auch.

: Bearbeitet durch User
von ghl (Gast)


Lesenswert?

Karl Heinz schrieb:
> Frank M. schrieb:
>> Karl Heinz schrieb:
>>> Eine andere Möglichkeit wäre es, einen entsprechenden Pointer zurecht zu
>>> casten.
>>
>> Ich würde genau diese Pointer-Cast-Variante immer dem union-Trick
>> vorziehen. Das mit der union ist ausdrücklich lt. C-Standard nicht
>> garantiert.
>
> Ich denke es war der C99 Standard (weiss nicht mehr genau), welcher
> diesen union Trick für chars (und damit auch unsigned chars) legitimiert
> hat.
Meinst du N1256 (C99) 6.2.6.1(5)?
1
Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined. [...]

Den Abschnitt gibt es auch in N1570 (C11).

> Zuvor war die Situation so, dass es keinen gerantierten Weg gab, weder
> durch union, noch durch umcasten von Zeiger-Typen.
Doch. 6.2.6.1(4):
1
Values stored in non-bit-field objects of any other object type consist of n × CHAR_BIT bits, where n is the size of an object of that type, in bytes. The value may be copied into an object of type unsigned char [n] (e.g., by memcpy);

Also:
1
struct foo f;
2
char tmp[sizeof(struct foo)];
3
memcpy(&tmp, &f, sizeof(f));

Und ja, das ist effizient. Sowohl Clang und gcc machen (zumindest auf 
x86_64) daraus entsprechende mov-Instruktionen da memcpy ein Builtin 
ist.
avr-gcc, msp430-gcc & Co sollten das auch können, hängt ja der gleiche 
Optimizer dahinter.

> Denn auch in dem Moment, in dem du einen Pointer Typ umcastest, landest
> du automatisch in 'undefined behaviour' land. Und wieder
> (konsequenterweise): einzige Ausnahme: umcasten zu einem char-Pointer.
Fast richtig. char und void + cv-qualifiers.
(memcpy nimmt void)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Danke für das Raussuchen, ghl. So hatte ich das auch in Erinnerung. 
Johann hatte das bzgl. union schon mehrfach hier im Forum angemerkt.

Also halten wir fest: union ist bäh, Pointer ist gut. :-)

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:
> Danke für das Raussuchen, ghl. So hatte ich das auch in Erinnerung.
> Johann hatte das bzgl. union schon mehrfach hier im Forum angemerkt.

Ist aber trotzdem Quatsch.
Ich schätze Johann sehr. Aber in Sachen Standard Auslegung ist er für 
mich keine Autorität.

Welcher Teil der ständig vprkommenden Phrase "except character type" ist 
unklar?

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> Welcher Teil der ständig vprkommenden Phrase "except character type" ist
> unklar?

Du meinst offenbar dies in der Formulierung:

"... is read by an lvalue expression that does
not have character type, the behavior is undefined."

Okay, das habe ich bisher auch überlesen. Gebe ich zu. Ich kann also 
offenbar in unions immer character-weise rumstochern. Dann hast Du 
recht: Ob ich char-Pointer oder (unsigned) char union member nehme, ist 
schnurz.

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> offenbar in unions immer character-weise rumstochern.

In alle Kürze:
Worum gehts denn.
es geht um die strict-Aliasing Sache.

Der Compiler darf immer (mit Ausnahmen) davon ausgehen, dass es keine 2 
Wege zum selben Objekt gibt, über die das Objekt modifiziert wird. Eine 
dieser Ausnahmen ist ein char-Typ. Sobald ein char-Typ im Spiel ist, ist 
die strict-Aliasing Regelung mehr oder weniger abgeschaltet und der 
Compiler muss von Aliasing ausgehen.

Es gibt gute Artikel zu dem Thema im Web.
Ein guter Anlaufpunkt sind fast immer die Artikel, die auf 
http://stackoverflow.com/
zu finden sind. Vor allen Dingen, seit die klassischen Newsgroups massiv 
an Bedeutung verloren haben - leider. Denn in den Newsgroups war es 
nicht ungewöhnlich, dass man dort auch auf die Leute traf, die am 
jeweiligen Standard mitgearbeitet haben und die Standard-Schreiber 
selbst um eine Klarstellung bitten konnte.

http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule
http://dbp-consulting.com/tutorials/StrictAliasing.html

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Danke.

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.