Hallo zusammen,
ich möchte einen Strom über einen ADC Eingang des ATmega32 messen.
Dazu habe ich den Wert ausgelesen und in einer uint16_t Variable
abgelegt.
Da der ADC ja nur 10Bit hat, liegen dort ja auch nur Werte zwischen 0
und 1023.
Nun habe ich mir eine Funkion geschrieben, die mir den Strom in mA
umrechnen soll, da ich den Strom im Programm vergleichen möchte.
1
floatADC2I_Melder(uint16_tadcvalue)
2
{
3
return(adcvalue*0.00488*0.01);//Umrechnung aus Auflösung ADC und Messwiderstannd=100Ohm, 0,1V pro mA
4
5
}
In der Main-Funkion habe ich dann folgende Auswertung.
1
if(stromMG1>0.2)
2
{
3
PORTB|=(1<<PB0);
4
}
5
else
6
{
7
PORTB&=~(1<<PB0);
8
}
Nur leider klappt diese Auswertung nicht. Vergleiche ich die uint16_t
Zahl direkt aus dem ADC, klappt die Auswertung in der Main einwandfrei.
Daher muss der Fehler entweder in der Umrechnung sein, oder aber in bei
der Auswertung.
Kann mir jemand einen Tipp geben, wo mein Fehler sein kann.
Vielen Dank.
Gruß Martin
Wenn ich mir die Zahlen so anschaue, dann kann der Wert maximal 1023 (10
Bit max Value) * 0.01 * 0.00488 werden das ist bei mir 0.0499.
Wie soll das jemals größer als 0.2 werden?
Die Faktoren bei der Umrechnung sind falsch: Der erste Faktor gibt die
Umrechnung in eine Spannung (in V). Um daraus dann den Strom im mA zu
bekommen müsste man mit 10 Multiplizieren, nicht mit 0.01. So gibt das
den Strom in A.
Das in float umzurechnen ist sowiso Blödsinn, kostet nur Platz und viel
Zeit. Eine Kommazahl braucht nur der menschliche Anwender wenn er es
angezeigt bekommt.
Martin schrieb:> Nun habe ich mir eine Funkion geschrieben, die mir den Strom in mA> umrechnen soll, da ich den Strom im Programm vergleichen möchte.
Wenn du nur vergleichen musst, dann ist es vernünftiger den
Vergleichswert in ADC Einheiten umzurechnen und nicht den ADC Wert in
Milliampere. Denn typischerweise vergleichst du viel öfter als das sich
der Vergleichswert ändert.
Hallo zusammen,
vielen Dank für die schnellen Antworten. Ich hab tatsächlich immer in A
gerechnet. Keine Ahnung, was mich da geritten hat.
Ich möchte zukünftig die Werte auch über ein LCD ausgeben, daher wäre
eine Gleitpunktzahl schon ganz ansehnlich.
Gruß Martin
@ Martin
Schau Dir mal in diesem Forum einige Threads zum Thema Ausgabe von
Fliesskommazahlen an.
Der allgemeine Teno ist, dass die oft auftretende Vermutung, man müsse
in Gleitkomma rechnen, weil man Gleitkomma ausgeben will, letztlich
falsch ist. Die Verfahren dazu wurden hier schon oft besprochen. Das
findest Du leicht.
Der tiefere Grund für die Vermeidung von Gleitkommazahlen ist, dass der
AVR recht viel Zeit und Code benötigt, um Berechnungen damit
auszuführen. Tatsächlich ist es in der Mehrzahl der Fälle auch nicht
wirklich nötig, mit Gleitkomma zu rechnen.
Wie Dir vielleicht noch aus der Schule bekannt ist, kann man die
Ergebnisse aller nicht-transzendenten Funktionen (d.h. was nicht etwa
trigonometrische Zahlen, Wurzeln, Logarithmen, etc.) als Brüche
darstellen.
Was aber auch wichtig ist, das Du Dir bei der Planung des Programmes
vorab Gedanken über die beteiligten Ein- und Ausgangs-Zahlen und die
Zwischenwerte und deren jeweilige Grössenordnungen machst.
Von der Bedeutung her ist das damit vergleichbar etwa Ports ansprechen
zu können oder die Sprache C ersteinmal zu können. Es ist unverzichtbar
sich diese Fähigkeit anzueignen.
Im übrigen kann man selbst bei den transzendenten Funktionen in der
Regel mit Näherungen arbeiten. Das machen die Bibliotheksfunktionen für
float und doube auch. Nur mit wesentlich mehr Stellen als es in der
Praxis mit uCs nötig ist.
Hallo Martin,
wie ja schon gesagt, muss es (adcvalue*0.00488*10) sein, damit du alles
in mA Einheiten hast.
Mein Vorschlag wäre, alles in µA zu skalieren.
Dann wäre es: (adcvalue*0.0048828..*10*1000)
oder auch (adcvalue*48,828...)
oder (adcvalue*49) , dann hast Du zwar 0.35% Fehler, was aber oft
vernachlässigbar klein ist.
oder ((adcvalue*(49-0.171875)) =((adcvalue*49-adcvalue*0.171875)) und
das ist (adcvalue 49 - adcvalue 11 / 64 ).
Das ist dann exakt, ohne Divison (da /64 durch Schieben optimiert wird)
und ohne Gleitkomma.
guldn schrieb:> oder (adcvalue*49) , dann hast Du zwar 0.35% Fehler, was aber oft> vernachlässigbar klein ist.
Wobei sich auch die Frage erhebt, wie genau denn eigentlich die 100 Ohm
des Widerstands sind und ob diese Genauigkeit das 'rummosern' an 0.35%
Fehler überhaupt rechtfertigt.
Es ist ja nicht nur das, sondern das Problem bei float besteht ja auch
darin, dass man nur so um die 5 bis 6 signifikante Stellen hat.
Signifikante Stellen! Nicht Nachkommastellen!
D.h. bei 0.00488 sind die 6 signifikanten Stellen schon aufgebraucht.
Bei einem Wert von 1023 sind bereits mit 2 Nachkommastellen die 6
signifikanten Stellen aufgebraucht. Und je öfter man mit einem float
rechnet, desto mehr 'schmutz' sammelt sich an, so dass aus den 6
signifikanten Stellen ganz schnell 5 werden. D.h. alles nach der 5.
Stelle ist nicht mehr vertrauenswürdig.
Bitflüsterer schrieb:> Der tiefere Grund für die Vermeidung von Gleitkommazahlen ist, dass der> AVR recht viel Zeit und Code benötigt, um Berechnungen damit> auszuführen.
Das ist doch Quatsch. Die float-Grundrechenarten brauchen etwa 1kB,
sodass man auch auf einem aktuellen ATtiny.. bequem float rechnen kann.
Vergleicht man zudem die Ausführungszeiten von float und int32_t (long),
besteht da kein großer Unterschied. Float hat den Vorteil des großen
Wertebereiches bei hinreichender Genauigkeit.
Auch wenn ich im vorliegenden Fall mit Integerwerten rechnen würde,
zwingend auf float zu verzichten ist unbegründet, solange der Code ins
Flash-ROM passt.
Die obige Zahlenakrobatik führt doch eher zu zusätzlichen Fehlern, als
zur Lösung eines Problems.
m.n. schrieb:> zwingend auf float zu verzichten ist unbegründet, solange der Code ins> Flash-ROM passt.
Und fuer nicht verbrauchtes Flash gibt es kein Geld zurueck....
@ m.n.
> Das ist doch Quatsch. Die float-Grundrechenarten brauchen etwa 1kB,
"Quatsch" ist das sicher nicht. Für den einen ist 1kB (oder welche
Grösse auch immer) viel, für den anderen wenig. Jedenfalls ist es nicht
sehr höflich eine Aussage eines anderen mit "Quatsch" zu bezeichnen.
>...zwingend auf float zu verzichten ist unbegründet...
Von "zwingend" habe ich nichts geschrieben. Es wäre mir angenehmer für
etwas kritisiert zu werden, was ich wirklich geschrieben habe.
>Das ist doch Quatsch. Die float-Grundrechenarten brauchen etwa 1kB,
sodass man auch auf einem aktuellen ATtiny.. bequem float rechnen kann.
Vergleicht man zudem die Ausführungszeiten von float und int32_t (long),
besteht da kein großer Unterschied.
Jagt den Troll raus.
Bitflüsterer schrieb:> Von "zwingend" habe ich nichts geschrieben.
Was ist denn konkret Deine Aussage?
Du redest von einem 'tiefen Grund', was kein Grund sondern ein
abgründiges Vorurteil ist.
Hier geht die Hexenverbrennung dann weiter:
Karl Heinz schrieb:> Und je öfter man mit einem float> rechnet, desto mehr 'schmutz' sammelt sich an, so dass aus den 6> signifikanten Stellen ganz schnell 5 werden. D.h. alles nach der 5.> Stelle ist nicht mehr vertrauenswürdig.
Dann zeigt doch mal eine Berechnung mit float, die so umfangreich ist,
dass die Zahlen dabei schmutzig werden. Und dann bitte zum Vergleich
noch die erleuchtende Berechnung mit long-Zahlen, die nicht schmutzig
sind.
Dann sieht man nämlich viel besser, dass diese Argumentation an den
Haaren herbeigezogen ist. Aber überkommene Vorurteile wieder aufzuwärmen
findet immer breite Zustimmung beim staunenden Fußvolk.
Um wieder auf den "Quatsch" zu kommen: mir geht der Quatsch einfach auf
den Senkel.
nochwas schrieb:> Jagt den Troll raus.
Jau, auf den Scheiterhaufen.
Sowohl die berechnung als auch die Verwendung vom float Typ sind hier
zumindest aus Sicht des Mikrocontrollers unnötig aufwändig.
Vorschlag wie man sowas angehen kann:
1
#define VOLT 1024/5
2
3
if (adcvalue > 2*VOLT ) {...}
VOLT gibt an, welchen Wert der ADC bei einem Volt liefert.
20mA ergeben bei 100 Ohm 2 Volt.
Wenn Du magst, kannst Du auf diese Art auch die Umrechnung in
Milliampere machen:
1
#define AMPERE 100*VOLT
2
#define MILLIAMPERE AMPERE/1000
3
4
if (adcvalue > 20*MILLIAMPERE ) {...}
AMPERE ist der Wert, den der ADC bei einem Ampere und 100 Ohm liefern
wuerde (wenn er es koennte).
MILLAMPERE ist der Wert, den der ADC bei einem Milliampere liefert.
Der Trick ist, dass diese Definitionen schon beim Compilieren optimiert
werden / ganz ohne Fliesskomma-Artihmetik:
20*MILLIAMPERE = 20*100*1024/5/1000 = 409
Es ist nicht 409.6, weil C den ganzen Ausdruck als Integer betrachtet,
denn alle Teile des Ausdruckes sind Integer Zahlen.
Zur Laufzeit muss der Mikrocontroller so nur noch testen, ob adcValue >
409 ist. Die Umrechnung der Einheiten entfaellt somit komplett.
> #define VOLT 1024/5> #define AMPERE 100*VOLT> #define MILLIAMPERE AMPERE/1000
Dabei muss man aufpassen, dass das richtig gerechnet wird, evtl. wird
das im Präprozessor nämlich nur mit 16 Bit gerechnet, der Wertebereich
ist dabei schnell überschritten.
Besser mindestens in einer Zahl vorne ein L spendieren, dann wird alles
long gerechnet (im Präprozessor):
#define VOLT 1024L/5
#define AMPERE 100L*VOLT
#define MILLIAMPERE AMPERE/1000L
Damit der µC nicht in 32bit rechnet, casten:
if (adcvalue > (uint16_T)(20*MILLIAMPERE) ) {...}