Hallo,
ich suche Anregungen und Ideen zu einer Codeoptimierung bezüglich einer
Division.
Ich messe an einem Motor eine Rotationsgeschwindigkeit einer Welle. Die
Drehzahlen können sehr hoch sein bis über 20000U/min, daher ist
Rechenzeit ein kostbares gut :-)
Mein Prozessor ist ein Atmega8 und läuft mit internem Oszi 8Mhz.
Zur Drehzahlerfassung läuft der 16-bit Timer ohne Prescaler (da er noch
andere Aufgaben erledigt, die µs genau zeitlich getimed sein sollten).
In dem 16-bit Timer errechne ich mir die Zeit von Signal zu Signal (1
Singal pro Umlauf), prinzipiell vereinfacht gesagt mal so:
Anzahl Timer Überläufe*65536+aktuellen Timerwert
Der Prozessor läuft 8 000 000. Die Berechnung der Drehzahl erfolgt nun:
Prozessortakt/gemessener Zeitwert
In den Prozessortakt rechne ich gleichzeitig den Faktor 60 min ein, um
von Umdrehungen pro Sekunde auf U/min zu kommen. Weiterhin skaliere ich
das ganze noch mit dem Faktor 100 herunter, um anstelle von 1000U/min
nur 10 zu erhalten, sodass ich in eine 8-bit Variable, bis 25600U/min
ablegen kann (entspricht dann 256).
Insgesamt sieht meine Rechnung also wie folgt aus:
32bit Variable aus der gemessenen Timer Rechnung (bei niedrigen
Drehzahlen, bei höheren würden 16-bit reichen) geteilt durch
(Prozessortakt*60/100).
Den Divisor habe ich bereits als eine konstante zusammgefasst: 8 000
000*60/100 = 4 800 000, die konstante ist logischerweise auch 32-bit
Eine Kürzung (cast) auf weniger bits wäre bei der Konstante problemlos
möglich, ergäbe aber bei niedrigen Drehzahlen eine ziemliche
Ungenauigkeit, wenn ich die Timerwerte ebenfalls herunter caste.
Lässt sich diese Rechnung noch weiter optimieren? Atmega8 mit
Hardwaremultiplizierer, allerdings keine Hardware Division. Alle
Rechnungen unsigned.
Kann mir jemand eine Schätzung geben, wie lange die Rechnung etwa auf
dem Mega8 dauern würde?
Danke.
Gruß
David
David schrieb:> Kann mir jemand eine Schätzung geben, wie lange die Rechnung etwa auf> dem Mega8 dauern würde?
Gehe in den Debugger und steuere dein Programm so, dass es auf die
Berechnung auflaufen wird.
Dann setzt in die Variablen die dich interessierenden Zahlenwerte ein
und achte im Debugger auf den Cycle-Count. Lass im Debugger die
Berechnung durchlaufen und sieh dir wieder den Cycle-Count an. Die
Differenz sagt dir, wieviele Zyklen verbraucht wurden. Und da der
Debugger auch deine Taktfrequenz kennt, ist unter dem Cycle-Count auch
dieselbe Angabe in Realzeit umgerechnet zu finden.
Also "interner Oszillator" und "genau" schließen sich gegenseitig schon
einmal aus. :D
Was dir vor allem fehlt ist eine Abschätzung, wie genau die Zeitmessung
wirklich sein muss (Fehlerrechnung). Man muss vorne sicher nicht 32 Bit
hineinstecken um hinten 8 Bit heraus zu bekommen.
Da du über weitere Anforderungen nichts geschrieben hast, gibt es wohl
auch keine. ;) Man könnte daher vielleicht eine andere Frequenz für den
Systemtakt wählen, so dass der Divisor (näherungsweise) 2^n ist, so dass
die Division per Shiften erledigt werden kann.
Dazu kommt dann noch:
Über einer bestimmten Grenzfrequenz schaltet man dann sowieso besser um.
Weg von Zeit zwischen Pulsen, hin zu: Wieviele Pulse gab es in einer
definierten Zeit.
Das hat neben der besseren Genauigkeit dann auch den schönen
Nebeneffekt, dass keine Division benötigt wird.
Detlev T. schrieb:> Was dir vor allem fehlt ist eine Abschätzung, wie genau die Zeitmessung>> wirklich sein muss (Fehlerrechnung). Man muss vorne sicher nicht 32 Bit>> hineinstecken um hinten 8 Bit heraus zu bekommen.
Wenn sich ein Bereich um die +/- 50 U/min bei der Zeitmessung ergibt,
wäre das ausreichend.
Da nach jedem Impuls bzw. jeder Wellenumdrehung die Drehzahl berechnet
wird und mit einer Sollvorgabe verglichen wird, folgt im Anschluss über
eine if-Abfrage, ob ein Grenzwert überschritten ist oder nicht, wenn ja,
tue etwas.
Daher kann ich nicht die Pulse zählen über einen Zeitraum, wirklich bei
jedem einzelnen Impuls entschieden wird, muss das Programm etwas regeln
oder nicht.
Detlev T. schrieb:> Also "interner Oszillator" und "genau" schließen sich gegenseitig schon>> einmal aus. :D
Das war mir etwas bewusst. Ein systematischer Fehler schadet jedoch
dabei nicht, solange alles verschoben ist. Ich dachte, der interne Oszi
macht mir letzendlich nur systematische Fehler (also eine Abweichung
imemr gleich), jedoch keine zufälligen. Liege ich da falsch?
David schrieb:> Ich dachte, der interne Oszi> macht mir letzendlich nur systematische Fehler (also eine Abweichung> imemr gleich), jedoch keine zufälligen. Liege ich da falsch?
Jain. Der interne Oszillator ist stark Temperaturabhängig. Besonders
beim Einschalten, oder wenn sich der Strom ändert (wenn z.B. eine LED
geschaltet wurde) gibt es doch recht schnelle Temperatur- und damit
Frequenzveränderungen.
Wenn der wert eh nur für ne if abfrage verwendet wird, könntest du doch
auch umgekehrt rechnen: timerwert < X ? tu irgendwas.
da das X sich warscheinlich selten ändert, darf die rechnung auch lange
dauern.
David schrieb:> Da nach jedem Impuls bzw. jeder Wellenumdrehung die Drehzahl berechnet> wird und mit einer Sollvorgabe verglichen wird, folgt im Anschluss über> eine if-Abfrage, ob ein Grenzwert überschritten ist oder nicht, wenn ja,> tue etwas.
Du könntest natürlich auch aus dem Sollwert die Zeit berechnen, die bei
einer Umdrehung nicht unterschritten werden darf und die dann mit dem
gemessenen Zeitwert vergleichen. Dann brauchst du gar nicht mehr
dividieren.
Ich verstehe das Problem nicht.
20000U/min sind rund 333 Ereignisse/s oder 3ms; das ist doch eher
langsam.
Eine 32 bit Division braucht etwa 20-30µs, was rund 1% von 3ms ist.
Ein Mega8 läuft auch gerne mit 16MHz, was seine Rechenzeit gegenüber
8MHz noch einmal verdoppelt.
Wo soll denn das Problem nun sein?
Hallo,
ich habe mich an Divisor = Nenner verstolpert.
1/4,8e6 sind etwa *179 / 2^33 , aber 4,8e6 steht ja im Zähler=
Dividend.
// http://de.wikipedia.org/wiki/Division_(Mathematik)
Wie Andreas und Detlev schrieben, es reicht auch mit einem Timerwert
passend zur Drehzahl zu vergleichen, den kann mal ja einmal vorher
bestimmen.
Die 20..30 µs wundern mich, ok bei 16 Mhz könnte es hinkommen.
Bei AVR Arithmetik saust die schnelle Assemblerversion
http://www.mikrocontroller.net/articles/AVR_Arithmetik#Division
32-Bit/32-Bit in maximal 431 Takten durch, bei 8 Mhz 54 µs.
Die normale in maximal 85 µs.
Falls man "sehr schnell" multiplizieren kann:
Es gibt eine Rechenvorschrift zur Bestimmung des Reziproken, wenn man
eine gute Näherung kennt.( ist das newton raphson bei AMD 3dnow ? )
Dazu müsste 1/x im Format 1.31 gespeichert sein und Du vielleicht alles
in Assembler schreiben.
Sei k bekannt und 1/k gesucht.
rez(0) ist eine erste Näherung
dann gilt
wiederhole
rez(n+1) = (2-rez(n)*k ) *rez(n)
bis passend genau.
Dieser rez-Wert muss anschliessend mit den 4,8e6 multipliziert werden.
Diese Vorschrift verdoppelt etwa mit jedem Durchlauf die
Genauigkeit/Stellenzahl, braucht aber 2 Multiplikationen und ein
Subtraktion.
Meine Überlegung ist, dass sich bei hohen Drehzahlen die Drehzahl
während einer Umdrehung nicht mehr so heftig ändert.
Wenn der Motor um 1% schneller wird, hat man nach einem Durchlauf der
Rechnung bei Benutzung des vorherigen Ergebnisses als Schätzwert rez(0)
nur eine Abweichung von (1%)^2 also 1e-4.
Also sei timer vorher für 19800 U/min passend und dann eine Umdrehung
später 20000 U/min, dann ist die Abweichung bei Dir etwa auf 2
Umdrehungen bei 20000 U/min.
Aber wozu das:
Man kann nicht schnell 32 x 1.31 rechnen.
Das dauert geschätze 4 x4 x 5..8(Mul Add Übertrag Schleife) 80..108
Takte und das mindestens 3 mal sind 300 Takte, dann ist eine Division
auch fast fertig und genau.
Da lohnt es sich, den Dividend = 4,8 Mio klein zu haben, je weniger Bit
desto besser.
Auf einem PC dauert die Multiplikation in der ALU/FPU einen Takt und die
Divison ~ 32, da lohnt es für manche Fälle,