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
voidUpdateAdcChannels(void)
2
{
3
uint16_tc;
4
floaty=12.34;
5
uint16_tx=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.)
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.
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.)
@ 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
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 :-)
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.
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.htmlhttp://gcc.gnu.org/gcc-4.8/changes.htmlhttp://gcc.gnu.org/install/configure.html
Und eine Lookup-Tabelle gibt's auch nicht mehr:
http://gcc.gnu.org/PR29524
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.
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:
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);
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.
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.
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
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
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
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.
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.
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)?
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.
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.
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;
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
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).
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...
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.
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
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 :)