Forum: Mikrocontroller und Digitale Elektronik Probleme mit int32 bei Berechnung - Ergebnis immer falsch


von Dennis (Gast)


Lesenswert?

Hi Leute!

Ich stehe gerade vor einem Rätsel. Ich möchte zwei int32-Werte 
miteinander verrechnen. Ich arbeite mit dem MSP430G2955 und CCS V5

Ich habe eine Funktion, welche mir einen Wert berechnen soll. Als 
Argument bekommt sie einen int32_t.

Wenn ich jetzt mit diesem Wert weiterrechnen will, dann kommt nurnoch 
Müll raus...
1
float lin_calc_percentage_linear( int32_t adc_val )
2
{
3
  float   adc_percentage;
4
  float   y2_minus_y1;
5
  int32_t x_minus_x1;
6
  int32_t x2_minus_x1;
7
8
  x_minus_x1 = (adc_val - device_param.adc_cal_values[0]);
9
  x2_minus_x1 = device_param.adc_cal_span;
10
  y2_minus_y1 = device_param.percent_cal_span;
11
12
  adc_percentage = (device_param.percent_cal_values[0] + ((y2_minus_y1 * x_minus_x1) / x2_minus_x1));
13
14
15
  return adc_percentage;
16
}

device_param.adc_cal_values[0] und device_param.adc_cal_span sind auch 
vom Typ int32_t und device_param.percent_cal_values[0] ist ein float - 
aber diese Werte sind ja sogar uninteressant, da der Fehler vorher 
auftritt.

adc_val hat jetzt beispielsweise einen Wert von 16000000, was ja locker 
in den int32 reinpasst.

device_param.adc_cal_values[0] hat den Wert 1764944.

Also:
1
x_minus_x1 = 16000000 - 1764944 = 14235056

x_minus_x1 wird nicht richtig berechnet. Da steht als Ergebnis -51792 
!!!


Ich habe jetzt alles doppelt und dreifach wegen den Datentypen 
kontrolliert. Und testweise habe ich mal einfach
1
x_minus_x1 = (adc_val - 5)
reingeschrieben - auch falsches Ergebnis!

Was funktioniert:
1
x_minus_x1 = adc_val;
und
1
x_minus_x1 = (adc_val - 0)

Kann mir jemand erklären, was da los sein kann? Ich habe auch schon den 
gesamten Workspace "gecleant"...Fehler bleibt!

Jemand 'ne Idee?

von Philipp L. (philipp_l89)


Lesenswert?

Du scheinst ja einen debugger zu haben. Was passiert denn in den 
Registern der betroffenen Variablen?

von Karl H. (kbuchegg)


Lesenswert?

Dennis schrieb:

> Also:
>
1
> x_minus_x1 = 16000000 - 1764944 = 14235056
2
>
>
> x_minus_x1 wird nicht richtig berechnet. Da steht als Ergebnis -51792
> !!!

Wo steht dieses Ergebnis?
Schon mal in Betracht gezogen, dass auch die Ausgabefunktion fehlerhaft 
sein kann, bzw. die falsche Ausgabefunktion benutzt wird?

von Karl H. (kbuchegg)


Lesenswert?

Dennis schrieb:

>
1
> x_minus_x1 = 16000000 - 1764944 = 14235056
2
>
>
> x_minus_x1 wird nicht richtig berechnet. Da steht als Ergebnis -51792
> !!!


Mal eine Beobachtung:

16Mio - 1764944 ergibt die von dir angegebene Zahl.
Als Hexzahl lautet das Ergebnis
1
       00 D9 35 B0

Du siehst an der Ausgabe das Ergebnis -51792. Dieses Ergebnis wiederrum 
als Hex-Zahl ausgedrückt, lautet
1
       FF FF 35 B0

Was sofort auffällt: die unteren 16 Bit stimmen. lediglich die oberen 
sind falsch. Das scheint so zu sein, dass da irgendwo 16 Bit verloren 
gehen.

Wenn wir mal davon ausgehen, dass der Compiler keinen Fehler gemacht 
hat, dann muss irgendwo das High-Word verloren gegangen sein. Unklar ist 
dann allerdings, wo die 0xFFFF im High-Word herkommen.

von Rene H. (Gast)


Lesenswert?

Karl Heinz schrieb:
> Wo steht dieses Ergebnis?
> Schon mal in Betracht gezogen, dass auch die Ausgabefunktion fehlerhaft
> sein kann, bzw. die falsche Ausgabefunktion benutzt wird?

Wenn man sich die beiden Zahlen binär anschaut, ist das sogar ziemlich 
sicher so.

Grüsse,
René

von Karl H. (kbuchegg)


Lesenswert?

> adc_val hat jetzt beispielsweise einen Wert von 16000000, was ja locker in den 
int32 reinpasst.

Wo hast du diese 16000000 verifiziert?

Innerhalb der Funktion oder gehst du davon aus, dass der Wert stimmt, 
weil du weißt dass da 16000000 drinnen stehen müsste.



> Was funktioniert:
> x_minus_x1 = adc_val;
> und
> x_minus_x1 = (adc_val - 0)

Das allerdings ist schon ein Fingerzeig, dass der Weg in Richtung 
Compiler-Fehler gehen könnte. Denn wenn adc_val schon falsch ist, dann 
müsste auch hier ein Word-Verlust feststellbar sein.

Nichts desto trotz, lass uns mal den Suchkreis auf den Aufruf der 
Funktion ausdehen. Wo kommt adc_val her, was passiert im Vorfeld mit 
diesem Wert?
(allerdings ist die erste Frage immer noch: wie ist 'da steht' zu 
verstehen? Eigene Funktion oder Debugger-Ausgabe. Das wär momentan mal 
die wichtigere Frage aus meiner Sicht)

: Bearbeitet durch User
von cybmorg (Gast)


Lesenswert?

Wie ueblich fehlt hier ein auf's Minimum reduziertes Testprogramm. Gaebe 
es das, waere das Problem wahrscheinlich schon gefunden.

von Dennis (Gast)


Lesenswert?

Hi Leute!

Vielen Dank für eure zahlreichen Beiträge.

Ich habe das Problem gerade entdeckt - die Rechnung funktioniert an 
sich, das Problem liegt hier:

Der Wert adc_val kommt von außen, klar. Um die Funktion nun mit 
verschiedenen Werten zu testen, also wenn z.B. der ADC-Wert niedriger 
als der minimal kalibrierte Wert, bzw. höher als der maximal kalibrierte 
Wert liegt, habe ich die Funktion aufgerufen und vor der ersten Aktion
1
x_minus_x1 = (adc_val - device_param.adc_cal_values[0]);
den Debugger angehalten und den adc_val, welcher ja von außen kommt und 
irgendeinen Wert aus der Messung enthält, selbst auf den Wert 16000000 
gesetzt. adc_val habe ich in meinem "Watch"-Fenster drin, da kann ich ja 
alle Werte jederzeit ändern, solange der Debugger auf Pause steht.

Der Wert wird auch übernommen, die 16000000 stehen dann im Watch-Fenster 
für die Variable adc_val. Mache ich mit dem Programm jetzt weiter, wird 
falsch gerechnet. Irgendwie wird die neu eingebene Zahl nicht für die 
Rechnung benutzt.

Manipuliere ich den Wert vor Aufruf der Funktion, dann funktioniert es 
wie erwartet. Also natürlich nicht adc_val selbst, sondern die Variable, 
welche dann an adc_val übergeben wird.

Kann mir jemand sagen, warum das so ist?

von Dennis (Gast)


Lesenswert?

Was mir halt garnicht einleuchtet ist die Tatsache, dass ich den in der 
Funktion manipulierten Wert mit 0 verrechnen kann und es dann stimmt. 
Und ebenso funktionierte die Zuweisung des manipulierten Wertes an eine 
andere Variable.

Nur eben das verrechnen mit einem Wert != 0 geht nicht.

von andi6510 (Gast)


Lesenswert?

welchen Datentypen verwendest Du für device_param.adc_cal_values[0]?

Möglicherweise einen 16-bit Typ?

Funktioniert es eventuell wenn du einen expliziten type-cast schreibst:

  x_minus_x1 = (adc_val - (int32_t) device_param.adc_cal_values[0]);

???

von Dennis (Gast)


Lesenswert?

andi6510 schrieb:
> welchen Datentypen verwendest Du für device_param.adc_cal_values[0]?

Dennis schrieb:
> device_param.adc_cal_values[0] und device_param.adc_cal_span sind auch
> vom Typ int32_t

Die Datentypen sind "leider" alle gleich

von c-hater (Gast)


Lesenswert?

Karl Heinz schrieb:

> Was sofort auffällt: die unteren 16 Bit stimmen. lediglich die oberen
> sind falsch. Das scheint so zu sein, dass da irgendwo 16 Bit verloren
> gehen.
>
> Wenn wir mal davon ausgehen, dass der Compiler keinen Fehler gemacht
> hat, dann muss irgendwo das High-Word verloren gegangen sein. Unklar ist
> dann allerdings, wo die 0xFFFF im High-Word herkommen.

Signed extend. Das riecht doch ziemlich stark nach Compilerfehler.

Aber ich verstehe das Problem nicht. Ein Blick in den Assemblercode 
klärt das doch umgehend. Keine Ahnung, warum das der TO nicht schon 
längst selbst gemacht hat, bevor er den Thread überhaupt erst eröffnet 
hat.

Ohne harte Fakten ist das doch alles bloß sinnloses Rumgerate.

von Steffen R. (steffen_rose)


Lesenswert?

Dennis schrieb:
> Was mir halt garnicht einleuchtet ist die Tatsache, dass ich den in der
> Funktion manipulierten Wert mit 0 verrechnen kann und es dann stimmt.
> Und ebenso funktionierte die Zuweisung des manipulierten Wertes an eine
> andere Variable.
>
> Nur eben das verrechnen mit einem Wert != 0 geht nicht.

Kommt immer auf den Compiler bzw. Debugger an. Zum einen müßtest Du im 
Disassembler genau sehen, was gemacht wird. Im einfachsten Fall änderst 
Du den Wert auf der RAM Zelle. An der Stelle, an der Du anhälst, rechnet 
dein Programm dann bereits mit den Registerinhalten.

Für solche Spielereinen ohne genaues nachsehen würde ich Optimierung 0 
und wenn das nicht reicht - volatile für alle relevanten Variablen - 
empfehlen. Nur nicht vergessen, das hinterher wieder wegzumachen.

Allerdings frage ich mich, ob es nicht einfacher ist, die Testwerte 
direkt im Sourcecode zu setzen.

von Go MSP (Gast)


Lesenswert?

Ich glaube nicht an einen Compilerfehler, solange hier nicht der 
lauffähige Code gezeigt wird. Da steckt bestimmt ein Programmierfehler 
drin und die Warnings werden missachtet oder sind ausgeblendet.

von Helmut (Gast)


Lesenswert?

Dennis schrieb:
> Was funktioniert:
> x_minus_x1 = adc_val;
> und
> x_minus_x1 = (adc_val - 0)

na das bedeutet doch nur, das keine veränderung vorgenommen wird --> der 
Compiler erkennt das und optimiert die Subtraktion gleich ganz weg...

von Jim M. (turboj)


Lesenswert?

Dennis schrieb:
> Manipuliere ich den Wert vor Aufruf der Funktion, dann funktioniert es
> wie erwartet. Also natürlich nicht adc_val selbst, sondern die Variable,
> welche dann an adc_val übergeben wird.
>
> Kann mir jemand sagen, warum das so ist?

Weil der Compiler die Variable irgenwann in ein (oder zwei bei int32_t) 
Register packt um damit zu rechnen. Dann kannst Du die Variable im RAM 
zwar ändern - der Prozessor rechnet aber weiter mit dem Inhalt der 
Register.

Darum zeigt ein guter Debugger auch immer den disassemblierten Code an, 
dort sieht man ob gleich mit Registern gerechnet oder eine Variable aus 
dem RAM geladen wird.

Ein optimierender C-Compiler wird so oft wie möglich mit Registern 
arbeiten.

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.