Forum: Compiler & IDEs Probleme mit Floating Point Rechnung (AVR-GCC)


von Jonas E. (gelatine)


Lesenswert?

Hallo,

ich sitze nun seit gestern an dem Problem und hab auch schon viel zu 
lang im Internet verbracht. Ich lese die internen AD-Wandler des AVR 
(Mega8) aus und muss dessen Counts in eine Spannung umrechnen. Diese 
muss dann über die Serielle an den PC gesendet werden, der sie dann 
weiterverarbeitet.

Das holen der Messwerte von den ADCs und dass versenden der float Zahl 
zum PC funktioniert einwandfrei. Die Floatzahl wird mit einer Union in 
ihre 4 Bytes zerlegt und dann über die Serielle versendet.

Nun zum eigentlichen Problem: der Umrechnung.

Hier ist der Code der eigentlich die Spannung umrechnen soll. Wenn ich 
statt der Umrechnung "c * 0.0048828125f" durch die auskommentierten 
Zeilen darunter ersetze funktioniert es einwandfrei.
1
void UpdateAdcChannels(void)
2
{
3
  uint16_t c;
4
  float y = 12.34;
5
  uint16_t x = 1234;
6
  
7
  for(i=0; i<4; i++)
8
  {
9
    c = GetAdcValue(i);
10
    AdcCountRegisters[i] = c;
11
    AdcDoubleRegisters[i].number = c * 0.0048828125f;
12
    //AdcDoubleRegisters[i].number = 2.54;
13
    //AdcDoubleRegisters[i].number = y;
14
    //AdcDoubleRegisters[i].number = x;
15
  }
16
}

Wenn ich jetzt aber das hier schreibe:
1
AdcDoubleRegisters[i].number = i * 2.0f;
dann funktioniert es auch nicht.

Irgendwie scheint der AVR-GCC ein Problem mit der Umwandlung von 
Ganzzahltypen in eine Floatzahl zu haben. Muss ich hier noch eine 
Compiler/Linker Option einfügen?
Ich hab zwar was gefunden, dass war aber immer nur für die sprintf 
Funktion, wenn man Floatwerte darstellen wollte.

Die Union für das Feld "AdcDoubleRegisters" sieht so aus:
(Das Overlay mit dem Integerfeld war nur ein Test und ist nicht 
relevant.)
1
typedef union
2
{
3
  volatile float number;
4
  volatile uint8_t byte[4];
5
  volatile uint16_t word[2];
6
} OverlayFloat;

Ich hoffe ihr könnt mit helfen.

Grüße, Jonas

von Michael (Gast)


Lesenswert?

Jonas Eberhard schrieb:
> typedef union
> {
>   volatile float number;
>   volatile uint8_t byte[4];
>   volatile uint16_t word[2];
> } OverlayFloat;

Na hoffentlich verwenden PC und µC bei Floats die selbe Bytereihenfolge.

von Dr. Sommer (Gast)


Lesenswert?

Ohne auf deinen Code einzugehen, warum bei einem 10 Bit ADC eine 
Floatzahl
wenn die Umrechnung eh auf dem PC stattfindet?

von Frank K. (fchk)


Lesenswert?

Ja, jetzt weißt Du auch, warum es Referenzspannungsquellen mit 4.096V 
gibt.

http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en019719

Denk mal drüber nach.

fchk

von Klaus W. (mfgkw)


Lesenswert?

Du hast die Option -lm angegeben bzw. irgendwo ein Häkchen gemacht, wo 
"use math library" oder sowas ähnliches dransteht?

Wenn nicht, kann der gcc nur Gleitkommazahlen nutzen, die er bereits 
beim Kompilieren kennt.

Weil Gleitkommazahlen viel Code erzeugen, den man auf einem 
Mikrocontroller normalerweise nicht braucht, wird er standardmäßig 
weggelassen.
Der Compiler selbst kann natürlich trotzdem mit Gleitkommazahlen 
umgehen, deshalb klappt es mit deinen Konstanten und scheitert erst, 
wenn zur Laufzeit gerechnet werden muß.

(Inwiefern man wirklich auf einem Controller für sowas Gleitkommazahlen 
braucht, steht auf einem anderen Blatt. Aber solange man nicht an 
mangelnder Rechenzeit oder der Programmgröße scheitert, steht es jedem 
frei, die Resourcen "sinnlos" mit Gleitkommarechnung zu verbrauchen oder 
brach liegen zu lassen.)

von Jonas E. (gelatine)


Lesenswert?

@ Michael:
Berechtigte Frage, ja der PC nutzt das gleich Format wie der AVR-GCC: 
IEEE 754.

@ Dr.Sommer:
Naja nicht ganz, der PC verarbeitet die Spannung weiter, die Umrechnung 
muss auf dem AVR stattfinden, da der PC schon eine fertige Floatzahl 
erwartet. Das ist der PC-Software geschuldet.

@ Frank:
Das ändert leider nichts daran, dass ich die ADC Counts in eine 
Kommazahl umrechnen muss. Sicher, die Umrechnung wäre ein wenig 
einfacher, einfach mal 0,004.
Leider scheitert es ja genau daran. Das will er partu nicht machen.


Sobald ich eine Ganzzahl in eine Floatzahl umrechnen (egal ob mit oder 
ohne Faktor) will, kommt Mist raus.
Wie ich oben geschrieben habe, funktioniert selbst dass hier nicht.
1
c = GetAdcValue(i);     //Hier kommt eine ganze Zahl raus
2
Floatwert = c;          //Hier sollte er ja nun die ganze Zahl in die Floatvariable kopieren

von Klaus W. (mfgkw)


Lesenswert?

Jonas Eberhard schrieb:
> @ Michael:
> Berechtigte Frage, ja der PC nutzt das gleich Format wie der AVR-GCC:
> IEEE 754.

IEEE 754 sagt aber nicht, in welcher Reihenfolge die 4 Bytes im Speicher 
liegen.

Bei der Kombination mit Intel-PC und AVR klappt es zufällig :-)

von Stefan E. (sternst)


Lesenswert?

Klaus Wachtler schrieb:
> Du hast die Option -lm angegeben bzw. irgendwo ein Häkchen gemacht, wo
> "use math library" oder sowas ähnliches dransteht?
>
> Wenn nicht, kann der gcc nur Gleitkommazahlen nutzen, die er bereits
> beim Kompilieren kennt.
>
> ...
> Der Compiler selbst kann natürlich trotzdem mit Gleitkommazahlen
> umgehen, deshalb klappt es mit deinen Konstanten und scheitert erst,
> wenn zur Laufzeit gerechnet werden muß.

Sorry Klaus, aber das ist natürlich Quatsch. Der GCC bringt generische 
Funktionen mit, libm.a ersetzt diese lediglich durch AVR optimierte. 
Außerdem, würden notwendige Float-Funktionen fehlen, dann würde es 
natürlich einen Fehler beim Linken geben.

Prinzipiell sollte es also auch ohne -lm gehen. Allerdings verbrauchen 
diese generischen Funktionen reichlich RAM (insb. durch eine 
LookUp-Tabelle), so dass je nach µC (hat der OP ja nicht genannt) 
Probleme entstehen.

-lm zu verwenden ist natürlich auf jeden Fall empfehlenswert, und könnte 
hier auch das Problem lösen, aber zu behaupten, ohne dem könnte gar 
nicht zur Laufzeit mit float gerechnet werden, ist Quatsch.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> Sorry Klaus, aber das ist natürlich Quatsch. Der GCC bringt generische
> Funktionen mit, libm.a ersetzt diese lediglich durch AVR optimierte.

Bei allen unterstützen Versionen von avr-gcc, also 4.7 aufwärts, ist das 
nicht mehr so. Voraussetzung: Der Compiler muss richtig configured sein, 
nämlich mit --with-avrlibc.  Damit sind dann die float-Funktionen, die 
die libm "überschreiben" will, nicht in der libgcc enthalten und...

> Prinzipiell sollte es also auch ohne -lm gehen. Allerdings verbrauchen
> diese generischen Funktionen reichlich RAM (insb. durch eine
> LookUp-Tabelle), so dass je nach µC (hat der OP ja nicht genannt)
> Probleme entstehen.

...ausserden setzen die Specs ein -lm, denn mit --with-avrlibc ist die 
libm so zu behandeln wie die libgcc!

http://gcc.gnu.org/PR54461

bzw. suche nach "AVR" und / oder --with-avrlibc in:

http://gcc.gnu.org/gcc-4.7/changes.html
http://gcc.gnu.org/gcc-4.8/changes.html
http://gcc.gnu.org/install/configure.html

Und eine Lookup-Tabelle gibt's auch nicht mehr:

http://gcc.gnu.org/PR29524

von Stefan E. (sternst)


Lesenswert?

Da war ich in der Tat bezüglich -lm nicht auf dem aktuellen Stand.

Aber die von Klaus gemachte Aussage "kein -lm => Float-Operationen zur 
Laufzeit funktionieren nicht" ist dennoch nicht richtig. Denn wenn bei 
einer Version >=4.7 (also keine generischen Funktionen vorhanden) 
tatsächlich kein -lm zum Einsatz kommt (z.B. Linker direkt aufgerufen 
statt über gcc-Frontend), dann wird das Linken ja wohl kaum erfolgreich 
verlaufen.

von Jonas E. (gelatine)


Lesenswert?

Jetzt bin ich langsam verwirrt. Muss jetzt ein "-lm" hinten dran oder 
nicht?
Ich verwende WinAvr (die letzte Version). Da ist der avr-gcc 4.3.3 drin.

Das hier ist der Output, wenn ich mein Projekt compilieren lasse:
1
Compiling C: main.c
2
avr-gcc -c -mmcu=atmega8 -I. -gdwarf-2 -DF_CPU=12000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main.lst  -std=gnu99 -MMD -MP -MF .dep/main.o.d main.c -o main.o 
3
main.c: In function 'UpdateAdcChannels':
4
main.c:428: warning: unused variable 'x'
5
main.c:427: warning: unused variable 'y'
6
7
Linking: main.elf
8
avr-gcc -mmcu=atmega8 -I. -gdwarf-2 -DF_CPU=12000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=main.o  -std=gnu99 -MMD -MP -MF .dep/main.elf.d main.o snap.o edm.o --output main.elf -Wl,-Map=main.map,--cref     -lm
9
10
Creating load file for Flash: main.hex
11
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock main.elf main.hex
12
13
Creating load file for EEPROM: main.eep
14
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" \
15
  --change-section-lma .eeprom=0 --no-change-warnings -O ihex main.elf main.eep || exit 0
16
17
Creating Extended Listing: main.lss
18
avr-objdump -h -S -z main.elf > main.lss
19
20
Creating Symbol Table: main.sym
21
avr-nm -n main.elf > main.sym

Beim Linker findet sich ein "-lm" ganz am Ende. Ich verwende immer den 
orginal Make-File und passe ihn nur immer für meine Projekte an.

von ich (Gast)


Lesenswert?

Du multiplizierst ein unsigned int mit einem float.
Ohne weiter darüber nachgedacht zu haben, must du vermutlich das
Ergebnis der Rechnung auf float casten.

unsigned int c = 1;
float f=3.14159;
float r = (float)(c*f);

von Peter II (Gast)


Lesenswert?

ich schrieb:
> Du multiplizierst ein unsigned int mit einem float.
> Ohne weiter darüber nachgedacht zu haben, must du vermutlich das
> Ergebnis der Rechnung auf float casten.

nein muss er nicht.

von Jonas E. (gelatine)


Lesenswert?

Mit Cast hatte ich es vorher auch schon ausprobiert :)
Hatte schon das hier, besteht quasi nur aus Casts (Wobei die vor den 
Zahlen sinnlos sind.):
1
(float)(((float)c * (float)5.0) / (float)1024.0)

von edgar (Gast)


Lesenswert?

Etwas komisch ist, dass die Konstanten, die angeblich funktionieren kein 
f hinten haben und die Anderen schon.

Ich glaubs zwar nicht, aber steht da irgendwo ein #define f rum ?

Und generier mal asm code und schau ob die Werte ok sind.

von Andreas B. (andreasb)


Lesenswert?

Alignment ist nicht angegeben...

Schön mal darüber nachgedacht?

http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Type-Attributes.html

Könnte ein Problem sein, muss aber nicht, normalerweise nur bei struct 
ein Thema, bin mir nicht sicher obs bei union auch relevant ist...


mfg Andreas

von Peter II (Gast)


Lesenswert?

Andreas B. schrieb:
> Alignment ist nicht angegeben...

auf einen 8bitter ist doch alles alignt.

von Andreas B. (andreasb)


Lesenswert?

Peter II schrieb:
> Andreas B. schrieb:
>> Alignment ist nicht angegeben...
>
> auf einen 8bitter ist doch alles alignt.

Ja, aber aber er will doch das ganze wider am PC einlesen, oder nicht?

Und wenn dann auf 32 / 64 bit aligned ist dann könnte es Probleme 
geben...

mfg Andras

von Klaus (Gast)


Lesenswert?

Andreas B. schrieb:
> Und wenn dann auf 32 / 64 bit aligned ist dann könnte es Probleme
> geben...

oder wenn auf dem PC, weil er im 64 bit Mode ist, statt float 
stillschweigend double genommen wird.

MfG Klaus

von Jonas E. (gelatine)


Lesenswert?

Das übertragen auf den PC funktioniert ja, es ist nur die Floatingpoint 
Mathematik die nicht funktioniert.

Ich hab mich jetzt grad mal versucht in das Alignment einzulesen, komme 
aber grad nicht so recht mit.
Was macht das ganze genau? So weit ich verstanden hab, kann ich damit 
irgendwie abgeben, dass die Adressen der Variablen durch die angegebene 
Zahl teilbar sein muss?

Auf dem PC wird ein Float (4 Byte) entgegen genommen und dort wieder 
zusammengesetzt.

von Klaus W. (mfgkw)


Lesenswert?

Jonas Eberhard schrieb:
> Ich hab mich jetzt grad mal versucht in das Alignment einzulesen, komme
> aber grad nicht so recht mit.

Brauchst du irgendwann, hat aber mit diesem Problem nichts zu tun.

> Was macht das ganze genau? So weit ich verstanden hab, kann ich damit
> irgendwie abgeben, dass die Adressen der Variablen durch die angegebene
> Zahl teilbar sein muss?

Ja.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jonas Eberhard schrieb:
> Wenn ich jetzt aber das hier schreibe:
>
> AdcDoubleRegisters[i].number = i * 2.0f;
>
> dann funktioniert es auch nicht.

Wie stellst du fest, was die berechneten Ergebnisse sind, und wie lauten 
diese Ergebnisse (für i=0..3)?

von Jonas E. (gelatine)


Lesenswert?

Yalu X. schrieb:
> Wie stellst du fest, was die berechneten Ergebnisse sind, und wie lauten
> diese Ergebnisse (für i=0..3)?

Ich übertrag nach jedem Durchgang die Daten (Bytes der Floatzahlen) an 
den PC und setze sie da zusammen.

Die Ergebnisse sind von Durchganz zu Durchgang sogar unterschiedlich und 
liegen meißt im tausender bis millionen Bereich.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jonas Eberhard schrieb:
> Ich übertrag nach jedem Durchgang die Daten (Bytes der Floatzahlen) an
> den PC und setze sie da zusammen.

Dann kann der Fehler aber doch sehr wohl auch auf der PC-Seite liegen.

von Jonas E. (gelatine)


Lesenswert?

Wenn ich aber die Floatvariable mit einer Konstante einfach fülle, dann 
funktioniert es auf der PC-Seite (das Zusammensetzten) ohne Probleme.

z.B. sowas hier: AdcDoubleRegisters[i].number = 2.54f;

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wenn das Problem auf der µC-seitetig ist, dann solltest du doch 
einen AVR-Testfall schreiben können, der von anderen nachvollziehbar 
ist.

von Frank K. (fchk)


Lesenswert?

Jonas Eberhard schrieb:

> @ Frank:
> Das ändert leider nichts daran, dass ich die ADC Counts in eine
> Kommazahl umrechnen muss. Sicher, die Umrechnung wäre ein wenig
> einfacher, einfach mal 0,004.
> Leider scheitert es ja genau daran. Das will er partu nicht machen.

Warum rechnest Du nicht in mV? Dann wäre ein ADC Count 4mV, und zwar 
exakt. Dafür braucht man dann kein float mehr, das kannst Du problemlos 
als long verarbeiten.

Vorteil: viel schneller, viel weniger Code, viel weniger Probleme. Genau 
deshalb machen schlaue Leute das üblicherweise genau so. Bei einem 12 
Bit ADC ist dann ein ADC Count exakt 1mV bei Vref=4.096V.

fchk

von Jonas E. (gelatine)


Lesenswert?

Das Problem ist, dass die eingesetzte Hardware schon feststeht und die 
Software eine Floatzahl haben will. Dan muss ich auch wenn ich mit 
Integerwerten und in mV rechne das ganze immernoch irgendwie in eine 
Floatzahl bekommen.
Und da das ganze nicht sehr zeitkritisch ist und ich auch mehr als genug 
Speicher in meinem ATMega8 habe, sollte das eben mit Floatingpoint 
berechnet werden.

Hat denn noch einer 'ne Idee warum der GCC bei mir keine Floatingpoint 
Mathematik macht?

Das -lm Flag beim Linker/Compiler ist angegeben (siehe Post weiter 
oben).

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jonas Eberhard schrieb:
> Das Problem ist, dass die eingesetzte Hardware schon feststeht und die
> Software eine Floatzahl haben will. Dan muss ich auch wenn ich mit
> Integerwerten und in mV rechne das ganze immernoch irgendwie in eine
> Floatzahl bekommen.
> Und da das ganze nicht sehr zeitkritisch ist und ich auch mehr als genug
> Speicher in meinem ATMega8 habe, sollte das eben mit Floatingpoint
> berechnet werden.
>
> Hat denn noch einer 'ne Idee warum der GCC bei mir keine Floatingpoint
> Mathematik macht?

Nein. Du hast ja noch nichtmal einen Testfall geliegert, der das Problem 
produziert. All dein Code von oben hat so viele Dinge, die undefiniert 
sind, daß man anfangen muß zu reten. Und die Kristallkugel hat wie immer 
eine seeehr kleine Trefferquote.

Dein Code von oben

> AdcDoubleRegisters[i].number = i * 2.0f;

also eine Multiplikation mit 2, funktioniert wie sie soll und liefert 
korrekte Ergebnisse. Der erzeugte Assembler-Code ist auch wie erwartet 
mit WinAVR-20100110 und auch anderen Versionen der Tools.

Allerdings ist dieser Code kein compilierbares oder gar ablaufbares 
Stück C-Code.

Ergo: Dein Problem liegt komplett woanners, etwa im Stack-Überlauf, 
fehlerhafter Übertragung, instabiler Clock, falschen UART-Einstellungen, 
zu langsamem PC, weiß-der-Teufel...

von Malte S. (maltest)


Lesenswert?

A propos UART. Wie synchronisierst du die Übertragung des ersten Byte? 
Und die Software am PC lässt sich nicht andern, dass sie ohne float 
auskommt? Sollte doch kein allzu großer Eingriff dafür nötig sein.
Float in the wire ist...suboptimal.

von Jonas E. (gelatine)


Angehängte Dateien:

Lesenswert?

Soo, das ganze lag mal wieder nicht am AVR-GCC sondern am Programmierer.

Nachdem ich mir dann eine Testfirmware geschrieben hatte, hab ich 
gemerkt, dass der Compiler alles richtig rechnet, so wieder soll.
(In Anhang ist mein Testprogramm.)

Das Problem lag letzten Endes weder im Float->Byte noch in der 
Übertragung, noch im Zusammensetzten am PC. Das hat ja mit Konstanten 
alles schon funktioniert.

Es lag an einem falsch erhöhten Index des Float Feldes beim Senden. 
grrr

Naja so kanns gehn, danke an alle die mir helfen wollten.

Und an Malte: Es wird ModBus als Protokoll verwendet.
Wenn man sich so die ganzen Industriellen Sensoren anschaut, dann ist 
eine Floatzahl zu übertragen relativ normal. Dort steht die Floatzahl 
einfach in zwei Registern, welche dann ausgelesen werden.
Das hab ich in der Ausbildung mit SPSen auch schon so gelernt.


Grüße,
Jonas

von Malte S. (maltest)


Lesenswert?

Danke für die Info. Ja, ich war eher aus Netzwerksicht daran gegangen. 
Da gibt es zwar auch etliche Protokolle, die sowas machen, aber in aller 
Regel wird das vermieden.
Aber sei's drum. Wenn es in dem Umfeld normal ist, ist es normal :)

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.