Forum: Mikrocontroller und Digitale Elektronik ADC Messung falsch


von Andi (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich möchte mit meinem ATmega168 die Quellspannung vor meinem 5V Regler
rückmessen.
Dazu habe ich einen Spannungsteiler R1=22k und R2 = 10k sowie zum
"ruhig" Messen einen 0.680 µF Kondensator an meinem Kanal ADC0.
Am PIN0 liegt auch die erwartete Spannung an
z.B. U_q = 12V PIN ADC = 3,75V.

Der ADC ist an AVref mit 5V und ADCC ebenfalls 5V beschalten. (davor 
jeweils ein LC Tiefpass). Möchte AVref verwenden.

Jetzt wollte ich mit angehängtem Code eine LED AN schalten wenn
U_q größer 10V ist und aus wenn U_q kleiner 10 V ist.
Leider geht die LED aber erst an wenn Uq ca. 13 V ist.
Wenn ich die 10V grenze auf z.B. 8V reduziere geht die LED erst bei
ca. 10 V an.

Denke an der HW ist alles ok da die erwarteten Werte an den PINS 
anliegen.
Findet vielleicht jemand in meinem Code einen Fehler? Bin noch Anfänger.

Danke schonmal & Gruß Andi

von Andi (Gast)


Lesenswert?

Achso:
Verwende einen externen 16 MHz Quarz.

von Karl H. (kbuchegg)


Lesenswert?

Ohne das jetzt konkret analysiert zu haben, ob das deine Ergebnisse 
bestätigt:

return_temp = ((temp*5)/1024)*3.2;

Siehe
http://www.mikrocontroller.net/articles/FAQ#Datentypen_in_Operationen
und denk darüber nach, wie sich das auf die Division durch 1024 bei dir 
auswirkt.

Generell: Du möchtest in einem Ausdruck Divisionen so weit wie möglich 
nach rechts schieben, so dass sie so spät wie möglich abgearbeitet 
werden.

von g457 (Gast)


Lesenswert?

Ein Fehler:

> return_temp = ((temp*5)/1024)*3.2;
                       ^
                        streiche "5", setze "5.0"

Alternativ kannst auch explizit casten.

HTH

von Andi (Gast)


Lesenswert?

erstmal danke daran hät ich noch ewig gesucht!

Karl heinz Buchegger schrieb:
> denk darüber nach, wie sich das auf die Division durch 1024 bei dir
> auswirkt.

also der Compiler macht eine Interger Division wodurch ich keine 
Nachkommastellen habe.


Jetzt geht die LED ab 10.03 V etwas Flakernt an ab 10.04V ganz an.
Das es nicht bei 10.01V angeht liegt denk ich an den Tolleranzen des 
Spannunsteiler, die Auflösung des ADC wären ja 4,88 mV.

Danke nochmal!

von Andi (Gast)


Lesenswert?

Hab noch eine Frage:

Wenn ich für die Rechnung alles mit Double mache funktioniert es.
Jetzt will ich aber nur mit Interger rechenen und das Ergebniss in mV.

Also ändere ich alle meine Datentypen in unsigned int und die
Rückrechnung:

return_temp = (temp*125)/8;

Wenn jetzt z.B. am ADC PIN 3 V anliegen:

ADC = 3V*1024 /5 = 614,4
müsste im ADC Register 614 stehen.
Wenn ich das nun oben als temp Auslese:

temp  = ADCL;
    temp += (ADCH<<8);
return_temp = (temp*125)/8 = 9593,75 mV
also liefert retun_temp 9593 zurück.

Jetzt lass ich in meiner main schleife vergleichen

V_quelle = get_Bat();
if (V_Quelle > 9000)
Do_LED2_On();

...aber die LED2 bleibt aus.
Warum?

Danke & Gruß Andi

von Karl H. (kbuchegg)


Lesenswert?

Andi schrieb:

> Also ändere ich alle meine Datentypen in unsigned int und die
> Rückrechnung:
>
> return_temp = (temp*125)/8;

Das ist nur die halbe Miete.
Du musst auch auf die Zahlenbereiche achten.


> ADC = 3V*1024 /5 = 614,4
> müsste im ADC Register 614 stehen.

OK.

>
> temp  = ADCL;
>     temp += (ADCH<<8);

Warum so kompliziert. Lass das doch den Compiler machen. Der weiß schon, 
wie man mit 16 BIt Registern umgeht:

    temp = ADCW;


> return_temp = (temp*125)/8 = 9593,75 mV

Nö.
Du hast das Zwischenergebnis vergessen

     614 * 125 = 76750

Mööp. 76750 ist zu groß für einen 16 Bit unsiged int. Bei 65535 ist 
Schluss. Darüber gibts einen Overflow.

Der Compiler wählt nicht nur die Art der Berechnung anhand der 
Datentypen der Operation aus, sondern auch in welchem Datentyp das 
Ergebnis vorliegen wird. Bei dir ist das alles unsigned int. Du hast 
also eine 16 Bit * 16 Bit Multiplikation, die ein 16 Bit Ergebnis 
bringen wird. Und dein Zwischenergebnis ist schlicht und ergreifend zu 
groß für 16 Bit.

> also liefert retun_temp 9593 zurück.

nicht ganz.
Es liefert 11214 / 8 = 1401

11214, weil die Multiplikation überlaäuft und du nicht 76750 sondern 
76750 - 65536 = 11214 bekommst

   return_temp = (temp * 125UL) / 8;

Jetzt müsste das richtige rauskommen. Durch die 125UL forciere ich eine 
Berechnung als unsigned long

von Andi (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> return_temp = (temp * 125UL) / 8;
> Jetzt müsste das richtige rauskommen. Durch die 125UL forciere ich eine
> Berechnung als unsigned long

Vielen dank so geht es.
Ich hatte mir selbst auch gedacht, dass das zwischen ergebniss zu groß 
ist.
Hatte es dann mit "uint32_t" als Typ probiert aber das ging nicht.
aber mit 125UL geht es.

Danke nochmal!

von Karl H. (kbuchegg)


Lesenswert?

Andi schrieb:

> Hatte es dann mit "uint32_t" als Typ probiert aber das ging nicht.
> aber mit 125UL geht es.

Doch, das geht genauso.
Nur wirst du es falsch gemacht habe :-)

Du musst temp auf uint32_t casten, BEVOR die Multiplikation stattfindet. 
Danach ist es zu spät

return_temp = ( (uint32_t)temp * 125) / 8;

von Andi (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Doch, das geht genauso.
> Nur wirst du es falsch gemacht habe :-)

ja ich hatte return_temp als uint32_t und temp normal unsigned int 
gelassen...;)

von Karl H. (kbuchegg)


Lesenswert?

Andi schrieb:
> Karl heinz Buchegger schrieb:
>> Doch, das geht genauso.
>> Nur wirst du es falsch gemacht habe :-)
>
> ja ich hatte return_temp als uint32_t und temp normal unsigned int
> gelassen...;)

:-)
dachte ich mir schon.
Also der klassische Fall aus der FAQ
1
Warum kriege ich bei
2
3
  double i = 5 / 8;
4
5
keine Nachkommastellen?
Gleiche Ursache :-)

Weil es für den Compiler irrelevant ist, was mit einem Ergebnis gemacht 
wird. Die Art und Weise wie eine bestimmte Operation durchgeführt wird 
(im Zahlenraum welchen Datentyps gerechnet wird) hängt einzig und 
alleine von den beteiligten Operanden ab und von sonst gar nichts.

  temp * 125
wird, so wie es da steht, im Zahlenraum unsigned int gerechnet. Selbst 
wenn du das Ergebnis in 128 Byte Fliesskommaformat-Variablen mit 
hyperbolischem Exponenten in trinärer Darstellung und gelbem Kommapunkt 
abspeicherst: Diese Multiplikation findet in unsigned int statt und in 
sonst nichts anderem. Da fährt die Eisenbahn drüber. Erst mit dem 
Ergebnis wird dann wieder etwas gemacht. Aber dann ist es zu spät sich 
über Overflows den Kopf zu zerbrechen.

Willst du das nicht, dann muss mindestens einer der beiden, entweder 
temp oder 125 oder alle beide, zu einem unsigned long werden. Nur die 
beiden Teilnehmer an der Operation sind relevant und steuern das.

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.