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
volatilefloatberechnete_spannung;
2
volatileuint16_tadc_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
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
>volatilefloatberechnete_spannung;
2
>volatileuint16_tadc_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.
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.
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
volatilefloatberechnete_spannung;
2
volatileuint16_tadc_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
volatileuint16_tadc_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
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.
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.
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:
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.
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.
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.
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.
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
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.