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


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Frewer (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


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

von Besserwisser (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Wie oft wird das gerechnet, dass ein paar ms störend sind?

von Werner F. (frewer)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.