Forum: Mikrocontroller und Digitale Elektronik Aus 2x8Bit mach 1x16Bit


von Ingo (Gast)


Lesenswert?

Hallo Leute,

ich bin scheinbar zu blöde auf einem STM32F415 aus zwei 8Bit Variablen, 
die einen Signed Wert für einen externen ADC (MCP3901) darstellen in 
eine 32Bit Signed Variable zu packen. Auf einem AVR speichere ich das 
Ergebnis in ein signed 16Bit => Klappt. Auf einem STM32 macht das wenig 
Sinn, somit sind dort alle meine Variablen int (= int32_t).
So sieht die Sache aus:
1
int Wert;
2
signed char Buffer[2];      // Hier wird das ADC Ergebnis gespeichert
3
...
4
// Dann wird ausgelsen, soweit alles gut
5
...
6
//           HighByte                         LowByte
7
Wert = ((int)Buffer[0] & 0x00ff) << 8 | ((int)Buffer[1] & 0x00ff);

Wenn ich mir den Wert ausgebe, scheint das Vorzeichen verloren zu gehen, 
denn anstatt -xyz (bei negativen Werten, positiv klappt) erhalte ich 
Werte um die 100000, also alles andere als negativ.
Die Ausgabe erfolgt über:
printf("\nData:\t%d", Wert;)

Code kann ich leider nicht posten, geht nur um die Umwandlung, wie 
gesagt auf einem AVR klappt das (Selbes IC).



Ingo

von holger (Gast)


Lesenswert?

>Wenn ich mir den Wert ausgebe, scheint das Vorzeichen verloren zu gehen,

Lass das schieben sein:

Wert = (int)Buffer[0] * 256 + Buffer[1];

von Karl H. (kbuchegg)


Lesenswert?

Ingo schrieb:

>
> Wenn ich mir den Wert ausgebe, scheint das Vorzeichen verloren zu gehen,

Yep.

> //           HighByte                         LowByte
> Wert = ((int)Buffer[0] & 0x00ff) << 8 | ((int)Buffer[1] & 0x00ff);


(int)Buffer[0]

du lässt dir zunächst den signed int auf einen int hochcasten.
soweit so gut. Da wird eine sign Extension gemacht und der int hat noch 
das Vorzeichen

  & 0x00ff

und dann schneidest du die oberen Bits (und damit auch das Vorzeichenbit 
weg.

> Code kann ich leider nicht posten, geht nur um die Umwandlung, wie
> gesagt auf einem AVR klappt das (Selbes IC).

logisch
weil dort die Zusammensetzung von 2 8 Bit WErten bereits einen 
vollständigen int (16 Bit) ergibt.
Aber hier hast du nicht diesen Fall. Du setzt zwar die 16 Bit korrekt 
zusammen, aber niemand kümmert sich um die höherwertigen 16 Bit, die 
nach fehlen um auf 32 Bit zu kommen.

Du könntest die ganze OPeration vom Compiler erledigen lassen, indem du 
zunächst wieder die 16 Bit zusammensetzt, aber diesmal auch wirklich 16 
Bit Operationen bzw. Variablen benutzt.
Und erst ganz zum Schluss, wenn alles in den 16 Bit fertig ist, dann 
castest du hoch auf einen 32 Bit int. Dann macht dir der Compiler die 
Sign extension.

Du kannst es allerdings auch händisch machen. Denn die oberen 16 Bit 
können nur 0x0000 (positive Zahl) bzw. 0xFFFF (negative Zahl) sein.

von Karl H. (kbuchegg)


Lesenswert?

probier mal

Wert = ((int16_t)Buffer[0] & 0x00ff) << 8 | ((int16_t)Buffer[1] & 
0x00ff);


das sollte das Problem eigentlich lösen, weil dadurch der Übergang von 
16 Bit auf 32 Bit erst erfolgt, wenn die 16 Bit komplett zusammen 
gesetzt worden sind und damit der Compiler ganz zum Schluss die Sign 
Extension einfügen muss.

von Ingo (Gast)


Lesenswert?

holger schrieb:
> Wert = (int)Buffer[0] * 256 + Buffer[1];
Da hast du Recht!

@ karl-heinz:
Vielen Dank für die ausführliche Erklärung. Ich hab den Fehler erst zum 
Feierabend gemerkt, sodass ich nicht mehr viel Zeit hatte ihn zu 
beheben.
Ich werde beides morgen ausprobieren, wiedereinmal danke!

Merke: 32 Bit hat auch seine Fallen ;)

Ingo

von Walter S. (avatar)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wert = ((int16_t)Buffer[0] & 0x00ff) << 8 | ((int16_t)Buffer[1] &
> 0x00ff);

geht nicht auch das:
Wert = ((int)Buffer[0] << 8 ) | ((int)Buffer[1] & 0xff);

von holger (Gast)


Lesenswert?

>Wert = ((int16_t)Buffer[0] & 0x00ff) << 8 | ((int16_t)Buffer[1] &
>0x00ff);
>
>
>das sollte das Problem eigentlich lösen,

Ich glaube nicht das das klappt. Mein Vorschlag
möglicherweise aber auch nicht. Das Problem ist das signed char.
Beide Bytes können dann ein Vorzeichen habe. Ich würde die
unsigned zusammenfügen, auf int16_t casten und zum Schluss
ein cast auf int.

von Kein Name (Gast)


Lesenswert?

>> Wert = (int)Buffer[0] * 256 + Buffer[1];
>Da hast du Recht!

Wirklich?

Bei zB.
Buffer[0] = 0xFF;
Buffer[1] = 0xFF;
sollte doch 0xFFFFFFFF rauskommen?

Tippe eher auf 0xFFFFFF00 + 0xFFFFFFFF = 0xFFFFFeFF

von holger (Gast)


Lesenswert?

>>> Wert = (int)Buffer[0] * 256 + Buffer[1];
>>Da hast du Recht!
>
>Wirklich?
>
>Bei zB.
>Buffer[0] = 0xFF;
>Buffer[1] = 0xFF;
>sollte doch 0xFFFFFFFF rauskommen?

Siehe meinen letzten Post. Das geht so nicht.

Eher so:
Wert = (int16_t)((uint16_t)Buffer[0] << 8 + (uint8_t)Buffer[1]);

von holger (Gast)


Lesenswert?

>Eher so:
>Wert = (int16_t)((uint16_t)Buffer[0] << 8 + (uint8_t)Buffer[1]);

Vieleicht noch ne Klammer zur Sicherheit;)

Wert = (int16_t)(((uint16_t)Buffer[0] << 8) + (uint8_t)Buffer[1]);

von Fabian O. (xfr)


Lesenswert?

Etwas kürzer:
1
int Wert;
2
unsigned char Buffer[2];
3
4
Wert = (int16_t) ((Buffer[0] << 8) | Buffer[1]);

von holger (Gast)


Lesenswert?

>signed char Buffer[2];      // Hier wird das ADC Ergebnis gespeichert

Allerdings frage ich mich gerade wie man auf die Idee kommt
einen 16Bit Wert in zwei signed chars zu verpacken;)
Das macht nur Ärger.

von holger (Gast)


Lesenswert?

@Fabian

>unsigned char Buffer[2];

Zwei doofe, ein Gedanke;)

von Werner (Gast)


Lesenswert?

Na, dann werfe ich noch mal das Schlüsselwort "union" in den Raum.
http://de.wikibooks.org/wiki/C-Programmierung:_Komplexe_Datentypen#Unions

von holger (Gast)


Lesenswert?

>Na, dann werfe ich noch mal das Schlüsselwort "union" in den Raum.

Geht auch, aber kann Ärger mit Bigendian/Littleendian machen.

von Walter S. (avatar)


Lesenswert?

Fabian O. schrieb:
> Etwas kürzer:int Wert;
> unsigned char Buffer[2];
>
> Wert = (int16_t) ((Buffer[0] << 8) | Buffer[1]);

das haut mit dem Vorzeichen nicht hin, ich bin immer noch guter Hoffnung 
dass mein Vorschlag funktioniert:

Walter S. schrieb:
> geht nicht auch das:
> Wert = ((int)Buffer[0] << 8 ) | ((int)Buffer[1] & 0xff);

positiv ist sowieso kein Problem, negativ mit Beispiel -1:
0xffff ergibt die chars 0xff 0xff

wert = (0xffffffff << 8) | (0xffffffff&0xff)
     = 0xffffffff = -1

von holger (Gast)


Lesenswert?

>> unsigned char Buffer[2];
>>
>> Wert = (int16_t) ((Buffer[0] << 8) | Buffer[1]);
>
>das haut mit dem Vorzeichen nicht hin, ich bin immer noch guter Hoffnung
>dass mein Vorschlag funktioniert:

Doch das haut hin weil Buffer jetzt unsigned ist.

von Ingo (Gast)


Lesenswert?

Aber der wenn ich ohne cast Buffer[0] << 8 mache, wirkt dann der cast 
vor der Klammer oder shifte ich ins Nirvana?

von Walter S. (avatar)


Lesenswert?

Laut K&R A.6.2 ist das implementierungsabhängig da 0xffff in int16_t 
nicht dargestellt werden kann:

 (int16_t) ( (uint16_t)0xffff ) ist nicht definiert

von Fabian O. (xfr)


Lesenswert?

In C werden Berechnungen immer mit mindestens int-Breite durchgeführt. 
Das nennt man Integer-Promotion. In dem Fall wird aus dem unsigned char 
implizit ein unsigned int. Insgesamt passiert also folgendes:
1
Wert = (int) ((int16_t) ( (((unsigned int) Buffer[0]) << 8) | ((unsigned int) Buffer[1]) ));

von Fabian O. (xfr)


Lesenswert?

Walter S. schrieb:
> Laut K&R A.6.2 ist das implementierungsabhängig da 0xffff in int16_t
> nicht dargestellt werden kann:
>
>  (int16_t) ( (uint16_t)0xffff ) ist nicht definiert

Das "Problem" hast Du aber immer, wenn Du von einem externen Gerät (hier 
ADC) vorzeichenbehaftete Zahlen bekommst, denn die werden ja erstmal 
unsigned aus einem Hardware-Register gelesen (SPI, I2C, ...).

Wenn man es ganz pedantisch machen will, müsste man also die Bits 
unsigned empfangen, von Hand das Vorzeichenbit prüfen, ggf. das 
Zweierkomplement zurückrechnen und dann die Zahl wieder negieren.

Oder man wählt den praktischen Ansatz und geht davon aus, dass der 
Prozessor bzw. die C-Implementierung mit Zweierkomplement rechnet und 
einem nichts böses will, dann funktioniert es einfach mit dem Cast. :)

von Walter S. (avatar)


Lesenswert?

Fabian O. schrieb:
> Wert = (int) ((int16_t) ( (((unsigned int) Buffer[0]) << 8) | ((unsigned int) 
Buffer[1]) ));

also passiert bei -1 (0xff 0xff) folgendes:
  wert = (int) ( (int16_t) ( 0x0000ff00 | 0x0000000ff ) )
       = (int) ( (int16_t) 0x0000ffff )

und das Ergebnis von (int16_t) 0x0000ffff ist implementierungsabhängig,
oder verstehe ich das falsch?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fabian O. schrieb:

> Das "Problem" hast Du aber immer, wenn Du von einem externen Gerät (hier
> ADC) vorzeichenbehaftete Zahlen bekommst, denn die werden ja erstmal
> unsigned aus einem Hardware-Register gelesen (SPI, I2C, ...).

Wieso?

Nur weil ein bestimmter Header das so macht, ist doch niemand gezwungen, 
es genauso zu machen...
1
#define ADC_VALUE (*(const int16_t volatile*) ADC_ADDRESS)

von holger (Gast)


Lesenswert?

>> Laut K&R A.6.2 ist das implementierungsabhängig da 0xffff in int16_t
>> nicht dargestellt werden kann:
>>
>>  (int16_t) ( (uint16_t)0xffff ) ist nicht definiert

Mag sein, aber ein cast ist ja keine Berechnung eines
Wertes den der uC durchführen muss. Es ist eine Änderung
der Interpretation der Werte im Speicher. Von daher gibt es
keine Probleme. 0xFFFF ist bei int16_t halt -1.

von Fabian O. (xfr)


Lesenswert?

Johann L. schrieb:
> Wieso?
>
> Nur weil ein bestimmter Header das so macht, ist doch niemand gezwungen,
> es genauso zu machen...
> #define ADC_VALUE (*(const int16_t volatile*) ADC_ADDRESS)

In dem Fall hat er ja einen externen ADC. Der Wert liegt also nicht 
schon als 16- oder 32-Bit-Wert mit garantiert passender 
Zahlenrepräsentation in einem Register. Wenn es so wäre, könnte man sich 
das ja alles sparen ...

Sondern er wird wohl per SPI oder I2C byteweise empfangen. Und da gibt 
es erstmal nur Bits, die nacheinander eintreffen und dann in einer 
bestimmten Reihenfolge im Empfangsregister stehen. Im Datenblatt des 
ADCs steht, dass man sie als vorzeichenbehaftete Zahl gemäß 
Zweierkomplement zu interpretieren hat.

Wenn man dem C-Compiler sagt, er soll den Wert im Register als 
vorzeichenbehaftetete Zahl interpretieren, dann nimmt er die 
Zahlenrepräsentation der Maschine und nicht zwangsläufig 
Zweierkomplement. Also ist es streng genommen 
plattform/implementierunsabhängig, was bei dem Code rauskommt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fabian O. schrieb:

> Der Wert liegt also nicht schon als 16- oder 32-Bit-Wert mit
> garantiert passender Zahlenrepräsentation in einem Register.
> Wenn es so wäre, könnte man sich das ja alles sparen ...
>
> Sondern er wird wohl per SPI oder I2C byteweise empfangen.

2 Bytes zu einem 16-Bitwert zu kombinieren ist doch keine 
Wissenschaft...

Oder ist die hochkomplexe Rechnung für Generation PISA?

von holger (Gast)


Lesenswert?

>2 Bytes zu einem 16-Bitwert zu kombinieren ist doch keine
>Wissenschaft...

Ingo hat doch gerade gezeigt wie man es falsch machen kann;)

von Ingo (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> probier mal
>
> Wert = ((int16_t)Buffer[0] & 0x00ff) << 8 | ((int16_t)Buffer[1] &
> 0x00ff);
Das funktioniert nicht.

holger schrieb:
> Wert = (int16_t)(((uint16_t)Buffer[0] << 8) + (uint8_t)Buffer[1]);
Das geht!

Fabian O. schrieb:
> Wert = (int16_t) ((Buffer[0] << 8) | Buffer[1]);
Auch das geht!


Vielen Dank!


Ingo

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.