Guten Tag, ich hoffe ich habe das richtige Unterforum erwischt.
Ich lese hier regelmäßig mit, habe bisher aber nichts gepostet.
Ich komme mit einer Programmierung nicht zurecht und möchte mir hier
Anregungen für die Lösung bekommen.
Es geht darum Interferenzstreifen mit einem Mikrocontroller zu zählen.
Hardware sieht so aus:
Ein Spiegel des Interferometers wird langsam verschoben und entsprechen
der Verschiebung ändert sich das Interfernzmuster.
Das Muster trift auf zwei Fotodioden (im selben gehäuse direkt
nebeneinander).
Das Signal der Fotodioden wird mit einem Transimpedanzverstärker
verstärkt und dann mit einer Komparatorschaltung zu einem 0V -5V
Rechtecksignal gemacht.
Soweit Funktioniert der Aufbau ganz ordentlich.
Jetzt möchte ich mit einem Atmega (8bit 16mhz, nehme evtl was
schnelleres) Auswerten wie viele Interferenzstreifen nach links oder
nach rechts gewandert sind.
Auf dem Foto ist das Ausgangssignal vom Verstärker und das signal vom
Komparator für eine der Fotodioden.
Wenn sich das muster bewegt, kommt zuerst das signal von einer diode und
kurz danach das der anderen diode.
Ganz ähnlich zu einem Inkrementalgeber. Mit diesem Artikel bei rn-wissen
und dem von mikrocontroller.net habe ich mir ein konzept überlegt, aber
das bekomme läuft nicht so gut wie es soll.
http://www.rn-wissen.de/index.php/%C3%9Cberlegungen_zur_Drehgeber-Auswertung
Ich habe die beiden Signale an die Interrupts 0 und 1 angeschlossen.
Wenn ein Pinchangeinterrupt ausgelöst wird, wird der Zustand der beiden
Pins eingelesen und in globale variablen gespeichert.
Im Hauptprogramm wird dann nach einer Tablle ausgewertet.
Vorwärts
00 vorher: 01
10 vorher: 00
11 vorher: 10
01 vorher: 11
Rückwärts
00 vorher: 10
01 vorher: 00
11 vorher: 01
10 vorher: 11
Alles andere ungültig.
Meine Probleme:
Leider wackelt/pendelt das signal hin und her wenn der Übergang gerade
an der kante einer Fotodiode ist.
Mein Code erkennt ignoriert dieses pendeln nicht und gibt offensichtlich
falsche ergebnisse aus.
Mein Code läuft recht langsam und kann nur sehr langsame bewegung
erkennen.
Ich weiß nciht ob ich auf dem richtigen weg bin...
Wäre für tipps dankbar. Beispiele die ich mir anschauen kann oder
Artikel die ich lesen kann wären auch hilfreich.
Richtig, du hast genau dasselbe Quadratursignal wie bei einem Drehgeber.
Wenn du es richtig auswertest
http://www.dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29
also mit Pegeln und nicht Flanken, hast du auch keine Probleme mit
Flattereffekten wie mit der Lösung von rn-wissen.
LarsB schrieb:> Wenn ein Pinchangeinterrupt ausgelöst wird, wird der Zustand der beiden> Pins eingelesen und in globale variablen gespeichert.
Überlege dir, wie schnell deine Streifen maximal durchlaufen und dann
taste die Leds per Timer regelmäßig ab. Über einen kleinen
Zustandsautomaten kann du dann Streifenwanderrichtung und Streifenanzahl
bestimmen.
> Jetzt möchte ich mit einem Atmega (8bit 16mhz, nehme evtl was> schnelleres) Auswerten wie viele Interferenzstreifen nach links oder> nach rechts gewandert sind.
Nimm einen Arduino...
Der Due kann die Streifen per Hardware auswerten. Etliche tausend
Wechsel pro Sekunde sind dann kein Problem.
Mike schrieb:> Nimm einen Arduino...> Der Due kann die Streifen per Hardware auswerten. Etliche tausend> Wechsel pro Sekunde sind dann kein Problem.
Kannst du das "per Hardware auswerten" etwas genauer beschreiben.
Der ist schon viel schneller, das klingt sehr gut, aber was kann der
Arduino Due mehr?
Danke MaWin, die Seite kannte ich noch nicht. Ist das eine alte
newsgroup aus dem usenet?
LarsB schrieb:> Kannst du das "per Hardware auswerten" etwas genauer beschreiben.
Über zwei Eingänge schließt du deine beiden Rechtecke (aufbereiteten
Photodiodensignale) an. Der Atmel SAM3X8E liefert dir dann in einem
Register immer die aktuelle Anzahl der Streifen.
Mike schrieb:> Der Due kann die Streifen per Hardware auswerten.
Nein.
> Etliche tausend Wechsel pro Sekunde sind dann kein Problem.
Die sind auch in Software kein Problem.
LarsB schrieb:> eine alte newsgroup aus dem usenet?
Die gibt es immer noch.
MaWin schrieb:> Nein
Ähm, doch, due != millanova due, scheiss Typenbezeichnung.
Nun ja, wer Software nicht beherrscht, muss es eben mit Hardwareoverkill
einer ARM Cortex machen.
MaWin schrieb:> Nun ja, wer Software nicht beherrscht, muss es eben mit Hardwareoverkill> einer ARM Cortex machen.
Dann möchte ich deine Software mal sehen, wenn ein Drehgeber mit 2048
Pulsen/Umdrehung mit 10000 rpm dreht und sie nebenher sich auch noch um
ein paar andere Dinge kümmern soll.
Oder hast du vom TO schon gehört, wie schnell er sein Interferometer
durchfahren lassen möchte.
Mike schrieb:> MaWin schrieb:>> Nun ja, wer Software nicht beherrscht, muss es eben mit Hardwareoverkill>> einer ARM Cortex machen.>> Dann möchte ich deine Software mal sehen, wenn ein Drehgeber mit 2048> Pulsen/Umdrehung mit 10000 rpm dreht und sie nebenher sich auch noch um> ein paar andere Dinge kümmern soll.>> Oder hast du vom TO schon gehört, wie schnell er sein Interferometer> durchfahren lassen möchte.
Das heißt also, wenn man keinerlei Ahnung hat, was der Fragesteller
eigentlich braucht, dann setzt man automatisch höchstmögliche
Anforderungen ein, um zu beweisen, daß es einfach nicht geht, sondern
nur mit maximalem Aufwand.
ich schrieb:> Das heißt also, wenn man keinerlei Ahnung hat, was der Fragesteller> eigentlich braucht, dann setzt man automatisch höchstmögliche> Anforderungen ein, um zu beweisen, daß es einfach nicht geht, sondern> nur mit maximalem Aufwand.
Nein, das war ein Beispiel aus der Praxis (bei mir momentan mit zwei
solcher Drehgeberkanälen) um MaWin von seinem "das mach ich locker mit
Software" runter zu holen. Wenn du schon mal mit einem Interferometer
gearbeitet hast, dann weisst du, wie schnell da ein paar 10kHz
zusammenkommen. Bereits bei 10mm/s hast du je nach Wellenlänge irgendwas
um typ. 40kHz als Streifenfrequenz.
Mike schrieb:> Bereits bei 10mm/s hast du je nach Wellenlänge irgendwas> um typ. 40kHz als Streifenfrequenz.
Na wegen 40kHz braucht man doch wirklich kein Geschrei machen. Wenn du
jetzt von mehreren MHz gesprochen hättest, das habe ich noch nicht
selbst gemacht. Aber bis reichlich 200kHz habe ich das selbst schon oft
genug im Einsatz. Das sind paar Programmzeilen, kein Problem.
Eine Schaltung für sin/cos-Signale für ATmega48 findest Du hier:
http://www.mino-elektronik.de/mt12_iic/mt12_iic.htm
Die Schaltung ist zwar für +/-11µA Signale vorgesehen, läßt sich aber
einfach anpassen. Selbst ein Index wird berücksichtigt.
Sofern Dich nur die Nulldurchgänge interessieren, kannst Du dieses
Programm verwenden. Beitrag "4-fach Flankenauswertung per Interrupt mit ATmega48/88"
Sofern Du noch eine Interpolation des Signals brauchst, sag Bescheid.
Danke für die vielen guten Antworten. Da hab ich erstmal einiges
durchzuschauen.
Ich muss mich noch für die ganzen Rechtschreibfehler entschuldigen. War
wohl etwas erledigt gestern.
Da noch nach genaueren Anforderungen gefragt wurde:
Der MC hat keine anderen Aufagneb als die Streifen zu zählen und den z
Wert periodisch über den uart zu schicken. Möchte den Zähler noch mit
der halben Wellenlänge multiplizieren, das könnte aber auch extern
passieren.
Die Frequenz wird nicht über 100 kHz hinausgehen. Es sind keine hohen
Geschwindigkeiten geplant, 10mm/s ist schon die richtige größenordnung.
Wellenlänge ist 532nm.
Die Verstärkerschaltung kann auch nicht viel schneller arbeiten wegen
einem recht hohen rückkoplungswiderstand.
Die 4fach Flankenerkennung mit Interrupt schau ich mir erstmal genau an,
auf den ersten Blick vielversprechend.
Auch Hardware Quadratur-dekoder sind eine überlegung.
LarsB schrieb:> Die 4fach Flankenerkennung mit Interrupt schau ich mir erstmal genau an,> auf den ersten Blick vielversprechend.
Die .a90-Datei ist im Intel-HEX-Format und kann direkt verwendet werden.
Falls Du noch eine Skalierung brauchst, könnte ich Dir diese ins
Programm schreiben.
Ob mit 32-Bit float oder 'echten' 64-Bit double - Alles kein Problem.
Ferner kannst Du Dich frei für das Auswerteverfahren entscheiden. Von
Hause aus ist die Flankentriggerung aktiv, welche Pulsabstände bis 2µs
erfaßt (125kHz auf auf beiden Kanälen).
Falls Du lieber mit Timerinterrupts die Pegeländerungen auswerten
willst, muß PD5 auf GND gelegt werden. Die Abtastfrequenz ist dann
400kHz und bremst den µC konstant aus. Die Datenausgabe kann daher nicht
so schnell erfolgen.
WIR sind flexibel und können Beides :-)
Uwe Bonnes schrieb:> 100 kHz und 4 Flanke Erkennung und damit 400 kHz Interupts hoeht sich> anspruchsvoll an...
Ja, aber man braucht gar keine Interrupts. Wie die dse faq schreibt
einfach die Erkennung in die Programmhauptschleife, und damit er seine
Daten senden kann EINE Instruktion dafür dahinter, dann die Erkennung
noch mal und die nächste zum Senden nötige Instruktion, auch in
unterschiedlichen If-Pfaden.
Wer also in Assembler programmieren kann sollte damit 4 Drehgeber
parallel mit 500ksps auf einem ATTiny auswerten können und seriell
verschicken.
@ MaWin (Gast)
>Wer also in Assembler programmieren kann sollte damit 4 Drehgeber>parallel mit 500ksps auf einem ATTiny auswerten können und seriell>verschicken.
Dazu braucht es kein Assembler, normales C reicht da vollkommen. Knoff
Hoff ist hier, wie so oft, gefragt. Der UART/SPI/I2C arebitet parallel
zur CPU.
Die 500 ksps bei 4 Drehgebern auf eine ATtiny dürften schon in Assembler
ziemlich sportlich sein. Bei 20 MHz sind das gerade einmal 40 Taktzyklen
pro Auswertezyklus oder 10 Taktzyklen pro Auswertezyklus und Drehgeber.
Natürlich kann man kann man ein paar Dinge (bspw. das Einlesen und einen
Teil der Bitmanipulation) für alle 4 Drehgeber parallel machen, wenn sie
alle am gleichen 8-Bit-Port angeschlossen sind. Aber das Zählen und die
Entscheidung, in welche Richtung gezählt werden soll, muss für jeden
Geber unabhängig erfolgen. Dazu kommt noch die serielle Kommunikation,
die trotz Hardwareunterstützung mit mindestens 1 I/O-Zugriff (2 Zyklen)
zu Buche schlägt.
Wenn man so etwas in C machen möchte, wird es wohl darauf hinauslaufen,
dass man sich erst überlegt, wie das Ganze in Assembler aussehen könnte,
und dann so lange den C-Code herumdreht, bis irgendwann zufälligerweise
der gewünschte Assemblercode herauskommt. Bei jedem Compiler-Update muss
dann aber überprüft werden, ob der erzeugte Code immer noch optimal ist.
Yalu X. schrieb:> Die 500 ksps bei 4 Drehgebern auf eine ATtiny dürften schon in Assembler> ziemlich sportlich sein.
Frag die Beiden doch einmal nach konkretem Code. Wenn ich danach frage,
wird immer nur der Finger nach oben in die unendlichen Weiten des
Sternenhimmels gezeigt und gesagt: "Da steht es doch!"
Abgesehen von den Zählkunststücken, die hier einem ATtiny (8-pol.?)
angedichtet werden, würden mich auch die Datenausgabekunststücke
interessieren, mit denen man in 2µs 16 Byte seriell
Falk Brunner schrieb:> UART/SPI/I2C
ausgeben kann.
Anfangs ist es ja immer lustig, aber je mehr sich die beiden
Herrschaften hier selbst demontieren, wird die Frage nach einem
kompetenten Arzt immer dringlicher.
Der Code aus dem Beitrag "4-fach Flankenauswertung per Interrupt mit
ATmega48/88"
(Beitrag "4-fach Flankenauswertung per Interrupt mit ATmega48/88") ist mit Vorsicht zu
genießen: Hier werden die Interrupts zum erkennen der Flanken genutzt,
das kann ggf. bei kurzen Störpulsen zu Zählfehlern führen. Die
zuverlässige Methode ist über den Vergleich von altem und neuen Zustand,
der jeweils nur einmal ausgelesen wird. Ob man die Abtastung in einen
timer interrupt, Pin-Change Interrupt oder ggf. auch die Hauptschleife
packt ist dabei relativ egal.
@ m.n. (Gast)
>> Die 500 ksps bei 4 Drehgebern auf eine ATtiny dürften schon in Assembler>> ziemlich sportlich sein.
Kann sein, muss nicht. Alles eine Frage des Lösungsansatzes.
>Frag die Beiden doch einmal nach konkretem Code.
Frag ruhig ;-)
> Wenn ich danach frage,>wird immer nur der Finger nach oben in die unendlichen Weiten des>Sternenhimmels gezeigt und gesagt: "Da steht es doch!"
Labersack!
>angedichtet werden, würden mich auch die Datenausgabekunststücke>interessieren, mit denen man in 2µs 16 Byte seriell> ausgeben kann.
Mamn oh Mann, du hältst dich wohl für den AVR-Programmierer schlecht
hin, weil deine Homepage mit diversen Kleinprojekten übersäht ist? Schon
mal über einen gescheiten Systemansatz nachgedacht? Ach ne, ist ja über
Bastlerniveau . . .
>Anfangs ist es ja immer lustig, aber je mehr sich die beiden>Herrschaften hier selbst demontieren, wird die Frage nach einem>kompetenten Arzt immer dringlicher.
Du bist wohl ein Spätpupertierender?
Systemansatz:
Kein Mensch braucht bei 500ksps eine Updaterate von 2us, dann dann wäre
wenig gewonnen. Denn der Dekoder soll ja die nachfolgende
Logik/Mirocontroller ENTLASTEN! Also nehmen wir mal 16 Bit/Kanal. Das
sind 65536 Schritte. Aha! Also dauert es mindesten soviele Takte, bis
der Zähler Überläuft. Also kann ich mir soviel Takte Zeit lassen, bis
ich wieder ein Update machen. Macht schlappe 7,6Hz bzw ~130ms. In dieser
Zeit müssen die Zähler einmal übertragen werden. Macht für 4 Zähler a 16
Bit ~488 Baud netto. Sagen wie 1200 oder 4800 Baud und wir haben massig
Luft.
Das Verschachteln der Abtastung und Übertragung ist eine kleine
Herausforderung, aber nicht unlösbares.
Kleiner Wettbewerb. Wer macht das cleverste Programm. Los geht's!
Ulrich schrieb:> Der Code aus dem Beitrag "4-fach Flankenauswertung per Interrupt mit> ATmega48/88"> (Beitrag "4-fach Flankenauswertung per Interrupt mit ATmega48/88") ist> mit Vorsicht zu> genießen: Hier werden die Interrupts zum erkennen der Flanken genutzt,> das kann ggf. bei kurzen Störpulsen zu Zählfehlern führen.
Ich erkläre es noch einmal:
Wer in der Lage ist, störungsfreie Signale anzuliefern, kann bedenkenlos
die max. Eingangsfrequenz nutzen, die sich bei direkter
Flankenauswertung an INT0 und INT1 ergibt.
So mache ich es, da bei mir in der Regel Sinussignale an den Eingängen
liegen, deren Verstärkerstufen garkeine Spikes liefern können (begrenzt
durch Anstiegszeit von OPVs).
Bei nächster Gelegenheit werde ich das Programm auf PCINT testweise
umstellen; laut Datenblatt haben die PCINTs eine Spike-Unterdrückung per
Hardware, sodass nur auf Pegeländerungen reagiert wird, die >= 3
CPU-Takte lang sind.
Sofern mit Störimpulsen zu rechnen ist, kann man diese entweder analog
(Tiefpass) ausfiltern, oder den Eingängen INT0/INT1 jeweils ein D-FF zur
Synchronisierung vorschalten. Das 500kHz Taktsignal wird vom µC
bereitgestellt.
Uns als letzte Möglichkeit kann man, wie oben bereits geschrieben, die
Auswertung im 400kHz Takt intern synchronisieren und auswerten.
So sollte für jeden ernsthaft Interessierten eine Möglichkeit dabei
sein.
Falls Jemand jetzt mit Stromausfall argumentiert, der den Zähler ja
zurücksetzen würde: der hat gewonnen :-)
m.n. schrieb:> Falls Jemand jetzt mit Stromausfall argumentiert, der den Zähler ja> zurücksetzen würde: der hat gewonnen :-)
Wieso? Wozu gibt's einen Brownout und einen EEPROM?
Yalu X. schrieb:> gerade einmal 40 Taktzyklen> pro Auswertezyklus
Ja. Der Code braucht 24.
> oder 10 Taktzyklen pro Auswertezyklus> und Drehgeber.
Nein, schon die dse faq erwähnt die mögliche Oarallelbearbeitung.
m.n. schrieb:> Ich erkläre es noch einmal:> Wer in der Lage ist, störungsfreie Signale anzuliefern, kann bedenkenlos> die max. Eingangsfrequenz nutzen, die sich bei direkter> Flankenauswertung an INT0 und INT1 ergibt.
Niemand ist in der Lage störungdfreie Signale abzuliefern, schon im
ersten Beitrag erwähnt LarsB das pendeln seiner Signale.
Vergiss deinen schrottigen Ansatz mit flankengetriggerten Interrupts, er
ist zudem langsamer.
MaWin schrieb:> Ja. Der Code braucht 24.
Jetzt machst du mich doch ein wenig neugierig :)
> Nein, schon die dse faq erwähnt die mögliche Oarallelbearbeitung.
Es ist schon klar, dass man durch rein sequentielle Abarbeitung der 4
Drehgeber nicht auf die 500 ksps kommen kann. In den DSE-FAQ kann ich
aber keine Informationen zur Parallelbearbeitung finden.
24 Taktzyklen pro Auswertezyklus bedeuten ja, dass bei 500 ksps eine
Prozessortaktfrequenz von 12 MHz ausreichend ist.
Du schaffst du es also, mit einem 12-MHz-ATtiny simultan 4 Drehgeber
unter folgenden Randbedingungen auszuwerten:
- Jeder dieser Drehgeber ändert bis zu 500000 Mal pro Sekunde seinen
Ausgangszustand.
- Die Inkremente der vier Drehgeber werden jeweils in einem vernünftigen
Wertebereich bidirektional gezählt. 8 Bit sind dabei m.E. ausreichend,
mehr sind natürlich besser.
- Alle vier Zählerstände werden über eine serielle Schnittstelle öfter
als alle 128 Inkremente (bei einer Zählerbreite von 8 Bit) ausgegeben,
so dass der Empfänger der Daten durch Aufaddieren der Differenzen die
aktuelle Winkelposition mit einer größeren Wortbreite rekonstruieren
kann.
- Es werden zu keinem Zeitpunkt Inkremente verschluckt.
Habe ich das richtig verstanden?
Wenn ja, werde ich mich morgen Abend daran machen, das Rätsel zu lösen,
falls du das Geheimnis nicht von dir aus lüftest ;-)
Der Falk scheint ja die Lösung ebenfalls schon zu kennen. Falls ihr den
entscheidenden Kniff unabhängig voneinander gefunden habt, sollte ja
eigentlich auch ein Dritter in endlicher Zeit darauf kommen können ;-)
Ich benötige für meine unmodifizierte Drehgeberabfrage 15 Takte für
einen Drehgeber. Nach Optimierung sind es nur noch 8 Takte. Kleiner
bekomme ich es gerade nicht mehr. Dies könnte man auf 3 Drehgeber
erweitern. Ab dem 4 Drehgeber steht kein 16-Bit-Register mehr zur
Verfügung und ich muss 1 Byte immer wieder hin und her schubsen. Sind 4
Zusatztakte. Sind in der Summe 4*8+4 = 36 Takte
Alle 128 Durchläufe müssten alle 4 Bytes einmal verschickt werden - also
alle 4608 Takte - Auch hierfür wird Rechenzeit benötigt, so dass ich
einen 20MHz-Controller nehmen würde. Es würden dann 4340.3 Wertegruppen
in der Sekunde verschickt werden. Damit ist der µC dann allerdings auch
ausgelastet.
Ob das sinnvoll ist, sowas so zu lösen, steht auf einem anderen Blatt
...
Gruß
Jobst
@ Yalu X. (yalu) (Moderator)
>Habe ich das richtig verstanden?
Ja.
>Der Falk scheint ja die Lösung ebenfalls schon zu kennen.
"Sie öööberschääätzen mich."
Nein, ich kenne die Lösung nicht, es ist nur ein Bauchgefühl und
sportlicher Ehrgeiz. Und ja, ich hab mich zu der Aussage, "das geht auch
einfach mit C" wohl etwas vorschnell hinreißen lassen. In Assembler wird
es anspruchsvoll genug. Mal sehen.
"Seien wir realistisch, versuchen wir das Unmögliche."
Und bitte jetzt keine Krümelkackerei wegen ATtiny und 8 Pins. Es gibt
auch solche Sachen wie ATtiny2313. Und selbst wenn es einen ATmega88
oder so brauchen würde, wäre es keine Niederlage. Wenn man mal
verlgeicht, was für unglaublich perfomanten Dinge mit so einem kleinen
AVR schon gemacht wurden (Videoterminal, Echtzeitdemo mit Grafig in
Midisound von diesem Skandinavier, USB-Slave etc.), dann denke ich
sollte auch das möglich sein.
Für mich wäre es kein Problem Hardware zu nehmen, die besser ist als
unbedingt nötig.
Preislich ist z.B. der arduino Due noch im Rahmen.
Ein Vorteil ist, dass der mit seinem hohen Takt (84 MHz) auch mit nicht
optimalem code noch die Aufgabe erfüllen kann. Was ich in Assembler
schreiben könnte, wäre wohl kaum dem c-code überlegen, also bleibe ich
bei c.
Das ist dann technisch wohl nicht die schönste Variante, aber eine die
funktioniert.
Werde erstmal die verschiedenen vorgeschlagenen Drehgeber Auswertungen
durchgehen und schauen was mir Ergebnisse liefert.
Mein Versuch von gestern, das Pendeln zu ignorieren, hatte zur Folge
dass der Zähler auch massenhaft Streifen verpasste, aber ich habe das
Gefühl dass ich in die richtige Richtung unterwegs bin.
Interpolation und anderes lasse ich zu diesem Zeitpunkt sein, erstmal
soll das ding "nur" richtig zählen.
Jobst M. schrieb:> Sind in der Summe 4*8+4 = 36 Takte
Ich bin bis jetzt bei 38 Takten (4, Drehgeber, 8-Bit-Zähler, Übertragung
per serieller Schnittstelle), muss das aber noch einmal überprüfen, ob
das wirklich so funktioniert. Bei 20 MHz Taktfrequenz würde man die
500 ksps also erreichen und hätte sogar noch 2 Takte Luft (bei deinem
Code sogar 4 Takte).
Um aber auf die 24 Takte zu kommen, fehlt wohl noch ein entscheidender
Trick. Ich werde heute Abend mal ein wenig Assembler-Puzzle spielen :)
LarsB schrieb:> Für mich wäre es kein Problem Hardware zu nehmen, die besser ist als> unbedingt nötig.> Preislich ist z.B. der arduino Due noch im Rahmen.> Ein Vorteil ist, dass der mit seinem hohen Takt (84 MHz) auch mit nicht> optimalem code noch die Aufgabe erfüllen kann.
Klar, das klingt vernünftig. Du musst dann nicht nach jeder kleinen
Code-Änderung überprüfen, ob die Zählerei noch mit der erforderlichen
Frequenz funktioniert.
Edit:
Ich habe mich verzählt. Es sind nicht 38, sondern 37 Zyklen.
Jobst M. schrieb:> Nach Optimierung sind es nur noch 8 Takte.
Mist, selbst wenn ich nur 8 bit zähle und PB1 und PB3 als Eingänge
nehmes, brauche ich 9, dazu 2 für der Scheifenrücksprung.
Dein code ist besser.
Ungefähr:
LDI R17,0 ; Zähler
IN R16,PORTB
ANDI R16,0xB0
LD R30,R16
LDI R31,HI(tablebase)
loop:
IN R16,PORTB
ANDI R16,0xB0
OR R30,R16
LPM
ADD R17,R0
SHR R31
:
RJMP loop
Oh, sind doch nur 8 - bei 8 bit Zähler
Jobst M. schrieb:>> SHR R31
Sollte LSR R30 heissen, dein Trick die Tabelle ins RAM zu holen spart
einen Takt, aber dein ADD Dout, tmp liefert doch nur 8 bit. Dein SBIS
ist zwar flexibler, aber 1 Takt mehr.
Wie kommst du auf 16 bit Zähler ?
Die Sign-Extension des Tabellenwerts macht mir Probleme, mir fällt
nichts kurzes ein.
Statt sign extension könnte man ggf. ein Sprungtabelle nutzen, und dann
separaten Code für up, down, nichts und ggf. auch Fehler haben - lohnt
sich vor allem bei mehr als 16 Bit. Eine weitere Möglichkeit wäre es den
Hauptcode weiter mit 8 Bit laufen zu lassen, und nur alle 120 (oder so)
Aufrufe, etwa vor der Ausgabe die oberen Bits zu aktualisieren.
Alternativ könnte man die Richtung auch nicht über eine Tabelle
bestimmen, sondern per logischen Vergleich: auch das ist nicht so
kompliziert.
Ein grobes Gerüst (der alte wert ginge auch in ein Register, ggf. auch 2
Versionen im Wechsel) für einen Kanal sehe etwa so aus:
.equ Maske = (1<<BitA) + (1<<Bit B)
lds R16 , alt ; ggf. auch alten Wert in Register merken
in R2 , PortA
BST R16 , BitA ; Bit A alt merken
eor R16,R2
andi R16,Maske
breq fertig ; kein schritt, da keine Änderung
cpi R16, Maske ; Optionaler test auf Fehler (beide Bits geändert)
BREQ Fehler
sts R2, alt
BLD R16,BitB ; Bit A(alt) nach Position B
eor R16,R2
sbrs R16,BitB
rjmp Schritt_vor
' Schritt zurück
DEC ; Zähler runter
RET
Schritt_vor:
' Schritt vor
INC ; Zähler hoch
fertig:
RET
Der Code ist nicht ganz so schnell, aber dafür flexibel bei den Pins,
und kommt mit wenig Registern aus.
Wenn es wirklich zeitkritisch ist, nimmt man halt einen µC mit Hardware-
Quadraturdecoder, etwa einen Xmega.
Ulrich schrieb:> Wenn es wirklich zeitkritisch ist, nimmt man halt einen µC mit Hardware-> Quadraturdecoder, etwa einen Xmega.
Wie man sieht ist das meistens überflüssig, schon der ATtiny schafft
fast 1Msps, real braucht LarsB nur 40ksps wie er geschrieben hat, also
vieeeeel Luft, kann er auch in C screiben und Unmengen weiteren Code
dazuschreiben.
Es ging vor allem darum, den Unsinn von m.n. hier zu widerlegen, man
bräuchte Hardware und Software ist sowieso zu langsam, bloss weil er
nicht programmieren kann.
MaWin schrieb:> Dein SBIS ist zwar flexibler, aber 1 Takt mehr.
Stimmt. Aber mit 'unflexiblen' Ports wird für 4 Drehgeber ein µC mit 4
Ports benötigt. Also ATmega164 aufwärts.
Dein
> ANDI R16,0xB0
verstehe ich allerdings auch nicht so recht. Was macht das 3. Bit?
> Wie kommst du auf 16 bit Zähler ?
Habe ich nie versprochen ...
Würde ich den Empfänger der Daten aufaddieren lassen.
Wenn ich doch 16 Bit in diesem Chip bilden müsste, dann würde ich sie
nicht jeden Durchlauf bilden, sondern jeden 64. - oder so. Dann hat man
auch genug freie Takte gesammelt.
> Die Sign-Extension des Tabellenwerts macht mir Probleme, mir fällt> nichts kurzes ein.
Keine Sign-Extension. 1 für hoch, -1 (=255) für runter. In einem Bereich
von +127/-128 reicht das. Gut, alle 127 Durchläufe müsste eine
Datenabfuhr stattfinden. (nicht 128)
So sieht die Tabelle bei mir aus:
Für einen count bei vollem Phasendurchlauf:
.db 0,-1,0,0,1,0,0,0,0,0,0,0,0,0,0,0
Für einen count bei jedem Flankenwechsel:
.db 0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0
Gruß
Jobst
Jobst M. schrieb:> Keine Sign-Extension.
Na ja, wenn man eine Tabelle hat wie du sue beschrueben hast und sie in
der dse faq steht mit 8 bit Werten, dann muss man sign extension
betreiben um sie auf einen 16 bit zähler addieren zu können.
Bisher ist es am kürzesten, die Tabelle mit 0, 1 und 2 zu füllen und
jedesmal ein Decrement nachzuschieben.
LDI R18,0 ; Zähler
LDI R19,0
IN R16,PORTB
ANDI R16,0xC0
LD R30,R16
LDI R31,HI(tablebase) 0, 1, 2
loop;
IN R16,PORTB
AND R16,0C0
OR R30,R16
LD R17,Z
ADD R18,R17
ADC R19,R19
SBWI R18,1
LSR R30
LSR R30
:
RJMP loop
macht 11 Takte + 2 für Rücksprung für 16 bit Zähler
Irgendwie fehlt da bei den hier gezeigten Tabellenlösungen fehlt
irgendwie die Verknüpfung mit dem alten Wert. So kann das noch nicht
unktionieren.
Vermutlich ist aber sowieso die Lösung ohne Tabelle schneller, also die
Richtung direkt aus der XOR Verknüpfung zu berechnen. Einen Schritt vor
macht man wenn sich Kanal A von alten Wert von Kanal B unterscheidet und
einen Schritt zurück wenn sich Kanal B von alten Wert von Kanal A
unterscheidet. Sind beide verschieden macht man keinen Schritt oder halt
vor und zurück.
Als Code dann etwa so:
CLR R0 ; nur einmal nötig
mov R16,R2 ; alten wert merken
; (ggf. auch Wechselweise Register nutzen)
swap R16 ; A -> B Position
in R2 , PortA
eor R16,R2 ; alt EOR neu
ror R16 ; Bit nach carry -> Bit set für up
adc R20,R0 ; Kanal 1
adc R21,R0 ; optional 16 Bit Zähler
ror R16 ; Bit nach carry -> down
sbc R20,R0
; optional längerer Zähler
ror R16 ; Bit nach carry -> Bit set für up
adc R22,R0 ; Kanal 2
ror R16 ; Bit nach carry -> down
sbc R22,R0
usw. für bis zu 4 Kanäle
Das gibt dann einmal 4 (mit Registertausch 3) Zyklen + 4 bzw. 6 (mit 16
Bit Zählern) Zyklen für jeden der bis zu 4 Kanäle.
MaWin schrieb:> Na ja, wenn man eine Tabelle hat wie du sue beschrueben hast und sie in> der dse faq steht mit 8 bit Werten, dann muss man sign extension> betreiben um sie auf einen 16 bit zähler addieren zu können.
Ja, das kann man doch machen. Und dies alle <127 Durchläufe zu tun ist
ausreichend. Man hat also genug Zeit, eine Sign-Extension zu machen.
Gruß
Jobst
@ Jobst M. (jobstens-de)
>Ja, das kann man doch machen. Und dies alle <127 Durchläufe zu tun ist>ausreichend. Man hat also genug Zeit, eine Sign-Extension zu machen.
Na dann poste doch mal ein VOLLSTÄNDIGES Programm.
Falk Brunner schrieb:> Na dann poste doch mal ein VOLLSTÄNDIGES Programm.
Das ist mir jetzt zu viel Aufwand. Ich habe noch andere Dinge zu
erledigen. Ich denke nicht, dass das so schwer ist. Du kannst Dich ja
mal dran versuchen.
Gruß
Jobst
@ Jobst M. (jobstens-de)
>> Na dann poste doch mal ein VOLLSTÄNDIGES Programm.>Das ist mir jetzt zu viel Aufwand. Ich habe noch andere Dinge zu>erledigen. Ich denke nicht, dass das so schwer ist.
Schöne Ausrede . . .
Aber da bist du nicht der Erste.
@ Ulrich (Gast)
>einen Schritt zurück wenn sich Kanal B von alten Wert von Kanal A>unterscheidet. Sind beide verschieden macht man keinen Schritt oder halt>vor und zurück.
Das klingt nach einer genial einfachen Lösung!
So ist es.
Wenn man das noch etwas weiter denkt, sollte es mit Ulrichs Idee möglich
sein, auf einem ATtiny 4 Drehgeber mit einer Abtastperiode von 21
Prozessortakten 16 Bit breit auszuwerten und dabei die Ergebnisse
periodisch über einen Hardware- oder Software-UART auszugeben.
Das ergäbe bei 20 MHz Taktfrequenz eine Abtastrate von 952 kHz!
Mich würde aber auch MaWins Code mit den 24 Takten interessieren. Ich
habe mit dieser Taktzahl eine tabellenbasierte Lösung gefunden (und
zusätzlich eine mit 23 Takten für ATmegas mit großem Flash), die aber
bei weitem nicht so elegant ist wie Ulrichs Methode (und dazu noch
langsamer).
Yalu X. schrieb:> Mich würde aber auch MaWins Code mit den 24 Takten interessieren.
Thread nicht gelesen ?
Ich habe eine mit 11 Takten für 16 bit Zähler beschrieben.
Die alte hing als Interrupt-Routine am Timer, daher mehrt Takte, war
aber nicht so elegant.
MaWin schrieb:> Thread nicht gelesen ?
Doch.
> Ich habe eine mit 11 Takten für 16 bit Zähler beschrieben.
Und mit dem Sprungbefehl am Ende sind es 13 Takte. Das ist aber nur für
einen einzelnen Drehgeber. Ich hatte dich so verstanden, dass die 24
Takte für 4 Drehgeber gelten :)
Ich glaube, andere haben dich ebenso missverstanden, was aber gar nicht
so schlecht war, da diese Vorgabe uns erst so richtig zum Nachdenken
angeregt hat ;-)
Für 1 Drehgeber geht Ulrichs Ansatz in 10 Takten (alles inklusive), für
2 Drehgeber sind es 14 Takte. Tabellenbasiert geht beides noch einen
Takt schneller, also in 9 bzw. 13 Takten. Vielleicht kann man aber auch
Ulrichs Methode noch etwas besser implementieren.
Falk Brunner schrieb:> Schöne Ausrede . . .
Ist mir scheissegal, was Du darüber denkst. Ich denke, ich habe hier
schon häufig genug bewiesen, dass ich solche Kniffelaufgaben drauf habe.
Gruß
Jobst
Im Code oben (Datum: 29.03.2014 10:14) ist noch ein kleiner Fehler: Die
Reihenfolge in denen die Zähler ausgewertet werden stimmt noch nicht.
Die ersten 4 Bits die man ins Carry schiebt sind für je eine Richtung
der 4 Kanäle, also etwa alle mit ADC ... und erst dann kommen die 4
anderen Richtungen. Die Lösung passt aber wirklich nur so gut für den
eher theoretischen Fall mit 4 Kanälen, weil es dazu den Swap Befehl
gibt. Für weniger Kanäle kommt dann für nicht nicht genutzte einfach 1
Zyklus dazu.
Bei den Taktzyklen sind es dann für 8 Bit Zähler
4 (ggf. auch 3) + 4 je genutzten Kanal und 1 je ungenutztem Kanal.
Für 16 Bit Zähler dannn
4 (ggf. auch 3) + 6 je genutzten Kanal und 1 je ungenutztem Kanal.
Dazu kommt noch die Schleife bzw. der Interrupt overhead.
Für einen Incrementalgeber geht es noch einen Zyklus schneller
(Alternativen zu 3 Shifts).
Das wären dann also für 1 Kanal 8 Bit minimal wohl 3+4+2= 9 Zyklen
zuzüglich der Schleife.
Was man allgemein noch machen kann ist eine Art Loop Unrolling:
Insbesondere kann man 2 Druchläufe hintereinander packen und so das
Tauschen von Registern für den aktuellen und alten Wert einsparen. Der
Teil für die Schleife auch auf die Hälfe.
@ Jobst M. (jobstens-de)
>Ist mir scheissegal, was Du darüber denkst. Ich denke, ich habe hier>schon häufig genug bewiesen, dass ich solche Kniffelaufgaben drauf habe.
Ein paar Fragmente hinschmeißen kann fast jeder . . .
Auch die andere Herren sind angehalten, eine KOMPLETTE Lösung zu
präsentieren.
@ Yalu X. (yalu) (Moderator)
>Wenn man das noch etwas weiter denkt, sollte es mit Ulrichs Idee möglich>sein, auf einem ATtiny 4 Drehgeber mit einer Abtastperiode von 21>Prozessortakten 16 Bit breit auszuwerten und dabei die Ergebnisse>periodisch über einen Hardware- oder Software-UART auszugeben.
Ich glaub dir gehen gerade die Pferde etwas durch. Von einer Routine mit
4 Drehgebern und nur 21 Takten sind wir MEILENWEIT entfernt, die Ausgabe
per UART / Whatever hat auch noch keiner im Detail angepackt.
>Das ergäbe bei 20 MHz Taktfrequenz eine Abtastrate von 952 kHz!
Ich glaube SOOOO schnell wird man es nicht schaffen. Ich bin aber
bereit, mich mit einem vollständigen Code vom Gegenteil überzeugen zu
lassen.
>bei weitem nicht so elegant ist wie Ulrichs Methode (und dazu noch>langsamer).
Die Idee ist sehr gut!
Ok, hier mal meine Version, basierend auf der genialen wie einfachen
Idee von Ulrich.
Beitrag "Re: Versetzte Rechtecksignale auswerten, kein drehgeber"
Das Dekodermakro braucht 20 Takte für 4 Drehgeber. Dann werden im
Multitasking die einzelnen Zähler per UART rausgehauen, was bei 8 Bit
Zählern noch ziemlich hohe Baudraten und Frame rates braucht. Wenn man
es auf 16 Bit erweitert und dadurch eine minimal geringere Abtastrate in
Kauf nimmt, kommt man mit 16 Bit Zählern auf SEHR entspannte Baudraten
und Wiederholfrequenzen.
Siehe Anhang.
Ulrich schrieb:> Irgendwie fehlt da bei den hier gezeigten Tabellenlösungen fehlt> irgendwie die Verknüpfung mit dem alten Wert.
Sie ist aber sowohl bei mir, als auch bei MaWin vorhanden.
Und von meinem Code kann ich behaupten, dass er gerade vor meiner Nase
funktioniert. (Wenn auch nur in einem TimerIRQ mit 5kHz)
Ich finde Deine Lösung sehr elegant!
Edit: erledigt ...
Gruß
Jobst
Falk Brunner schrieb:> Von einer Routine mit> 4 Drehgebern und nur 21 Takten sind wir MEILENWEIT entfernt,
Du vielleicht.
Das ist der Unterschwied zwischen denen, die können, und denem, die nix
können.
LDI R16,0 ; Zähler 1
LDI R17,0 ; Zähler 2
LDI R18,0 ; Zähler 3
LDI R19,0 ; Zähler 4
LDI R31,0
LDI R0,1
OUT RAMP2,R0
loop:
IN R30,PORTB
ELPM
SBRC R0,0
INC R16,1
SBRC R0,1
DEC R16,1
SBRC R0,2
INC R17,1
SBRC R0,3
DEC R17,1
SBRC R0,4
INC R18,1
SBRC R0,5
DEC R18,1
SBRC R0,6
INC R19,1
SBRC R0,7
DEC R19,1
LD R31,R30
:
RJMP loop
21 Takte + 2 für den RJMP, dafür eine 64k Tabelle im ATmega128.
und eine Instruktion bleibt distributed zum Aussenden der Daten.
Nun ja, hier mal die 16 Bit Version und die etwas aufgräumte 8 Bit
Version.
Möge sich jeder selber seine Meinung bilden.
Ich habe auf Timer 1 umgestellt, der ist deutlich flexibler, was die
Framerate angeht. Und er ist sowieso verfügbar.
Mag sein dass man mit der Brachialmethode, 128kB Flash für ne
Statemachine zu benutzen ein paar Takte schneller ist, die Lösung hier
ist auch auf nem kleinen ATtiny wie vor einiger Zeit mal angekündigkt
lauffähig. Und das bei gerade mal 35% Flashauslastung.
Ach ja, und da gibt es auch kleine Unterschiede zwischen denen, die nur
mal schnell ein paar Brainstomingfragmente hinwerfen und denen, die ein
fehlerfrei kompilierbares, und zumindest im Simulator getestetes
Programm haben. Ein LDI r0,1 knallt dir der Assembler mal ganz fix um
die Ohren ;-)
Die 8 Bit Version braucht 20 Takte für die Dekodierung und maximal 26
Takte für einen Dekoderumlauf incl. UART-Übertragung. Macht bei 20 MHz
maximal 769 kHz Abtastrate. Not bad für so eine Softwarelösung. Die 16
Bit Variante braucht 28 Takte für die Dekodierung und maximal 34 Takte
für einen Dekoderumlauf, sprich es sind maximal 588 kHz Abtastrate
möglich.
Danke an Ulrich für die Inspiration.
MFG
Falk
Edit: Die minimale Framerate ist doppelt so hoch wie im Quelltext
genannt, denn man muss die Zähler ja vorzeichenbehaftet interpretieren.
Damit hat man nur noch 127 bzw 32767 Abtasttakte Zeit, um einen Frame zu
senden.
Falk Brunner schrieb:> Ich glaub dir gehen gerade die Pferde etwas durch. Von einer Routine mit> 4 Drehgebern und nur 21 Takten sind wir MEILENWEIT entfernt
Nein, gar nicht. Ulrich hat doch schon fast alles erklärt :)
MaWin schrieb:> Das ist der Unterschwied zwischen denen, die können, und denem, die nix> können.> ...> 21 Takte + 2 für den RJMP, dafür eine 64k Tabelle im ATmega128.> und eine Instruktion bleibt distributed zum Aussenden der Daten.
Sei nicht so überheblich, denn ganz so arg viel kannst du offensichtlich
auch nicht, sonst hättest du nicht angefangen, die 23-Takte-Lösung mit
dem großen ATmega zu implementieren (die ich in meinem vorletzen Beitrag
schon erwähnt habe, sondern gleich diejenige mit Ulrichs Ansatz, die mit
einem ATtiny auskommt und nur 21 Takte einschließlich RJMP braucht ;-)
Hier ist noch einmal sein vervollständigter Code mit aussagekräftigeren
Registernamen und mit einer kleinen Optimierung am Ende der Schleife:
1
#include <avr/io.h>
2
3
; port connections
4
;
5
; PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
6
; | | | | | | | |
7
; B4 B3 B2 B1 A4 A3 A2 A1
8
9
10
; register assignments
11
12
zero = 0
13
counter1 = 1
14
counter2 = 2
15
counter3 = 3
16
counter4 = 4
17
encbits = 5
18
work = 6
19
20
; initialization
21
22
clr zero
23
in encbits,PORTB
24
25
; main loop
26
27
loop:
28
29
mov work,encbits
30
swap work
31
in encbits,PORTB
32
eor work,encbits
33
34
lsr work
35
adc counter1,zero
36
lsr work
37
adc counter2,zero
38
lsr work
39
adc counter3,zero
40
lsr work
41
adc counter4,zero
42
43
lsr work
44
sbc counter1,zero
45
lsr work
46
sbc counter2,zero
47
lsr work
48
sbc counter3,zero
49
sub counter4,work
50
51
rjmp loop
Ein Schleifendurchlauf einschließlich des RJMPs wird in den genannten 21
Takten abgearbeitet, zählt aber erst mit 8 Bit und sendet die Ergebnisse
auch noch nicht über den UART.
Mit einem einfachen Trick kosten diese Zusatzfunktionen kosten aber
keine Zeit: Der eigentliche Schleifenblock ohne den RJMP braucht 19
Takte. Schreibt man ihn n-mal hintereinander, können nach jedem Block
(mit Ausnahme des letzten) ein oder zwei zusätzliche Befehle mit
zusammen 1 oder 2 Takten ausgeführt werden, ohne dass die Abtastperiode
von 21 Takten überschritten wird. Auf den letzten Block folgt der RJMP,
der ebenfalls 2 Takte braucht. Bei n Kopien des Schleifenblocks bekommt
man also 2·(n-1) Takte für zusätzlichen Code geschenkt, ohne dass auch
nur ein einziges Abtastintervall die 21 Takte überschreitet.
Mit dieser zusätzlich gewonnenen Zeit und den 25 noch freien Registern
kann man einige lustige Dinge anstellen:
- den Hardware-UART mit Daten beschicken
- die Datenleitung des Software-UART auf high oder low setzen
- die 8-Bit-Zählerstände zu 16-Bit-Werten akkumulieren
- usw.
Zwischen zwei gesendeten Bytes (bzw. Bits beim Software-UART) müssen –
abhängig von der Baudrate – soviele Schleifenblöcke eingefügt werden,
wie die Übetragung des Bytes (Bits) dauert. Jeder dieser eingefügten
Blocke schafft Zeit für andere Dinge.
Die Akkumulation in 16-Bit-Werte muss nicht ständig gemacht werden,
sondern immer nur dann, wenn gerade eine neue Übertragung des
entsprechenden Zählers ansteht, oder nachdem 127 Schleifenblöcke
ausgeführt worden sind, so dass der 8-Bit-Zählerstand mehrdeutig zu
werden droht.
Ein Schleifenblock ist einschließlich der zusätzlichen Befehle 40 oder
42 Bytes lang. In den 16 KiB Flash, die der größte ATtiny mitbringt,
passen also mindestens 390 Schleifenblöcke, so dass in die bis zu
1560 Bytes zusätzlicher in die Gesamtschleife hineingewoben werden kann.
Soviel zusätzlichen Code braucht man aber gar nicht, so dass man auch
mit kleinern ATtinies schon sehr weit kommt.
Also, es bleibt bei 21 Takten mit 16 Bit, HW-/SW-UART und was einem
vielleicht sonst noch so einfällt.
@ Yalu X. (yalu) (Moderator)
>> Ich glaub dir gehen gerade die Pferde etwas durch. Von einer Routine mit>> 4 Drehgebern und nur 21 Takten sind wir MEILENWEIT entfernt>Nein, gar nicht. Ulrich hat doch schon fast alles erklärt :)
Ok, war ein wenig übertrieben.
>Hier ist noch einmal sein vervollständigter Code mit aussagekräftigeren>Registernamen und mit einer kleinen Optimierung am Ende der Schleife:>Takten abgearbeitet, zählt aber erst mit 8 Bit und sendet die Ergebnisse>auch noch nicht über den UART.
Eben, das fehlt noch.
>(mit Ausnahme des letzten) ein oder zwei zusätzliche Befehle mit>zusammen 1 oder 2 Takten ausgeführt werden, ohne dass die Abtastperiode>von 21 Takten überschritten wird.
Naja, als Extremoptimierung VIELLEICHT machbar, aber irgendwann ist es
auch mal wieder gut mit dem Unrolling ;-)
>Zwischen zwei gesendeten Bytes (bzw. Bits beim Software-UART) müssen –>abhängig von der Baudrate – soviele Schleifenblöcke eingefügt werden,>wie die Übetragung des Bytes (Bits) dauert. Jeder dieser eingefügten>Blocke schafft Zeit für andere Dinge.
Ja, dann wächst der Flashbedarf aber auch mal ganz fix. Man muss es
nicht immer übertreiben. Mag ja sein dass der Igor-USB das so macht.
>Also, es bleibt bei 21 Takten mit 16 Bit, HW-/SW-UART und was einem>vielleicht sonst noch so einfällt.
Na dann poste mal ein VOLLSTÄNDIGES Beispiel. Oder willst du dich lieber
auf die geniale Position von MaWin begeben?
Falk Brunner schrieb:> Naja, als Extremoptimierung VIELLEICHT machbar, aber irgendwann ist es> auch mal wieder gut mit dem Unrolling ;-)
Hier liest man doch öfters den Spruch (kann sein, sogar von dir), dass
man für ungenutzte Flash-Bytes kein Geld von Atmel zurückbekommt ;-)
Falk Brunner schrieb:> Na dann poste mal ein VOLLSTÄNDIGES Beispiel.
Die Chance, dass ich da einen Fehler einbaue, wäre recht hoch, und zum
intensiven Testen fehlt mir etwas die Zeit.
> Oder willst du dich lieber auf die geniale Position von MaWin begeben?
Unter diesen Umständen: Ja ;-)
Als kleine Entschädigung kommt hier der – ebenfalls unvollständige ;-) –
Code für einen einzelnen Drehgeber mit einer Abtastperiode von 9 Takten:
1
#include <avr/io.h>
2
3
; port connections
4
;
5
; PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
6
; | | | | | | | |
7
; B A
8
9
10
; register assignments
11
12
counter = 0
13
encbits = 1
14
diff = 2
15
xl = 26
16
xh = 27
17
18
; initialization
19
20
ldi xh,hi8(table)
21
in encbits,PORTB
22
23
; main loop
24
25
loop:
26
27
in encbits,PORTB
28
swap xl
29
andi xl,0xf0
30
or xl,encbits
31
32
ld diff,x
33
add counter,diff
34
35
rjmp loop
36
37
.data
38
39
table:
40
; ... 256 bytes ...
Er verwendet – etwas verschwenderisch – eine Tabelle mit 256 Werten,
deren Elemente die 8-Bit-Werte -1 (0xff), 0 oder +1 haben. Der ATtiny
sollte also mindestens 256 Bytes SRAM haben.
Und als Bonus: So geht's für 2 Drehgeber in 13 Takten:
1
#include <avr/io.h>
2
3
; port connections
4
;
5
; PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
6
; | | | | | | | |
7
; B2 B1 A2 A1
8
9
10
; register assignments
11
12
counter1 = 0
13
counter2 = 1
14
encbits = 2
15
diff = 2
16
xl = 26
17
xh = 27
18
yl = 28
19
yh = 29
20
21
; initialization
22
23
ldi xh,hi8(table1)
24
ldi yh,hi8(table2)
25
in encbits,PORTB
26
27
; main loop
28
29
loop:
30
31
in encbits,PORTB
32
swap xl
33
andi xl,0xf0
34
or xl,encbits
35
36
ld diff,x
37
add counter1,diff
38
39
mov yl,xl
40
ld diff,y
41
add counter2,diff
42
43
rjmp loop
44
45
.data
46
47
table1:
48
; ... 256 bytes ...
49
50
table2:
51
; ... 256 bytes ...
Diesmal werden – noch verschwenderischer – sogar zwei Tabellen mit
zusammen 512 Byte benötigt. Der ATtiny sollte also mindestens 512 Bytes
SRAM haben.
Die Tabelleninhalte müssen jeweils in der Initialisierungsphase ins SRAM
geschrieben werden. Die genauen Inhalte sollten aus der Art und Weise,
wie die Tabellen genutzt werden, hervorgehen.
Weitere Features (wie 16 Bit und UART) können, wie in meinem letzten
Beitrag skizziert, ohne Einfluss auf die Abtastrate nachgerüstet werden.
Ulrich schrieb:> Man kann noch 1 Zyklus sparen (das mov work,encbits), wenn man die> Schleife 2 mal hinschreibt, und dabei die Register-nutzung vertauscht.
Stimmt, das habe ich vergessen. Du hast das ja oben schon vorgeschlagen.
Dann schafft man mit deinem Ansatz 20 Takte für 4, 13 Takte für 2 und 9
Takte für 1 Drehgeber. Die beiden tabellenbasierten Beispiele für 2 und
1 Drehgeber aus meinem letzten Beitrag sind damit obsolet.
Noch gestern hätte ich es kaum für möglich gehalten, 4 Drehgeber in
weniger als 36 Takten abzuarbeiten :)
Auch von mir ein Danke für deinen genialen Tipp!
Ich hatte zwar auch schon eine ganze Weile überlegt, wie man die 8 alten
und 8 neuen Drehgebersignale durch 8 Bit breite Bitoperationen so in ein
einzelnes Byte überführen kann, dass man daraus mit wenig Aufwand die
Inkremente und Dekremente der 4 Zähler ableiten kann, aber leider nur
mit sehr bescheidenem Erfolg: Es blieben immer noch 33 Takte, die man
durch Vereinfachung der Terme vielleicht noch auf 30 hätte reduzieren
können. Aber das wären ja immer noch 50% mehr als mit deiner Methode :)
Falk Brunner schrieb:> Oder willst du dich lieber> auf die geniale Position von MaWin begeben?
Die geniale Position von MaWin lautet:
Du kannst nur fordern, von dir kommt nix ausser Zweifeln,
und selbst mit einer Lösung, die alles erfüllt,
motzt du immer noch unzufrieden rum und willst ANDEREN
Leute Arbeit machen, die du dir lachend sparst.
Spiel weiter deine dummen Spielchen, aber alleine.
Yalu X. schrieb:> Hier ist noch einmal sein vervollständigter Code mit aussagekräftigeren> Registernamen und mit einer kleinen Optimierung am Ende der Schleife:
Meiner Meinung nach fehlt dort die Umsetzung zwischen Encoderstand und
Increment/Decrement-Befehlen, eben meine 64k Tabelle.
Ein A1 EOR B1alt macht nämlich keineswegs richtige up und ein B1 EOR
A1alt keineswegs richtige down Zählimpulse.
A 000011110000111100001111
B 001111000011110000111100
A EOR Balt 00100010001000100010001 (increment)
B EOR Aalt 01110111011101110111011 (decrement)
richtig 01010101010101010101010 (increment)
wäre 00000000000000000000000 (decrement)
Wenn man das Increment und Decrement zusammenzählt, heben die sich auf
wo 2 mal eine 1 steht. Der Witz bei dem Code mit dem XOR ist ja das ggf.
auch mal hoch und runter gezählt wird um in der Summe nichts zu ändern.
Das gibt dann das gewünschte Muster, nur halt die Andere Zählrichtung -
aber das ist Geschmackssache.
@ MaWin (Gast)
Red mal mit deinem Psychiologen, er muss mal deine Medikamente
kontrollieren.
>Meiner Meinung nach fehlt dort die Umsetzung zwischen Encoderstand und>Increment/Decrement-Befehlen, eben meine 64k Tabelle.
Tja, das ist der Unterschied mal wieder zwischen "genialen Leuten" und
Arbeitern, die den Code mal umsetzen und testen und dabei feststellen,
dass er hervorragend funktioniert, auch ohne "geniale" 64k Tabelle.
Das man einen fremden ASM Code nicht sofort versteht, ist keine Schande,
immerhin ist der Code mit den Verknüpfungen auch alles andere als
offensichtlich. Die Kommentare sind halt bei der suche nach der
schnellsten Lösung auch eher sparsam ausgefallen. So weit auseinander
sind die Lösungen ja auch nicht - für die eher realistischen Fälle mit
nur 1 oder 2 Kanälen hat die Tabelle auch einiges für sich. Und ob es
dann 20 oder 22 Zykeln sind macht auch keinen so großen Unterschied.
Auch MaWin hat mit der Frage wie man die Sign Extension macht dazu
beigetragen es mal mit addieren über das Carry zu versuchen.
Ulrich schrieb:> Wenn man das Increment und Decrement zusammenzählt, heben die sich auf> wo 2 mal eine 1 steht.
Stimmt auffallend, habe ich trotzdem nicht bemerkt obwohl ich die
Zahlenreihen so schön untereinander geschrieben habe. Sehr praktisch.
Nun ja, viele Lösungen für unter 24 Taktzyklen für 4 Drehgeber.
MaWin schrieb:> Nun ja, viele Lösungen für unter 24 Taktzyklen für 4 Drehgeber.
So unterschiedlich kann man die Realität wahrnehmen: ich sehe keine
einzige Lösung. Was ich sehe, ist eine Bitschubserei im virtuellen Raum.
Nirgendwo gibt es gültige Daten, nirgendwo gibt es ein Problem für diese
'Lösung'.
Stillschweigend wurde die Ein-Prozessor-Lösung verworfen, wobei nicht
auszumachen ist, wieviele µCs denn noch die Hauptaufgabe des Zählens
übernehmen müssen. Dass in der realen Welt auch noch Index-Impulse bei
Drehgebern oder Referenzmarken bei linearen Weggebern ausgewertet werden
müssen, wird hier völlig verkannt.
Eine zusätzliche Auswertung der Sinussignale? Fehlanzeige!
Synchrones Auslesen mehrerer Kanäle? Fehlanzeige!
Was hier als 'Lösung' propagiert wird, ist ein überstürztes Rennen in
eine Sackgasse. Aber das merkt man erst, wenn man ein Problem hat, was
eine richtige Lösung braucht.
Und ich dachte schon, ich könnte noch etwas dazulernen :-(
Noch ein Takt weniger durch swap 'n' rol...
Für einen einzelnen Encoder reichen so 9 Takte, bzw. 2*8 Takte für den
von Ulrich vorgeschlagenen Doppeldurchlauf mit vertauschten Registern.
; initialization
clr zero
in encbits,PORTB
in work,PORTB
; main loop
loop:
swap work
in encbits,PORTB
eor work,encbits
lsr work
adc counter1,zero
swap work
rol work
sbc counter1,zero
...
...
swap encbits
in work, PORTB
eor encbits, work
lsr encbits
adc counter1, zero
swap encbits
rol encbits
sbc counter1, zero
rjmp loop
Hier mal ein kleines Update. Die Quelltexte sind aufgeräumt und ein paar
Zahlen zur Abtastrate und Framerate korrigiert. Ausserdem ist die
Abtastung nun absolut gleichmäßig und jitterfrei, alle Programmschleifen
sind exakt gleich lang! Viel Spaß damit.
@ Uwe S. (de0508)
>ich habe noch eine kleine Korrektur im Abschnitt timer_loop 8bit Code:>sbrs tmp, OCF1A ; <--
Ahhh, Stimmt, das war mal Timer 0. Mist!
Falk,
kein Problem, hattest Du ja geschrieben Timer0 -> Timer1.
Die LunaAVR Implementation habe ich nun auch komplett fertig und dein
Assembler-Code in zwei Interface aufbereitet.
Den Downloadlink hast du ja bekommen.