PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Float verhindern



Thalhammer
16.03.2014, 14:12
Hallo,
ich habe folgendes Problem:
Ich bekomme Werte vom ADC(12bit), diese werden dann auf einen bestimmten bereich beschränkt (Da die Potis die Maximalwerte nicht erreichen) und ein offset abgezogen, sodass dann werte zwischen 0 und ca. 2900 (je nach kanal unterschiedlich).
Nun möchte ich die werte in dem Bereich in den Bereich 0-1023 (10bit) Packen.
Die erste Idee war (natürlich) den Wert einfach mit einem float zu multiplizieren.
Allerdings hab ich immer jede menge geschichten gehört das float auf avrs so schlimm ist und so weiter.
Leider hab ich da keine Erfahrungswerte weil ich noch nie Floats auf nem AVR gebraucht hab.
Daher nun meine Frage: sind die wirklich so schlimm ?
Und wie lange (in takten) würd eine die Multiplikation einer 16 bit unsigned zahl mit einem float ungefähr dauern ?
Meine Zweite idee war dann (da ich die 16 ja eh nicht ausschöpfe) die zahl erst per multiplikation mit einem 8bit auf die vollen 16bit zu erweitern und dann per rightshift in die 10 bit zu rücken.
Allerdings kommt da bei meinem beispiel (maximalwert 2900, multi= 2^16/2900=22) ein maximalwert für die 10bit zahl von 996 raus, was nur 97% des werte bereichs entspricht.
Is es irgendwie möglich (mindestens) 99% genauigkeit hinzukriegen ohne das es zu rechenaufwendig wird ?

MFG Thalhammer

Besserwessi
16.03.2014, 15:08
So schlimm sind Fließkommazahlen auch nicht. Wie lange es braucht, könnte man ggf. im Simulator probieren. Meine Schätzung liegt so bei vielleicht 100-500 Zyklen für das Umwandeln und Multiplizieren. Es hängt aber auch vom µC ab (ein Tiny ist da langsamer). Das 2. Problem ist ggf, das man einmal einiges an Code-größe dazu bekommt.

Die Idee mit der Multiplication und Shifts ist schon richtig. So wie es aussieht müsste man aber wohl schon auf unsigned long, also einen 32 Bit Datentypen gehen (einen 24 Bit Datentypen unterstützt C nicht direkt, nur die Fließkomma Zahlen sind 24 Bit + Exponent). Das sollte immer noch etwas schneller als mit Fließkommazahlen sein. Bei den 32 Bit Zahlen kann man sich dann ggf. beim Shiften einiges Sparen und das Ergebnis aus den oberen 16 Bit nehmen.

Thalhammer
16.03.2014, 17:51
Also ich hab in der zwischenzeit bisl gegooglet und wies ausschaut ist der UNterschied schon kraß:
Hier :http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=72923&start=0
hat einer ein paar tests mit unterschiedlichen Datentypen gemacht und anscheinend hat da ein float schon enorme nachteile und schlägt offensichtlich mit 1876 takten zu buche (vs 86 bei nem 32bit integer).
Also ich denke das da meine Lösung deutlich besser abschneidet:


#define MAX 3750
#define MIN 850
#define MULTI 4294967296/(RV_MAX-RV_MIN)

static inline uint16_t to10bit(uint16_t val)
{
if(val<MIN) val=0;
else if(val>MAX) val=max-min;
else val=val-min;
uint32_t t=val*multi;
t=t>>22;
return (uint16_t)t;
}



Ich weis zwar nicht genau wieviel takte das braucht aber weniger als 1800 sinds sicher :-)
und die genauigkeit ist auf jedenfall ausreichend.

PS: Mir is klar das man das noch optimieren kann (max-min vorher ausrechnen) aber ich vertraue da in den Compiler da es ja geinlined wird und bereits zur compilezeit bekannt ist.

MFG Thalhammer

Peter(TOO)
16.03.2014, 19:00
Hallo Thalhammer,

Das geht ganz einfach übers Bruchrechnen.

x = (n*k1)/k2;

Anstatt mit
x = n*0.75;
rechnest du
x = (n*3)/4;
3/4 = 0.75

Du musst nur erst nachsehen, mit welcher Genauigkeit dein C Zwischenberechnungen ausführt Meist sind es 32-Bit, das Zwischenresultat darf nicht grösser werden. Hinzu kommt noch, dass die entsprechenden Bibliotheken Platz benötigen.
In meinen MicroController-Projekten der letzten 35 Jahre, findest du nirgends FP-Arithmetik. Allerdings kann es sein, dass z.B. in hundertstel Grad gerechnet wird. Falls nötig, wird dann das Komma bei der Ausgabe "dran gefummelt".

MfG Peter(TOO)


Praktisch kannst du damit rechnen, dass Floating-Point-Emulation so in der Grössenordnung Faktor 100 langsamer als Integer ist.

BMS
16.03.2014, 19:34
Hallo,
das "Bruchrechnen" habe ich in meinen Projekten auch verwendet. Der Bruch sollte dabei so weit wie möglich gekürzt werden. Auf möglichen Überlauf hat Peter(TOO) bereits hingewiesen. Zudem muss auch beim Teilen durch große Zahlen beachtet werden, dass aufgrund des ganzzahligen Teilens ohne Rest Genauigkeit verloren gehen kann.

22 Shifts im obigen Code brauchen ohne Barrel Shifter auch viele Takte. Das Schieben kann auch auf das nächste Vielfache von 8 Bit geschehen, der Zugriff kann dann auf die einzelnen Bytes erfolgen:


//Vorraussetzung: Little Endian
lsbyte0=*(((uint8_t*)(&variablemit32bit))+0);
byte1=*(((uint8_t*)(&variablemit32bit))+1);
byte2=*(((uint8_t*)(&variablemit32bit))+2);
msbyte3=*(((uint8_t*)(&variablemit32bit))+3);

Grüße, Bernhard

Peter(TOO)
16.03.2014, 20:29
Hallo Bernhard,

Zudem muss auch beim Teilen durch große Zahlen beachtet werden, dass aufgrund des ganzzahligen Teilens ohne Rest Genauigkeit verloren gehen kann.
Da muss man eben optimieren. Den Bruch so klein wie möglich, aber so gross, dass man die benötigte Genauigkeit noch erreicht!
Aber gefragt wurde nach einem Fehler im Bereich von 1% und das ist eigentlich kein Problem.

22 Shifts im obigen Code brauchen ohne Barrel Shifter auch viele Takte. Das Schieben kann auch auf das nächste Vielfache von 8 Bit geschehen, der Zugriff kann dann auf die einzelnen Bytes erfolgen.
Das ist im Allgemeinen aber der Job des Compilerherstellers, dass er optimalen Code erzeugt.

Bei manchen CPUs ist eine Integermultiplikation schneller als mehrere Left-Shifts.

MfG Peter(TOO)

robin
16.03.2014, 20:42
Zum weiteren Optimieren, sollte man auch noch versuchen den Nenner als eine 2er Potenz zu wählen.

Der AVR kann zwar multiplizieren, zum Dividieren gibt es aber keinen Befehl. Hier muss also emuliert werden, außer es handelt sich um eine 2er Potenz, dann kann man das Ergebnis einfach nach rechts schieben.