Hallo,
ich muss einen Drehgeber mit 100kHz simulieren. Vormals waren es 15kHz
aber die Anforderungen wurden wiedermal geändert.
Aus diesem Grund ist ein Atmega16 bestückt (16MHz Quarz).
Ich habe eigentlich nur vier Zustände, die regemlmäßig durchlaufen
werden:
/A/B
A/B
AB
/AB
Dann geht das Rondo von vorne los.
Ursprünglich war angedacht, über RS232 die Frequenz änderbar zu
gestalten. Daher hatte ich per TWI den Teiler für den Überlauf
übertragen und im Timer Overflow Interrupt aus einem Array die Werte für
die Ausgänge ausgelesen und gesetzt.
Mit dieser Methode komme ich aber nicht ansatzweise an meine 100kHz
heran.
Jetzt habe ich den Quelltext mal komplett zusammengestrichen (nurnoch
einen Zähler von 1-4 und Ausgabe der Werte auf den Port).
Damit erreiche ich bei maximaler Optimierung gerade einmal 120kHz.
Wieso ist das so langsam? Das würde ja bedeuten, dass der Prozessor für
diese lapidare Aufgabe über 100 Takte benötigt?!
Habe ich einen prinzipiellen Denkfehler oder ist der Prozessor wirklich
so langsam?
Danke für Input!
Wenn der Quelltext so zusammengestrichen wurde, wird wohl auch nur noch
wenig drinstehen, was der Geheimhaltung bedarf.
Wird der Grau-Code über eine Tabelle erzeugt? Oder über
Shift-Operationen mit XOR?
Sind die Port/Pin-Adressen fest oder Variablen?
Es gibt schon ein paar Möglichkeiten, das langsam zu bekommen.
Wer wird da gleich... schrieb:> Wieso ist das so langsam? Das würde ja bedeuten, dass der Prozessor für> diese lapidare Aufgabe über 100 Takte benötigt?!
Das wird an deinem Programm liegen.
Der Prozessor kann es auf jeden Fall schneller. Wenn es also wirklich
schnell sein soll, nimmst du den ganzen Timer- und Interrupt-Kram raus
und lässt den Prozessor die Pulse direkt mit Zeitsteuerung z.B. über
NOP-Befehle ausgeben.
Felix U. schrieb:> Bubbi B. schrieb:>> Oder muss ich andere Fuses auch noch setzen????>> CLKDIV8 darf nicht gesetzt sein, das ist iirc bei Auslieferung gesetzt.
Hat der Atmega16 nicht.
Bubbi B. schrieb:> if (i==4)> {i=0;}
Damit erzeugst du dir einen (vermutlich) ungewollten Jitter im Signal,
i.e. der Zustand bei i=3 ist um die Ausführungszeit für das 0-setzen von
i länger. Gleichmäßiger läuft das ab, wenn du einfach immer ein
1
i &= 0x03;
durchläufst.
Die ganze Indizierung mit i=0..3 kann man durch linearen Code ersetzen,
das würde noch die Zeit für den indizierten Array-Zugriff sparen.
PORTC=((PORTC&0xC3)|encodervalue[i] ...
Da musst Du den Port erstmal lesen, mit c3 verunden, encodervalue lesen,
mit dem vorangegangenen verodern und dann in Port schreiben, das sind
schon einige Schritte ...
schreib doch einfach direkt in Port, sind ja nur 4 Zustände, ergo 4
Zeilen Code, zur Not in ASM
out PORTC, 0xirgendwas
Bubbi B. schrieb:> Hat der Atmega16 nicht.
Sorry, das habe ich verwechselt.
Schneller geht es aber auch, wenn du die encodervalues in 8-bit statt
16-bit ints schreibst, weil der Compiler die dann in Registern behalten
kann. Dasselbe gilt natürlich für i. Dann noch das, was der Weinbauer
gesagt hat, dann bist du wohl schon doppelt so schnell.
Wolfgang schrieb:> Das wird an deinem Programm liegen.
Vermutlich. Ich hab das Programm jetzt komplett reduziert und setze stur
den Port.
2,6 MHz...
Irre, wieviel so wenig Befehle und ein paar optimierungsprobleme (16-
statt 8bit Zugriff etc.) den Prozessor extrem verlangsamen o.O
Selbst wenn ich alles bis ultimo optimiere; ich weiß nicht, ob der
Prozessor das wirklich packt, SPI auch noch zu machen, wenn er 100.000
Hz erreichen soll!
Danke euch!
Bubbi B. schrieb:> SPI auch noch zu machen,
Wenn SPI oder UART per Interrupt zuschlagen wird nur in dem Moment der
Übertragung der Output gestört. Aber das man doch sicher nicht ständig
machen, also läuft die Ausgabe genauso schnell wenn man die Vorgabe per
Schnittstelle einbaut.
ok, fast genauso schnell, es muss ja noch die einstellbare Verzögerung
in die Schleife.
Bubbi B. schrieb:> Vermutlich. Ich hab das Programm jetzt komplett reduziert und setze stur> den Port.>> 2,6 MHz...
Zyklus oder Schrittrate?
Wenn Zylus, dann hast du das ist das Optimum für diese Busy-Loop
erreicht. Sowas kann C gut.
Allerdings: Jitter schon ohne Störungen durch ISRs: 300%. Das kommt
durch das Schließen der Schleife. Kann man aber kompensieren, indem man
nach jeder der ersten drei Portausgaben 2x nop einfügt. Dann ist der
Jitter weg. Asm rules...
Allerdings sinkt dadurch natürlich die Zyklusfrequenz von 16M/6 auf
16M/12, also auf die Hälfte.
Egal, auch dann: Wenn da nur eine einzige in C geschriebene ISR
reingrätscht, bist du locker bei einigen 1000% Jitter...
Nö, nö, der Ansatz mit einer Timer-ISR war schon OK, bloß die Sprache
war falsch. Mit Asm kann man 1MHz ohne jeden Hauch von Jitter erreichen
und auch konkurrierende ISRs wirken sich längst nicht so schädlich aus
(wenn sie ebenfalls liebevoll Asm-optimiert werden).
Falls es dich interessiert, so würde dein Problem in einer richtigen
Sprache gelöst werden:
Wer wird da gleich... schrieb:> Damit erreiche ich bei maximaler Optimierung gerade einmal 120kHz.
Nimm einfach den Timer T1 mit Toggle Output on Compare, da sollten bis
4MHz gehen und ganz ohne Interruptlast.
Peter D. schrieb:> Nimm einfach den Timer T1 mit Toggle Output on Compare, da sollten bis> 4MHz gehen und ganz ohne Interruptlast.
Das kann man natürlich auch machen und ist für dieses einfache Problem
die bei weitem effizienteste Lösung.
Aber ganz so einfach ist sie auch nicht umzusetzen. In der Nähe von Vmax
werden sowohl Änderungen der Schrittfrequenz als auch Richtungswechsel
zum Alptraum. Jedenfall, wenn sie einigermassen glitchfrei erfolgen
sollen.
Und schon wieder heisst es: Assembler rules...