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.
Siehe Drehgeber. Und lass dich nicht von "Experten" bequatschen, dass man es mit Pegelwechsleinterrupts auch machen kann.
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.
ansonsten gibts auch einige Mikrocontroller, die haben einen Quadraturencoder in Hardware on Board.
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 :-)
100 kHz und 4 Flanke Erkennung und damit 400 kHz Interupts hoeht sich anspruchsvoll an...
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.
@Uwe Bonnes Wie Du siehst, macht das ein richtiger Profis ganz anders. Vermutlich wird die perfekte Lösung dann am 1.04. verkündet :-)
@ 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.
:
Bearbeitet durch Moderator
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
Funktioniert aber nicht, die Lösung mit PB2 und PB3 und 2 SHR R30 war besser, aber 1 Takt mehr.
Initialisierung: Drehgebertabelle liegt im RAM (ist schneller als ROM) ab Adresse xx00 In ZH liegt das obere Byte dieser Adresse
1 | sbis DrehgeberA-Pin ; |
2 | ori ZL, 8 ; 2 ; Zusammen immer 2 CLK |
3 | sbis DrehgeberB-Pin ; |
4 | ori ZL, 4 ; 4 ; Zusammen immer 2 CLK |
5 | |
6 | LD tmp, Z ; 5 ; Ein Byte aus der RAM Adresse der Tabelle des Z-Registers in tmp geladen |
7 | ADD Dout, tmp ; 6 ; Addiere tmp zu Dout dazu |
8 | LSR ZL ; 7 ; |
9 | LSR ZL ; 8 ; Die 2 alten bits werden nach rechts raus geschoben und die 2 neuen bits werden zu den alten |
MaWin schrieb: > SHR R31 ??? Gruß Jobst
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.
Ulrich schrieb: > Irgendwie fehlt da bei den hier gezeigten Tabellenlösungen fehlt > irgendwie die Verknüpfung mit dem alten Wert. OR R30,R16
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.
:
Bearbeitet durch Moderator
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!
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch Moderator
@ 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?
Man kann noch 1 Zyklus sparen (das mov work,encbits), wenn man die Schleife 2 mal hinschreibt, und dabei die Register-nutzung vertauscht.
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.
:
Bearbeitet durch User
Hallo Falk, ich habe noch eine kleine Korrektur im Abschnitt timer_loop 8bit Code:
1 | sbrs tmp, OCF1A ; <-- |
2 | rjmp timer_loop |
3 | nop
|
@ 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.
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.