Hallo,
in meiner Sinus-Motoransteuerung muss ich den PWM Tastgrad laufend
ändern. Meine Berechnung ist aber zu langsam, als dass ich schnelle
Drehzahlen hin bekomme. Mein XMEGA16E5 läuft schon mit 34MHz...
Hat jemand eine Idee, wie ich die Berechnung schneller durchführen
könnte?
Die variable pwm_output hat werte zwischen 0-1000 (entspricht 0-100%,
also Promille)
In der Sinustabelle sinTable liegen 360 Werte zwischen 0 und 8000. In
das PWM Register TCC4_CCABUF müssen dann Werte zwischen 0 und 800. Für
0% bis 100%.
Im Moment ists wie unten gelöst. Dauert aber sehr lange (rund 32µs).
Jede Division braucht ca. 6µs.
Wie könnte man das schneller machen?
Gruß,
Michael
Michael schrieb:> Wie könnte man das schneller machen?
Die Division in Assembler schreiben und aufrollen (ohne
Schleife laufen lassen). Dabei fällt vielleicht noch etwas
Optimierung ab wenn ein paar Register nicht gesichert/wieder-
hergestllt werden müssen.
Oder einen 32 Bit ARM nehmen, da bist du solche Sorgen los.
Das sich deine Drehzahl ja wohl nicht von einer µs auf die andere
ändert:
Was spricht dagegen, sich eine für die jeweilige Drehzahl exakt passende
Tabelle zurechtzulegen.
Wird die Drehzahl geändert, wird einmalig eine neue passende Tabelle
zurecht gelegt. Die ganze Rechnerei für die TCCRxxx Register fällt dann
weg.
Fasst man deine beiden Formeln zu einer zusammen, dann ergibt sich:
1
value=sine_value*pwm_output/1000;
schon mal 1 Division weniger, zugunsten einer Multiplikation (welche
meist schneller ist).
Die DIvisionn durch 1000 lässt sich auch noch vereinfachen, wenn du
deine Promille aufgibst und sie durch eine 2-er Potenz ersetzt.
Was für uns Zahlen im 10-er System sind, sind für einen Computer Zahlen
zur Basis 2: sie ermöglichen so manche Abkürzung im Rechenweg.
'Dividierst' du im 10-er System 5678 durch 100 ohne zu rechnen, sondern
nur indem du 'das Komma verschiebst' und 56 erhältst, so tut sich auch
ein Computer ohne Divisionshardware leichter, wenn er durch 2-er
Potenzen 'dividieren' darf. Dann muss nichts gerechnet werden, sondern
es wird nur die Bitdarstellung entsprechend verschoben (was effektiv
einem Verschieben des Binär-Kommas gleich kommt)
Michael schrieb:> Wie könnte man das schneller machen?
Die Divisionen durch eine Multiplikation ersetzen...
1
u16x=(10000/w);
2
3
r=(Table[y])/x;
Ist das selbe wie:
1
r=(Table[y])*w/10000;
Und wenn jetzt noch die Skalierung in die Tabelle aufgenommen wird
(10000 fach kleinere Werte in der Tabelle), oder "shiftfreundliche"
Zahlen verwendet werden (z.B. 8192 oder 16384 statt 10000), dann geht
das recht knackig...
BTW1: besser könnte man es machen, wenn die ganzen Unterstriche weg
wären. Die machen m.E. jeden Quelltext zum "Qualtext", weil man immer
erst mal schauen muss, ob das Wort vorbei ist oder ob noch ein
Unterstrich kommt:
Karl Heinz schrieb:> Was spricht dagegen, sich eine für die jeweilige Drehzahl exakt passende> Tabelle zurechtzulegen.
Eventuell der zu kleine Speicher.....
Wie geschon gesagt, besser auf 2-er Potenzen skalieren. Die Division
wird dann zu nem simplen shift-right und es gibt gar keine Divison mehr.
Die 16384 hab ich gewählt, damit die Werte noch in 16 Bit passen.
Und btw, wie du evtl schon gemerkt hast, zu lange Namen (und zu viele
Klammern) sind kontraproduktiv - in der Kürze liegt die Würze ;-)
isidor schrieb:> Schön geschrieben, aber gerade die Funktionsaufrufe vergeuden> wieder wertvolle Zeit mit Springen, Register Retten/Restaurieren> und Parameter-Übergabe (kommt drauf an...)
Yep.
Zm Beispiel davon, ob der Compiler die Funktion inlined.
EIn Blick ins Assembler Listing schafft da Klarheit.
> aber gerade die Funktionsaufrufe vergeuden wieder wertvolle Zeit ...
Das sind Mikrooptimierungen, mit denen man sich erst beschäftigt, wenn
es überhaupt nicht mehr anders geht. Und selbst dann, schaut man
erstmal, ob der Compiler das nicht eh inlined hat. Wenn nicht, macht
man aus der Funktion eine inline-Funktion. Und wenn das dann immer noch
zu langsam ist, dann nimmt man einen schnelleren Proz ;-)
foobar schrieb:> Das sind Mikrooptimierungen, mit denen man sich erst beschäftigt, wenn> es überhaupt nicht mehr anders geht.
Je nach Gesamtlage fällt bei Sichern/Restaurieren von Registern
einiges an Arbeit für den Prozessor an .....
isidor schrieb:> Karl Heinz schrieb:>> ob der Compiler die Funktion inlined.>> Wenn man es dem Compiler nicht sagt (das inlinen) wird er es nicht> machen.
Das würde ich mal als kühne Behauptung auffassen.
Compiler optimieren eine Menge Dinge, wenn man nur den Optimizer
einschaltet und ohne das man es ihnen sagt. Vor allen Dingen wenn die
Funktion static ist und sie daher davon ausgehen können, dass ausserhalb
dieser Translation Unit die Funktion nicht aufgerufen werden kann.
Function Inlining hat es schon gegeben, da hat es das Schlüsselwort noch
gar nicht gegeben.
> Wenn man es dem Compiler nicht sagt (das inlinen) wird er es nicht> machen.
Der GCC schon. Wenn er meint, inlinen ist kürzer/schneller macht er
das, auch ohne inline Keyword. Mit Keyword, zwingt man ihn dazu.
Danke für eure Vorschläge.
Mit foobars Vorschlag konnte ich die Rechenzeit halbieren. Modifiziert
auf einen Teiler /512. Mit 16384 würde die variable schon bei einem fact
= 2 überlaufen. (sollte ja 0-100% darstellen)
isidor schrieb:> Oder einen 32 Bit ARM nehmen, da bist du solche Sorgen los.
Darauf wird's wohl hinauslaufen...
@ Michael (Gast)
>> Oder einen 32 Bit ARM nehmen, da bist du solche Sorgen los.>Darauf wird's wohl hinauslaufen...
Jaja, und dann kommen die nächsten "Experten" die eine Motorsteuerung in
Java machen wollen, dann muss es halt ein Quad-Core sein.
Brain 2.0 ist immer noch unschlagbar und sehr zu empfehlen!
Eine weitere Optimierung waere die Sinutabelle auf 128 Phasen Werte zu
reduzieren. Und die Sinustabelle auf eine Wertbreite von 256. Das waere
dann ein Wert fuer alle 3 Grad, und eine Amplitudenaufloesung von 1%.
Falk Brunner schrieb:> Jaja, und dann kommen die nächsten "Experten" die eine Motorsteuerung in> Java machen wollen, dann muss es halt ein Quad-Core sein.
Wenn der Prozessor an der "Kotzgrenze" mit 34 MHz arbeitet
und die Optimierungen schon grenzwertige Ergebnisse zeigen,
dann muss es wohl auf einen Prozessorwechsel hinauslaufen.
Divisionen/Multiplikationen in wenigen Takten (bei noch höherer
Taktfrequenz) gegenüber einigen hundert Takten in einem ATxMega
sprechen eine deutliche Sprache.
Ich würde mich mal fragen, warum es notwendig ist, die Drehzahl alle
36µs neu einzurechnen. Kein Motor ändert so schnell seine Drehzahl
nennenswert.
weiters würde mich interessieren, wo eigentlich pwm_output herkommt.
Auch wenn es nicht erwähnt wird, geh ich davon aus dass das ein
Vorgabewert ist. Und auch der ändert sich (im Normalfall) nicht im µs
Takt, wodurch es völlig sinnlos wäre, die Division 1000/pwm_output
laufend zu machen.
Aber ohne mehr von der Umgebung des Code-SChnipsel zu kennen bzw. zu
sehen, ist das alles Spekulation. Zeit in einem Programm holt man sich
nicht durch Mikrooptimierung von einzelnen Anweisungen sondern indem man
sich das Gesamtkonzept ansieht und überlegt, welche Berechnungen wann
unbedingt notwendig sind.
Versuch anstelle der durch 10.000 zu teilen:
X/1024 * 105 / 1024
Ergibt einen Fehler von 1,4 Promille. Sollte also reichen, als
Annäherung.
E: Es ist allerdings notwendig mir 24 Bit fest-Komma u rechnen. Also
16.8 Format.
Du brauchst nur eine Sinustabelle von 0-90°, der Rest sind
Wiederholungen (rückwärts auslesen, Vorzeichen ändern)
Bleib bei 8bit Arithmetik, die Massenträgheit des Motor integriert
hervorragend.
Für Drehzahländerungen verändere ich nur die Zeitkonstante mit der ich
die Sinustabelle lese bzw. den PWM setze.
Wo das nicht reicht überspringe ich Werte in der Tabelle.
So habe ich schon mit deutlich schwächeren MCUs DTMF Töne erzeugt.
(zwei überlagerte Sinustöne aus einer Tabelle bis 1.6Khz)
isidor schrieb:> dann muss es wohl auf einen Prozessorwechsel hinauslaufen.
Ja ?
Man kann auch einfach effizienten Code schreiben.
Karl Heinz schrieb:> Ich würde mich mal fragen, warum es notwendig ist, die Drehzahl alle> 36µs neu einzurechnen. Kein Motor ändert so schnell seine Drehzahl> nennenswert.
Das wurde ja eingangs schon erwähnt. Ich habe mir vorgestellt die
Divisionen im Interrupt-Kontext zu rechnen, sozusagen in low
Priority im Hintergrund. Aber ob soviel Zeit (vermutlich viele
hundert Zyklen) bleibt um nicht den Gesamtablauf in Stottern zu
bringen ist so schwer zu beurteilen. Leider müsste eine Division
komplett im Interrupt-Kontext ausgeführt werden, das ist wohl
nicht noch zusätzlich zeitlich aufteilbar. Die Update-Rate der
Berechnungen könnte dann durch die höhe der Interrupt-Rate
gesteuert werden.
Eine Verbesserung wäre auch die Berechnung in drei Phasen
aufzuteilen, d.h. nur eine Division pro Durchlauf, jedesmal eine
andere. Ob das praktikabel ist hängt natürlich auch von Umständen
ab die ich nicht kenne.
@ isidor (Gast)
>Wenn der Prozessor an der "Kotzgrenze" mit 34 MHz arbeitet>und die Optimierungen schon grenzwertige Ergebnisse zeigen,>dann muss es wohl auf einen Prozessorwechsel hinauslaufen.
Nö. Weder das Problem noch die Lösung wurden hinreichend analysiert,
geschweige denn optimiert.
Ich empfehle weiterhin Brain 2.0. Eine Division in dieser Art ist nicht
nötig. Ausserdem würde ich prüfen, ob der Compiler schlau genug war, die
Division in eine deutlich schnellere Schiebeoperation umzuwandeln. Oder
noch einfacher, die Schiebeoperation DIREKT hinzuschreiben.
>Divisionen/Multiplikationen in wenigen Takten (bei noch höherer>Taktfrequenz) gegenüber einigen hundert Takten in einem ATxMega>sprechen eine deutliche Sprache.
Bla. Die AVRs haben einen MUL Befehl, der dauer für 8x8 Bit 2 Takte, für
16x16 Bit vielleicht ein Dutzend Takte incl. der Addidtionen.
Jetzt Nicht schrieb:> Interrupts muessen so schnell wie moeglich sein.
Sagen wir mal: so schnell wie notwendig.
Das kann je nach Aufgabenstellung unterschiedlich sein.
Wenn es in die µs geht, dann muss man eben ein wenig 'vorsichtiger' zu
Werke gehen, als wenn man 'alle Zeit der Welt' hat. In letzterem Fall
kann man durchaus auch naiv vorgehen und einen Sinus in Zehntelgrad
Abstufung und mit 8000 Amplitudenabstufungen generieren, auch wenn das
keiner braucht.
Falk Brunner schrieb:> Bla. Die AVRs haben einen MUL Befehl, der dauer für 8x8 Bit 2 Takte, für> 16x16 Bit vielleicht ein Dutzend Takte incl. der Addidtionen.
Ich habe mich auf die Divisionen bezogen um die es bei der
Problemstellung gegangen ist. Und da dauert es deutlich länger.
Siehe __udivmodhi4
@ isidor (Gast)
>Ich habe mich auf die Divisionen bezogen um die es bei der>Problemstellung gegangen ist. Und da dauert es deutlich länger.
Schon klar, aber deren Notwendigkeit bezweiflich ich im Moment noch.
Letztendlich ist das Ganze nur eine Variation des DDS Algorithmus,
und der braucht auch keine Divisionen. Allein schon die Sinustabelle mit
360 anstatt eher 256 oder 512 Einträgen ist diskussionswürdig.
Jetzt Nicht schrieb:> Interrupts muessen so schnell wie moeglich sein. Allenfalls eine> Variable setzen. Mehr nicht.
Ich sag meinen Kindern auch immer, dass man nicht an den Herd geht.
Selber jedoch habe ich kein Problem damit.
Soll heißen: Die Regel "im Interrupt setzt man höchstens eine Variabel"
kommt daher, dass man schon wissen sollte wie Interrupts funktionieren,
was an Fragestellungen durch die Nebenläufigkeit und der Priorisierung
auftaucht und wo die Fallstricke liegen. Das ist bei vielen Einsteigern
nicht gegeben, genausowenig wie das Kind weiss worauf es aufpassen muss.
Es als "die Wahrheit" zu verkaufen, dass Herde nicht angefasst werden,
bzw. ISRs nur Variablen schubsen ist jedoch der Weg in die Tiefkühlkost.
Wenn man aber irgendwann mal lernt, bzw. sich mit dem Thema ausser durch
Generalisierungen beschäftigt ist auch das Arbeiten in ISRs wie das
Kochen auf dem Herd: Alltag.
Nicht zuletzt existieren gar HW Unterstzützungen für vielschichtige
Aufsplittung von ISRs und deren Priorisierungen untereinander, sowie
wiedereinsprung-sicheren IRQ Quellen (vgl. z.B. Cortex).
Maxx schrieb:> Es als "die Wahrheit" zu verkaufen, dass Herde nicht angefasst werden,> bzw. ISRs nur Variablen schubsen ist jedoch der Weg in die Tiefkühlkost.
So ist es.
Wenn man in seiner Hauptschleife im Programm nichts zu tun hat kann
man (wenn man es kann) seine rechenintensiven Aufgaben durchaus
im Interrupt-Kontext abarbeiten.
Falk Brunner schrieb:> schon die Sinustabelle mit 360 ... Einträgen ist diskussionswürdig.
Ja, der Mensch hat eben 10 Finger und ein Kreis 360 Grad. Davon kommt
man nicht so leicht weg...
@ Lothar Miller (lkmiller) (Moderator) Benutzerseite
>> schon die Sinustabelle mit 360 ... Einträgen ist diskussionswürdig.>Ja, der Mensch hat eben 10 Finger und ein Kreis 360 Grad. Davon kommt>man nicht so leicht weg...
Gib mir mal ein scharfes Messer . . . 8-)