Forum: Mikrocontroller und Digitale Elektronik GCC C, falsche Umrechnung der ADC-Werte


von Frewer (Gast)


Lesenswert?

Hallo,
habe Probleme mit der Berechnung der Spannung aus dem ADC-Wert und zwar 
wie folgt:

int Berechne(void)
{
uint32_t volt;
uint16_t adc1val;

  adc1val = getadc(1);    // hole Akkuspg (ADCW)
  volt = ((488*adc1val)/100);  // berechne Akkuspannung in mV
}

An VRef liegt AVCC (5V) an. Damit berechnet sich aus einem Wandlerwert 
von 212 ein Spgsergebnis von 488*212/100 = 1034,56 als int also 1034mV. 
Tatsächlich bekomme ich einen Wert von 364mV angezeigt. Verändere ich 
die Zeile auf volt= 5*adc1val, dann funktioniert die Berechnung korrekt 
mit Ergebnis (1060mV). Wenn ich beim Simulieren mir den Rechenweg im 
Disassembler ansehe, dann wird dort nur mit 2 Byte gerechnet und nicht 
mit 4Byte (uint32_t), wobei das Ergebnis nach der Teilung durch 100 ja 
auch tatsächlich mit uint16_t auskommt.
Was mache ich falsch?? rechnet AVR-GCC grundsätzlich nicht mit mehr als 
2Byte?

Gruß Frewer

von Peter II (Gast)


Lesenswert?

Frewer schrieb:
> rechnet AVR-GCC grundsätzlich nicht mit mehr als
> 2Byte?

wenn nicht vorgegeben ist, rechnet er als int (also bei dir 16bit)

> Was mache ich falsch??
1
int Berechne(void)
2
{
3
uint32_t volt;
4
uint16_t adc1val;
5
6
  adc1val = getadc(1);    // hole Akkuspg (ADCW)
7
  volt = ((488*(uint32_t)adc1val)/100);  // berechne Akkuspannung in mV
8
}

so sollte es gehen.

von Georg G. (df2au)


Lesenswert?

Der Compiler rechnet mit Integer Werten, wenn du ihm nichts anderes 
explizit vorgibst.

488*adcval ergibt dann einen Überlauf.

Abhilfe: Mit long rechnen. Also (488L * adcval) / 100

von Dirk B. (dirkb2)


Lesenswert?

Georg G. schrieb:
> Abhilfe: Mit long rechnen. Also (488L * adcval) / 100

Wenn du bei unsigned long bleiben willst:
       (488UL * adcval) / 100

von Falk B. (falk)


Lesenswert?

@ Frewer (Gast)

>von 212 ein Spgsergebnis von 488*212/100 = 1034,56 als int also 1034mV.

625*212/128 = 1035

ist ein wenig schneller, denn /128 kann der Compiler meistens als 7 Bit 
Rechtsschieben umsetzen.

https://www.mikrocontroller.net/articles/Festkommaarithmetik#Die_L.C3.B6sung

von Peter II (Gast)


Lesenswert?

Falk B. schrieb:
> 625*212/128 = 1035
>
> ist ein wenig schneller, denn /128 kann der Compiler meistens als 7 Bit
> Rechtsschieben umsetzen.

könnte man nicht gleich

1250*212/256 = 1035

machen, dann muss gar nichts geschoben werden?

von Besserwisser (Gast)


Lesenswert?

Einfacher für den µC:
1
millivolt = (1250L * adc) / 256;  // 5000 / 1024 * 256 = 1250

von Besserwisser (Gast)


Lesenswert?

Haha, wir hatten den gleichen Gedankengang.
Oder 1251, dann zeigt er beim Anlegen von 5V  4,999 statt 4,995 an.

von Ralf G. (ralg)


Lesenswert?

... und wenn man sich über die benötigte/ erzielbare Genauigkeit 
Gedanken macht (vor allen Dingen die 'mV' ausschließlich für eine 
Anzeige verwendet), könnte man vielleicht auch innerhalb der 16Bit 
bleiben.

von Falk B. (falk)


Lesenswert?

@ Ralf G. (ralg)

>... und wenn man sich über die benötigte/ erzielbare Genauigkeit
>Gedanken macht (vor allen Dingen die 'mV' ausschließlich für eine
>Anzeige verwendet), könnte man vielleicht auch innerhalb der 16Bit
>bleiben.

Naja, das wir schon eher schwierig, vor allem wenn man das Ganze noch 
kalibrieren will. Bei einem 10 Bit ADC-Ergebnis bleiben nur 6 Bit für 
den Umrechnungsfaktor, das ist eine Auflösung von ~1,5%. Da kann man 
auch gleich nur mit 8 oder gar 7 Bit vom ADC rechnen, dann bleibt mehr 
für den Faktor und es stimmt wieder.

von Martin H. (horo)


Lesenswert?

Wie oft wird das gerechnet, dass ein paar ms störend sind?

von Werner F. (frewer)


Lesenswert?

Hallo,
vielen Dank für die vielfache Hilfe. Insbesondere die Thematik des 
erneuten Deklarierens von (uint32_t)adc1val bei der Berechnung habe ich 
so in der einschlägigen Literatur noch nicht gesehen. Der Wert in 
adc1val ist ja stets  kleiner als 2Byte, daher habe ich nie daran 
gedacht, dass für die Rechnung der Compiler adc1val als Zwischenspeicher 
benutzt. Jetzt ist das klar.

Deshalb nochmals vielen Dank für die Beratung!!!!!
Frewer

von Falk B. (falk)


Lesenswert?

@Werner Freytag (frewer)

>erneuten Deklarierens von (uint32_t)adc1val bei der Berechnung habe ich

Das nennt man einen "cast", also nur ein zeitweises Umdeklarieren auf 
einen neuen Variablentyp.

von Peter II (Gast)


Lesenswert?

Werner F. schrieb:
> dass für die Rechnung der Compiler adc1val als Zwischenspeicher
> benutzt

das macht er auch nicht. Aber er verwendet den Datentype von adc1val.

Sonst hätte ja adc1val danach einen andere Wert.

von (prx) A. K. (prx)


Lesenswert?

Werner F. schrieb:
> dass für die Rechnung der Compiler adc1val als Zwischenspeicher
> benutzt.

Das tut er auch nicht. Nur hängt die Breite einer Berechnung in C nicht 
vom Ergebnis ab, sondern nur von der Breite des grössten der beiden 
Operanden, mindestens aber "int". Folglich ist int*int ein int.

von Werner F. (frewer)


Lesenswert?

Hallo,

also das erste Problem ist gelöst. Mit dem neuen Wissen habe ich dann 
weitergearbeitet und hänge nun wieder an so einem Problem "integer 
overflow". Es soll die Akkukapazität ermittelt werden, indem alle Sek 
der Strom durch einen 10 Ohm Entlade-Widerstand gemessen wird. Die 
Berechnung ist (5V/1024)*(1000/(60*60)) = 25/18432 (Wandlung in mV, dann 
sek in h) und das Ganze dann noch /10 Ohm.
Wie sieht der Programmteil aus:

#define widvalue (resistor*18432)  // mit R = 10 Ohm
uint32_t kapazitaet;    // alle Sek wird adc1val addiert
uint32_t strom;

  strom = (((uint32_t)kapazitaet * 25)/(uint32_t)widvalue);

Für diese Zeile meldet der Compiler stets ein integer Overflow.
Warum?

Gruß
Frewer

von Peter II (Gast)


Lesenswert?

Werner F. schrieb:
> Für diese Zeile meldet der Compiler stets ein integer Overflow.
> Warum?

zeige mal die echte Fehlermeldung, ein Overflow kann der Compiler 
eigentlich nicht melden.

von Ralf G. (ralg)


Lesenswert?

Werner F. schrieb:
> Für diese Zeile meldet der Compiler stets ein integer Overflow.
> Warum?

Wie groß ist 'widevalue'? ... ... Richtig!

von Peter II (Gast)


Lesenswert?

Das wird die Ursache sein:

> #define widvalue (resistor*18432)

wie ist resistor definiert?

vermutlich hift:
1
#define widvalue (resistor*18432l)

von Dirk B. (dirkb2)


Lesenswert?

Wie ist resistor definiert?

Denn erst das Ergebnis von (resistor*18432) wird nach uint32_t gecastet.
aber da ist es schon zu spät

#define widvalue (resistor*18432L) // achte auf das L nach der 
Konstanten!

von (prx) A. K. (prx)


Lesenswert?

Peter II schrieb:
> #define widvalue (resistor*18432l)

Wer das so schreibt stellt sich selbst ein Bein. Besser:
  #define widvalue (resistor*18432L)

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Werner F. schrieb:
> #define widvalue (resistor*18432)  // mit R = 10 Ohm
> uint32_t kapazitaet;    // alle Sek wird adc1val addiert
> uint32_t strom;
>
>   strom = (((uint32_t)kapazitaet * 25)/(uint32_t)widvalue);

kapazitaet ist schon ein uint32_t. Das nochmal in den Typ zu casten, den 
es schon hat, bringt nix.

> Für diese Zeile meldet der Compiler stets ein integer Overflow.
> Warum?

Von welchem Typ ist denn resistor? Ich vermute, dass er nur 16 bit hat. 
Dann gibt es bei der Multiplikation 10*18432 einen Überlauf.

von Dirk B. (dirkb2)


Lesenswert?

Die Rechenregeln (Klammer- vor Punkt- vor Strichrechnung) sollten die 
bekannt sein.
Der C-Compiler zerlegt einen Term nach den Regeln in Teilausdrücke und 
berechnet sie jeweils mit der Genauigkeit der beteiligten Werte (aber 
mindestens als int).

zuerst wird ((uint32_t)kapazitaet * 25) berechnet und dann 
(uint32_t)(resistor*18432) Diese beiden Terme sind unabhängig 
voneinander.
Erst danch wird geteilt.

Beim letzten Ausdruck kommt der Fehler:
resistor ist ein int
18432l   ist ein int
-> int*int = int
Dann folgt erst der cast und aus dem int wird ein uint32_t

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
> Wer das so schreibt stellt sich selbst ein Bein.

Dirk B. schrieb:
> 18432l   ist ein int

q.e.d. :-)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@Werner Freytag (frewer)

>der Strom durch einen 10 Ohm Entlade-Widerstand gemessen wird. Die
>Berechnung ist (5V/1024)*(1000/(60*60)) = 25/18432 (Wandlung in mV, dann
>sek in h) und das Ganze dann noch /10 Ohm.
>Wie sieht der Programmteil aus:


>#define widvalue (resistor*18432)  // mit R = 10 Ohm
>uint32_t kapazitaet;    // alle Sek wird adc1val addiert
>uint32_t strom;

>  strom = (((uint32_t)kapazitaet * 25)/(uint32_t)widvalue);

Jaja, das liebe Chaos. Im Physikunterricht hätte man dir die Ohren 
langegzogen! Denn zu jeder Variable gehört auch eine Einheit! Welche 
Einheit hat deine Variable Kapazität? Ah? mAh mAs? Sowas gehört 
UNBEDINGT in die Kommentare, penible Menschen packen das sogar in den 
Variablennamen, wie z.b.

unsigned long Kapazitaet_mAs

Wenn man die Kapazität in mAs mißt und speichert, kommt man mit 32 Bit 
VERDAMMT weit, denn ~4e9 mAs sind 1,1e6 mAh. Eine große Monozelle (D) 
hat um die 20000 mAh, ein mittlerer Autoakku ca. 50000 mAh. Ergo 32 Bit 
mit mAs reichen.

von Peter II (Gast)


Lesenswert?

Falk B. schrieb:
> Wenn man die Kapazität in mAs mißt und speichert

die Frage ist ob man das überhaupt in einer "für Menschen gemachten" 
Einheit speichern muss.

Für die CPU ist es viel einfacher ein ADC * Takt zu rechnen. Also 
überhaupt nicht vorher umrechnen.

Für eine Anzeige kann man noch immer für den Menschen umrechnen.

von Pandur S. (jetztnicht)


Lesenswert?

Bist du sicher, dass du die geplante Rechnung haben musst ? Fuer eine 
Anzeige in mV ja, aber fuer eine Regelung nicht.

von Rolf M. (rmagnus)


Lesenswert?

Peter II schrieb:
> Für eine Anzeige kann man noch immer für den Menschen umrechnen.

Wo wir dann aber wieder bei dem obigen Problem sind. Wofür man es 
umrechnet, ist schließlich egal, wenn es darum geht, dass die Umrechnung 
aufgrund von Überläufen nicht richtig funktioniert.

von Werner F. (frewer)


Lesenswert?

An Falk Brunner: OK, OK, habe zwar nicht Physik studiert aber immerhin 
Elektronik. Allerdings zu Zeiten, da man noch mit Zuse und Lochkarten 
gelernt hat.

Nun zu den Fragen:
1. Die tatsächliche Warnung: "../Entlader.c:217: warning: integer 
overflow in expression"

2. widvalue ist eine Konstante definiert zu
#define widvalue (resistor*18432)  // mit R (resistor) = 10 Ohm
also  widvalue = 184320.
Gehe davon aus, dass der Compiler dieser Konstanten dann auch mindestens 
3 Byte spendiert.

3. Dirk B. schrieb:
> #define widvalue (resistor*18432L) // achte auf das L nach der
> Konstanten!

Das habe ich ergänzt und die Warnung ist weg. Jetzt bin ich gespannt, ob 
alles zusammen funktioniert.
Merci
Frewer

von Rolf M. (rmagnus)


Lesenswert?

Werner F. schrieb:
> 2. widvalue ist eine Konstante definiert zu
> #define widvalue (resistor*18432)  // mit R (resistor) = 10 Ohm
> also  widvalue = 184320.
> Gehe davon aus, dass der Compiler dieser Konstanten dann auch mindestens
> 3 Byte spendiert.

Nein, so funktioniert es nicht. widvalue ist das Ergebnis einer 
Berechnung. Und bei jeder Berechnung gilt, dass der Typ von den Typen 
der Operanden abhängt. resistor ist ein int, 18432 ist ein int, also 
wird auch das Ergebnis der Multiplikation der beiden in einen int 
gespeichert. Ob die Werte konstant sind oder nicht, spielt dabei keine 
Rolle.

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.