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,
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.