Forum: Mikrocontroller und Digitale Elektronik Versetzte Rechtecksignale auswerten, kein drehgeber


von LarsB (Gast)


Angehängte Dateien:

Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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.

von LarsB (Gast)


Lesenswert?

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?

von Mike (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

Siehe Drehgeber. Und lass dich nicht von "Experten" bequatschen, 
dass man es mit Pegelwechsleinterrupts auch machen kann.

von MaWin (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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.

von ich (Gast)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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.

von ich (Gast)


Lesenswert?

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.

von Samsing (Gast)


Lesenswert?

ansonsten gibts auch einige Mikrocontroller, die haben einen 
Quadraturencoder in Hardware on Board.

von m.n. (Gast)


Lesenswert?

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.

von LarsB (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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 :-)

von Uwe Bonnes (Gast)


Lesenswert?

100 kHz und 4 Flanke Erkennung und damit 400 kHz Interupts hoeht sich 
anspruchsvoll an...

von MaWin (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

@Uwe Bonnes

Wie Du siehst, macht das ein richtiger Profis ganz anders. Vermutlich 
wird die perfekte Lösung dann am 1.04. verkündet :-)

von Falk B. (falk)


Lesenswert?

@ 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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Ulrich (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

@ 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!

von m.n. (Gast)


Lesenswert?

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 :-)

von ich (Gast)


Lesenswert?

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?

von MaWin (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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 ;-)

von Jobst M. (jobstens-de)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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.

von LarsB (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
von MaWin (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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

von MaWin (Gast)


Lesenswert?

Funktioniert aber nicht, die Lösung mit PB2 und PB3 und 2 SHR R30 war 
besser, aber 1 Takt mehr.

von Jobst M. (jobstens-de)


Lesenswert?

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

von MaWin (Gast)


Lesenswert?

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.

von Ulrich (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von Jobst M. (jobstens-de)


Lesenswert?

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

von MaWin (Gast)


Lesenswert?

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

von Ulrich (Gast)


Lesenswert?

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.

von Jobst M. (jobstens-de)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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.

von MaWin (Gast)


Lesenswert?

Ulrich schrieb:
> Irgendwie fehlt da bei den hier gezeigten Tabellenlösungen fehlt
> irgendwie die Verknüpfung mit dem alten Wert.

OR R30,R16

von Jobst M. (jobstens-de)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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.

von Falk B. (falk)


Lesenswert?

@ 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!

von Yalu X. (yalu) (Moderator)


Lesenswert?

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).

von MaWin (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
von Jobst M. (jobstens-de)


Lesenswert?

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

von Ulrich (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

@ 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.

von Falk B. (falk)


Lesenswert?

@ 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
von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

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
von Jobst M. (jobstens-de)


Lesenswert?

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
von MaWin (Gast)


Lesenswert?

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.

von Falk B. (falk)



Lesenswert?

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
von Yalu X. (yalu) (Moderator)


Lesenswert?

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
von Falk B. (falk)


Lesenswert?

@ 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?

von Ulrich (Gast)


Lesenswert?

Man kann noch 1 Zyklus sparen (das mov  work,encbits), wenn man die 
Schleife 2 mal hinschreibt, und dabei die Register-nutzung vertauscht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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 :)

von MaWin (Gast)


Lesenswert?

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)

von Ulrich (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

@ 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.

von Ulrich (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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 :-(

von Simpel (Gast)


Lesenswert?

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

von Falk B. (falk)



Lesenswert?

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
von Uwe S. (de0508)


Lesenswert?

Hallo Falk,

ich habe noch eine kleine Korrektur im Abschnitt timer_loop 8bit Code:
1
sbrs    tmp, OCF1A ; <--
2
rjmp    timer_loop
3
nop

von Falk B. (falk)


Lesenswert?

@ 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!

von Uwe S. (de0508)


Lesenswert?

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
Noch kein Account? Hier anmelden.