Forum: Mikrocontroller und Digitale Elektronik STM32 und MPU6050: Falsche Werte


von E. T. (jadeaffenjaeger)


Lesenswert?

Hallo, ich versuche gerade mit einem STM32 Werte aus einer MPU6050-IMU 
zu lesen. Ich habe dazu diese Funktion geschrieben:
1
void read_sensor(sensor_raw * data) {
2
  int8_t buf[14];
3
  read_buffer(MPU6050_RA_ACCEL_XOUT_H, &buf, 14);
4
  data->temp =   (((float) (buf[6] << 8 | buf[7]))    /340.0 + 36.53);
5
  data->x_acc =   (((float) (buf[0] << 8 | buf[1]))    /16384.0);
6
  data->y_acc =   (((float) (buf[2] << 8 | buf[3]))    /16384.0);
7
  data->z_acc =   (((float) (buf[4] << 8 | buf[5]))    /16384.0);
8
  data->x_gyro =   (((float) (buf[8] << 8 | buf[9]))    /131.0);
9
  data->y_gyro =   (((float) (buf[10] << 8 | buf[11]))    /131.0);
10
  data->z_gyro =   (((float) (buf[12] << 8 | buf[13]))    /131.0);
11
}
read_buffer() ist dabei nur ein Wrapper um eine HAL-Funktion, die per 
I2C von der IMU liest.

Ungefaehr bei jeder zweiten Messung, allerdings ohne (erkennbares) 
Muster, zeigt der Debugger (Eclipse CDT mit OpenOCD) eigenartige 
Ergebnisse im Speicher an.

Beispiel:
x_acc:-0.434814453
y_acc:-0.00268554688
z_acc:-0.00317382812

Da ich mich auf dem Planeten Erde befinde, und die IMU vor mir auf einem 
Tisch liegt, wuerde ich erwarten, dass sqrt(x^2 + y^2 + z^2) zumindest 
in der Groessenordnung von eins liegen muesste. Bei etwa jeder zweiten 
Messung taucht auf der z-Achse auch der richtige Wert, etwa 0.8, auf.
Als Schuldigen vermute ich meinen Code, der nach float castet, da die 
rohen Sensordaten gut aussehen.

Zum Beispiel fuer die z-Achse (selbes Sample):
H: 0x32 L: 0xcc
(Das ist eine Zweierkomplementzahl, bei der das erste Bit nicht gesetzt 
ist. Das dabei als Ergebnis ueberhaupt was Negatives rauskommt ist also 
schonmal komplett verkehrt).

Ist mein Code falsch? Oder spielt mir Eclipse da einen Streich?

von Nico W. (nico_w)


Lesenswert?

Guck dir mal deinen buf an. Das ist ein int8_t. Den schiebst du 8 bits 
nach links?!? Das kann ja so nicht stimmen.

von E. T. (jadeaffenjaeger)


Lesenswert?

Das ist jeweils das untere und obere Byte, welche konkateniert werden. 
Das Ergebnis wird ja nicht ins Array zurueck geschrieben, sonder dient 
direkt als Operator fuer den Cast. Was denkst du, warum das nicht 
funktioniert?

von Nico W. (nico_w)


Lesenswert?

Step by step mal durchgehen was passiert.
1
int8_t x = 100;
2
int8_t malguckenwaspassiert;
3
malguckenwaspassiert = x << 8;

Edit: Und mal nen paar Warnings vom Compiler einschalten. Der sollte da 
eigentlich schon Alarm schlagen.

von E. T. (jadeaffenjaeger)


Lesenswert?

Ich verstehe, was du meinst. Aber wie gesagt: Ich schreibe das Ergebnis 
anders als in deinem Samplecode ja nicht in eine 8Bit-Speicherstelle, 
verliere die vorderen Bits also auch nicht. C speichert das 
Zwischenergebnis beim Kompilieren automatich in einer 16-bit-Variablen. 
Zum Vergleich:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
int main(int argc, const char *argv[])
5
{
6
  uint8_t foo = 0x80;
7
  float test = (float) (foo << 8);
8
  printf("Ergebnis: %f \n", test);
9
  return 0;
10
}
liefert als Ergbnis: 32768.0

(Habe auch schon probiert, erstmal das Zwischenergebnis in einem 
int_16t-Array zu speichern und dann zu casten. Macht fuer den Fehler 
aber keinen Unterschied)

von Häkelnadel (Gast)


Lesenswert?

> ...  x << 8

hat ein 16-Bit Ergebnis

1
#include<stdio.h>
2
3
int main (void)
4
{
5
    int a;
6
    char b = 100;
7
8
    a = b << 8;
9
10
    printf("%i", a);
11
12
    return(1);
13
}

von Nico W. (nico_w)


Lesenswert?

E. T. schrieb:
> C speichert das
> Zwischenergebnis beim Kompilieren automatich in einer 16-bit-Variablen.

C macht das was du ihm sagst. Ggf. optimiert er dein Beispiel aus und 
sieht nie dein 8bit Wert?
Caste doch mal dein buf vorher auf 16bit und guck dir das dann an.

von E. T. (jadeaffenjaeger)


Lesenswert?

Testweise fuer eine Achse:
1
uint16_t res = buf[4] << 8 | buf[5];
2
data->z_acc =   (((float) (res))    /16384.0);

Gibt immer noch bei jeder zweiten oder dritten Messung wirre Werte aus. 
(Nun allerdings 3.99)

von Nico W. (nico_w)


Lesenswert?

Ist denn der buf wirklich int oder vielleicht doch uint?

von E. T. (jadeaffenjaeger)


Lesenswert?

int8_t. Ganz sicher.
Zwischenergebnis muss natuerlich auch int16_t sein.
Wenn ich das korrigiere, ist der Fehler auch wieder exakt der selbe wie 
vorher.

von E. T. (jadeaffenjaeger)


Lesenswert?

Ah, habe die Loesung glaube ich gefunden:
Das hintere Byte wird bei der Umrechnung ebenfalls als 
vorzeichenbehaftet betrachtet, was scheinbar die Oder-Operation 
veraendert. Falsche Ergebnisse gibts immer genau dann, wenn das MSB im 
unteren Byte gesetzt ist. Werde das nachher mal fixen, und gucken, was 
passiert.

von Nico W. (nico_w)


Lesenswert?

Left shift von nicht-negativen Zahlen geht. Aber sobald du eine negative 
Zahl hast, ist das Ergebnis nicht definiert.

Du solltest dir nochmal genau ansehen, was da aus deinem read_buffer 
wirklich kommt. Der Code dazu sollte helfen.

http://stackoverflow.com/questions/7622/are-the-shift-operators-arithmetic-or-logical-in-c
> The caveat here is that for signed types the value should be non-negative
> and the resulting value should be representable in the result type.
> Otherwise the operation is undefined.

von E. T. (jadeaffenjaeger)


Lesenswert?

Damit hat es nix zu tun. Mittlerweile ist mir klar, wo das Problem 
liegt.

Falls sich jemand fuer die Loesung interessiert:
Beide Zahlen werden vorzeichenbehaftet interpretiert, das heisst sie 
werden auch mit Blick aufs MSB expandiert.
1
1010 1010 << 8 | 1010 1010

wird intern also zu:
1
   1010 1010 0000 0000
2
OR 1111 1111 1010 1010
3
=  1111 1111 1010 1010
Deshalb kam auch immer eine sehr kleine, negative Zahl raus.

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.