Forum: Mikrocontroller und Digitale Elektronik Berechnung von float Speicherung als Integer


von Steffen (Gast)


Lesenswert?

Hallo zusammen,

ich habe eine Frage zum Umrechnen von Daten eines ADC.

Ich bekomme von einem ADC 16Bit Daten. Diese rechne ich in eine 
entsprechende Spannung um, um sie z.B. per serielle Schnittstelle 
auszugeben.
1
volatile float berechnete_spannung;
2
volatile uint16_t adc_daten;
3
/*Aus den ADC-Daten eine entsprechende Spannung berechnen*/
4
berechnete_spannung= adc_daten*(1234)*1.1;   
5
/*Komma verschieben und die berechneten Daten wieder als int speichern*/
6
adc_daten= (int)(berechnete_spannung*100);
Nun möchte ich diese umgerechneten Daten (real-Daten) allerdings auch 
noch anderweitig verwenden.  Dazu würde ich gerne den errechneten 
Spannungswert ohne das Komma in einer int-Variable abspeichern. Dazu 
verschiebe ich das Komma mit einer Multiplikation und speichere die 
Daten wieder ab.
Also z.B. berechntete_spannung : 12.49
Multipliziert mit 100 = 1249 in adc_daten speichern.
Somit stünde in:
berechnete Spannung: 12.49
adc_daten: 1249

Wenn man die Daten, hier dann wieder 16Bit, versendet kann man sie 
einfach mit einer Division wieder herstellen.

Kann man das so machen? Ist das der richtige Weg?

Steffen

von Karl H. (kbuchegg)


Lesenswert?

Steffen schrieb:
> Hallo zusammen,
>
> ich habe eine Frage zum Umrechnen von Daten eines ADC.
>
> Ich bekomme von einem ADC 16Bit Daten. Diese rechne ich in eine
> entsprechende Spannung um, um sie z.B. per serielle Schnittstelle
> auszugeben.
>
1
> volatile float berechnete_spannung;
2
> volatile uint16_t adc_daten;
3
> /*Aus den ADC-Daten eine entsprechende Spannung berechnen*/
4
> berechnete_spannung= adc_daten*(1234)*1.1;
5
> /*Komma verschieben und die berechneten Daten wieder als int speichern*/
6
> adc_daten= (int)(berechnete_spannung*100);
7
>
> Nun möchte ich diese umgerechneten Daten (real-Daten) allerdings auch
> noch anderweitig verwenden.  Dazu würde ich gerne den errechneten
> Spannungswert ohne das Komma in einer int-Variable abspeichern. Dazu
> verschiebe ich das Komma mit einer Multiplikation und speichere die
> Daten wieder ab.

Ja
Genau das hast du ja in deinem Codebeispiel gemacht.

> Also z.B. berechntete_spannung : 12.49
> Multipliziert mit 100 = 1249 in adc_daten speichern.
> Somit stünde in:
> berechnete Spannung: 12.49
> adc_daten: 1249
>
> Wenn man die Daten, hier dann wieder 16Bit, versendet kann man sie
> einfach mit einer Division wieder herstellen.
>
> Kann man das so machen?

Warum sollte man das nicht können.
Irgendwie verstehe ich die Frage nicht - du beantwortest sie dir ja 
selber.

von xfr (Gast)


Lesenswert?

Brauchst Du denn überhaupt die float-Variable? In den allermeisten 
Fällen tut es nämlich auch Festkommaarithmetik. Dann rechnet man nur mit 
Integern, was auf einem uC wesentlich schneller geht und weniger 
Programmspeicher benötigt:
1
adc_daten = (uint32_t) adc_daten * 1234 * 110;

Vermutlich hat 1234 eine andere Größe, sonst macht es von den 
Wertebereichen her nicht so viel Sinn. Wenn es ein Bruchteil ist, 
ersetzt man die Zahl durch eine Multiplikation und Division. Statt
1
* 1.234
also:
1
* 1234 / 1000
oder:
1
* 1264 / 1024
Letzteres kann der Compiler zu einem Shift machen.

Wichtig ist, erst alle Multiplikationen zu machen und am Schluss die 
Divisionen, um Rundungsfehler zu vermeiden. Dabei in einen Datentyp 
casten, der groß genug, dass er alle möglichen Zwischenergebnisse ohne 
Überlauf aufnehmen kann.

von Steffen (Gast)


Lesenswert?

Hallo,

Karl Heinz Buchegger schrieb:
> Warum sollte man das nicht können.
> Irgendwie verstehe ich die Frage nicht - du beantwortest sie dir ja
> selber.

Ich wollte einfach mal eine Bestätigung bzw. schauen, ob es auch andere, 
vielleicht bessere Lösungen dafür gibt.

xfr schrieb:
> Vermutlich hat 1234 eine andere Größe, sonst macht es von den
> Wertebereichen her nicht so viel Sinn. Wenn es ein Bruchteil ist,

Hier vielleicht nochmal das Beispiel:
1
volatile float berechnete_spannung;
2
volatile uint16_t adc_daten;
3
/*Aus den ADC-Daten eine entsprechende Spannung berechnen*/
4
berechnete_spannung= adc_daten*(0.000471)*1.1;   
5
/*Komma verschieben und die berechneten Daten wieder als int speichern*/
6
adc_daten= (int)(berechnete_spannung*100);

Die 1234 ist viel zu groß. Hab ich dann hier missverständlich 
wiedergegeben. Die liegt etwa in der Größenordnung von der zahl im 
aktuellen Beispiel.

So ginge das auch, indem ich folgendes mache:
1
volatile uint16_t adc_daten;
2
/*Aus den ADC-Daten eine entsprechende Spannung berechnen*/  
3
/*Komma verschieben und die berechneten Daten wieder als int speichern*/
4
adc_daten= (uint16_t)(adc_daten*0.000471*1.1*100);
Somit wäre dann das Ergebnis wie oben:
adc_daten enthält die Spannung in der folgenden Form: adc_daten= 1249,
so dass der Empfänger die empfangenen Daten nur noch durch 100 teilen 
muss, um wieder auf die entsprechende Spannung zu kommen.

Vielen Dank schonmal!

Steffen

von Karl H. (kbuchegg)


Lesenswert?

Steffen schrieb:


> So ginge das auch, indem ich folgendes mache:
>
1
> volatile uint16_t adc_daten;
2
> /*Aus den ADC-Daten eine entsprechende Spannung berechnen*/
3
> /*Komma verschieben und die berechneten Daten wieder als int speichern*/
4
> adc_daten= (uint16_t)(adc_daten*0.000471*1.1*100);
5
>

Zieh die Berechnung aber trotzdem selbst zusammen und sieh nach, ob du 
da nicht auf ganze Zahlen kommen kannst, damit du Integer Berechnung 
benutzen kannst. Das hat dann auch den Effekt, dass man fast automatisch 
mit überprüft, ob sich denn auch alles so ausgeht, dass man am Ende mit 
einem uint16_t auskommt, bzw. ob man den auch vernünftig ausnutzt. Hat 
ja schliesslich auch keinen Sinn, wenn dann am Ende im uint16_t nur 
Zahlen von 0 bis 100 landen können, weil sich rein rechnerisch aus dem 
ADC Wert gar nichts ausserhalb dieses Bereichs ergeben kann.
In deinem Beispiel hier wird bei einem AVR-ADC Wert, der nie größer als 
1023 sein kann, in dieser ganzen Berechnung nie etwas größeres als 53 
rauskommen. D.h. du hättest hier zb das Potential auf sogar 3 
Nachkommastellen (530), 4 Nachkommastellen würden auch noch gehen, ja 
sogar 5 wären noch drinnen.

von W.S. (Gast)


Lesenswert?

xfr schrieb:
> Wichtig ist, erst alle Multiplikationen zu machen und am Schluss die
> Divisionen, um Rundungsfehler zu vermeiden.

und zwischendurch in eine volatile Variable speichern, weil man sonst 
nicht weiß, was der Compiler einem wegoptimiert.

Wenn du nen auseichend schnellen uC hast, würde ich an deiner Stelle 
schlichtweg bei float bleiben und mir das anfällige integer Rechnen 
ersparen.
(ich mach das schon seit Jahren selbst auf PIC16Fxxx)

W.S.

von Sam .. (sam1994)


Lesenswert?

W.S. schrieb:
> und zwischendurch in eine volatile Variable speichern, weil man sonst
> nicht weiß, was der Compiler einem wegoptimiert.

Quatsch.

von xfr (Gast)


Lesenswert?

Steffen schrieb:
> adc_daten= (uint16_t)(adc_daten*0.000471*1.1*100);

Karl Heinz hat es Dir ja schon ausgerechnet: Damit kann adc_daten nur 
Werte zwischen 0 und 53 annehmen (falls es vorher zwischen 0 und 1023 
lag). Das ist wahrscheinlich nicht, was Du möchtest.

Die äquivalente Rechnung ohne Fließkommazahlen sähe so aus:
1
adc_daten = (uint32_t) adc_daten * 471 * 110 / 1000 / 1000;
oder:
1
adc_daten = (uint32_t) adc_daten * 494 * 110 / 1024 / 1024;
bzw. noch genauer:
1
adc_daten = (uint32_t) adc_daten * 54327 / 1024 / 1024;

Sinnvoller wäre aber, eine Division durch 1000 bzw. 1024 wegzulassen und 
damit den Wertebereich auf 0 bis 53053 zu vergrößern. Also so:
1
adc_daten = (uint32_t) adc_daten * 471 * 110 / 1000;

von Ralph (Gast)


Lesenswert?

W.S. schrieb:
> würde ich an deiner Stelle
> schlichtweg bei float bleiben und mir das anfällige integer Rechnen
> ersparen.

Unsinn, Dein µC rechnet sowieso auf Integer.
Wenn du float verwendest überlässt du das umskalieren der Werte von 
Float nach Integer nur einer Library.
Es sei denn das dein µC eine FPU hat.  Naja vergessen wir das bei den 
AVR's

Das problem bei diesem Vorgehen ist , das du bei jeder Berechnung 2 
Umwandlungen hast. Float ==> Int ==> Berechnung ==> Int ==> Float.
Jede Umwandlung führt zu einem Verlust von Auflösung.
Das kann so gering sein das es nicht beachtet werden muss, aber kan auch 
leicht so viel werden das es eine signifikanten Auswirkung auf das 
ergebnis hat.

Wenn also der µC keine FPU hat ist es IMMER besser komplett in INT zu 
rechnen und erst für die Ausgabe aufs Display in eine Float Darstellung 
umzurechnen.
Und ja man muss für die Skalierung etwas mehr nachdenken als wenn man 
das ganze einer Library überlässt.

von Sebastian S. (sebastian_s50)


Lesenswert?

Hin und Her und Her und Hin!

Es gibt kaum etwas Genaueres als Rohdaten. Natürlich gibt es auch Gründe 
diese zu Konvertieren, auch z.B. in Fließkommazahlen. Sollte dies aber 
an anderer Stelle wieder rückgängig gemacht werden, vor allem durch eine 
erneute Konvertierung, DANN IST IRGENDETWAS SCHIEF GELAUFEN!

Wie auch schon an anderer Stelle angedeutet: EINE UMRECHNUNG BRINGT NIE 
EINE VERBESSERUNG, die Wahrscheinlichkeit einer Verschlechterung ist 
aber nicht auszuschließen.

von Rolf Magnus (Gast)


Lesenswert?

Steffen schrieb:
> volatile float berechnete_spannung;
> volatile uint16_t adc_daten;

Hat es einen speziellen Grund, daß du hier alles volatile machst?

Karl Heinz Buchegger schrieb:
> n deinem Beispiel hier wird bei einem AVR-ADC Wert, der nie größer als
> 1023 sein kann, in dieser ganzen Berechnung nie etwas größeres als 53
> rauskommen.

xfr schrieb:
> Karl Heinz hat es Dir ja schon ausgerechnet: Damit kann adc_daten nur
> Werte zwischen 0 und 53 annehmen (falls es vorher zwischen 0 und 1023
> lag).

Ralph schrieb:
> Naja vergessen wir das bei den AVR's

Irgendwie kann ich nirgends einen Hinweis darauf finden, daß es sich um 
einen AVR handelt oder daß der Bereich des ADC auf 0 bis 1023 beschränkt 
sein könnte. Und selbst wenn, dann könnte der ADC immer noch auf "left 
aligned" stehen. Dann wäre der maximale Rohwert aus dem ADC nicht 1023, 
sondern 65472.

von Karl H. (kbuchegg)


Lesenswert?

Rolf Magnus schrieb:

> Karl Heinz Buchegger schrieb:
>> n deinem Beispiel hier wird bei einem AVR-ADC Wert, der nie größer als
>> 1023 sein kann, in dieser ganzen Berechnung nie etwas größeres als 53
>> rauskommen.
>

> Irgendwie kann ich nirgends einen Hinweis darauf finden, daß es sich um
> einen AVR handelt oder daß der Bereich des ADC auf 0 bis 1023 beschränkt
> sein könnte.

Da hast du recht - ich habe es angenommen.

Was soll der TO daraus lernen?
Es ist meistens schlecht, Fragen so ganz allgemein ohne Bezug zu einer 
realen Situation zu stellen.

von Morz Bonzo (Gast)


Lesenswert?

So ganz nebenbei ... Ein 32bit Float hat 5-6 signifikannte Stellen, das 
kommt daher, dass die Mantisse 14 bit ist. der Rest ist Exponent.
Demgegenueber hat ein Longint, 32 bit, bis zu 9 signifikatte Stellen.
Dh falls der dynamische Bereich des Resultates nicht ueber zu viele 
Dekaden variiert, man sich den Exponenten im Kopf behalten kann, ist man 
mit Longint schneller & genauer wie 32bit float.

Also, .. float, flupp - in die Tonne

von Morz Bonzo (Gast)


Lesenswert?

Sorry, die Mantisse eines 32bit floats ist natuerlich 24bit.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ich verstehe nicht, warum überhaupt konvertiert wird. Warum wird nicht 
einfach das, was der ADC liefert (und das ist bereits eine Ganzzahl!) 
über den UART verschickt?

Der PC am anderen Ende kann das doch in eine Fließkomma-Zahl umrechnen.

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.