Mich wundert's, dass ich auf einem ITG nichts über einen endlichen
Automaten gehört habe. Bildung in Deutschland, na was soll's ...
Benutze AVR und Assembler. Möchte einen Drehimpulsgeber auswerten. Habe
natürlich bereits per Suche und in der Artikelsammlung recherchiert. Nun
weis ich aber nicht, wie man eine state machine programmiert.
Kann mir jemand ein kleines Beispiel geben oder zumindest erklären, wie
ich das in Assembler umsetze?
Das ist kein Witz ? Etwas trivialeres wie eine Zustandsmaschine gibt es
kaum. Manbenoetigt eine einzelne Variable, die den Zustand haelt. Dann
im main mach man eine Verzweigung ( CASE / Switch ) nach der Variablen
und fuehrt den passenden Code aus.
Die Statemachine besteht im wesentlichen aus einer Variablen, die die
Nummer des aktuellen Zustandes enthält, einem Adressvektor, in dem die
Startadressen der Codestücke stehen, die zum Zustand n und den
Eingabevariablen den Folgezustand und evtl. die Ausgabe bestimmen.
Der Startpunkt ist ein indizierter indirekter Sprung:
SM_Start: jmp addr[n]
Der Code für einen Zustand endet immer mit einem Sprung an eine
gemeinsame Endadresse, oder evtl. direkt wieder auf den Start.
Wenn ich also bspw. vier mögliche Zustände habe, wobei von jedem Zustand
aus nur in zwei andere gewechselt werden kann, brauche ich vier Tabellen
mit je drei Möglichkeiten:
T1
---------
nach D
bleib in A
nach B
T2
---------
nach A
bleib in B
nach C
T3
---------
nach B
bleib in C
nach D
T4
---------
nach C
bleib in D
nach A
Habe ich das richtig verstanden?
Man kann so ein Teil tabellengetrieben konstruieren, aber ich denke, daß
das eher einfache und vor allem sehr gleichförmige Ausnahmefälle sind.
Meistens wird man jeden Zustand durch ein Stück Code repräsentieren, das
angesprungen wird, wenn die Maschine im betreffenden Zustand ist. Das
macht die Konstruktion wesentlich mächtiger.
Vielleicht solltest du dir erst mal überlegen, wie sowas in einer
Sprache, wie C o.ä. aussieht. Wenn du dann klarheit über die
Funktionsweise hast, überlegst du dir, wie man das in ASM schreibt.
Weitere Informationen: z.B.
http://de.wikipedia.org/wiki/Virtueller_endlicher_Automat
Ja klar, mit switch oder if u.s.w stelle ich einfach Bedingungen auf,
wann welcher Zustand eintreten soll. Aber ich habe halt schon
mitbekommen, dass man durch Arrays sehr vielfältige Operationen mit
irrsinnig vielen Verzweigungen überschaubar in Code umsetzt, der vor
allem schnell abgearbeitet werden kann.
Ok, für meine Anwendung muss es aber nicht so kompliziert sein.
Hier ist ein Diagram aus dem Datenblatt des Drehgebers:
http://www.cipoint.homepage.t-online.de/phase.gif
Der ist von Panasonic, bei Pollin gekauft.
Komisch ist, dass ein Einrastpunkt immer an der Flanke vom Signal B ist.
Bei einem anderen DIG waren die Signale bei den Einrastpunkten klar
definiert. Außerdem durchläuft bei diesem DIG die Drehung von einem
Einrastpunkt zum nächsten nur die halbe Periode.
Das verwirrt mich etwas: Wenn sich der DIG im zweiten Einrastpunkt im
Bild befindet und ich ihn im UZS drehe, wird zuerst A low(im Bild ON).
Dann erreicht der DIG den dritten Einrastpunkt im Bild. Aber da der
Zustand nicht definiert ist, kann es ja vorkommen, dass Signal B gar
nicht low (ON) wird. Dann habe ich ein Problem, oder?
Der Thread-Beginner sucht nach einer Lösung, einen Drehencoder von
Pollin in AVR-Assembler abzufragen bzw. auszuwerten. Dieser Encoder hat
folgende Eigenschaften:
- billig (Restposten, evtl. fehlerhafte Produktion?)
- mechanische Schleifkontakte
- rastet zweimal pro Vollzyklus
- ist enweder etwas asymmetrisch oder auf einer Spur etwas unzuverlässig
Er will also einen rastenden Drehknopf als Eingabegerät zum Dialog
zwischen Mensch und Maschine nutzen, nicht mehr und nicht weniger...
Falk Brunner wrote:
> Drehgeber
Was willst Du uns damit sagen??
Da werden (zu recht!) alle Varianten angeprangert, die externe
Interrupts verwenden. Da wird auch (zu recht) darauf hingewiesen, dass
die Abfrage zyklisch (in gleichen Abständen, also mit fester Frequenz)
erfolgen soll. Da wird auch auf einige C-Beispiele verlinkt und ein
VHDL-Beispiel gezeigt. Aber ich kann keinerlei Lösung in AVR-ASM
entdecken.
> AVR-Tutorial: Mehrfachverzweigung
Das ist zwar korrekt und sollte zum Grundwissen eines
AVR-ASM-Programmierers gehören, löst aber die konkrete Frage nicht, wie
man mit der Drehgeberbewegung eine Variable mitlaufen lässt.
...
Hier mal ein unbereinigtes Codesegment, welches in einer Timer
ISR-läuft, die jede Millisekunde ausgelöst wird. Das Flag "RotaryFlag"
speichert den letzten Pinzustand des A-Terminals vom Drehencoder,
"NewRot" ist das Flag, welches gesetzt wird, wenn ein neuer Drehzustand
detektiert wurde, "RotaryDir" signalisiert die Drehrichtung. Unten
werden noch Buttons abgefragt und entprellt, einer davon ist der des
Drehencoders. Der Drehencoder selbst ist mit 33nF an jedem Terminal nach
Masse hardware-entprellt.
@ Hannes Lux (hannes)
>erfolgen soll. Da wird auch auf einige C-Beispiele verlinkt und ein>VHDL-Beispiel gezeigt. Aber ich kann keinerlei Lösung in AVR-ASM>entdecken.
Na dann mach mal ne AVR-ASM Lösung. Muss ja schliesslich nur die
C-Version umgestrickt werden.
MFg
Falk
Travel Rec. wrote:
>> Aber ich kann keinerlei Lösung in AVR-ASM entdecken.>> Wird wohl Zeit, da mal etwas aufzuarbeiten.
Für mich nicht... Ich komme mit meiner Lösung ganz gut zurecht und bin
auch in der Lage, sie an sich ändernde Gegebenheiten anzupassen.
> Ist sowieso alles zu> C-verseucht hier ;-) ;-) ;-)
Ich habe nichts gegen C. Aber Einige tun so, als ob man AVRs auch ohne
ASM-Kenntnisse in C programmieren könnte. C-Programmierer können (auf
kleinen Mikrocontrollern) nur dann effizienten Code schreiben, wenn sie
auch ASM verstehen. Das wird vom Anfänger oft und gern übersehen...
...
Falk Brunner wrote:
> @ Hannes Lux (hannes)>>>erfolgen soll. Da wird auch auf einige C-Beispiele verlinkt und ein>>VHDL-Beispiel gezeigt. Aber ich kann keinerlei Lösung in AVR-ASM>>entdecken.>> Na dann mach mal ne AVR-ASM Lösung.
Habe ich doch schon, ist oben auch verlinkt. Sie ist aber speziell auf
den labrigen Pollin-Encoder zugeschnitten, funktioniert damit auch,
passt aufgrund dieses speziellen Encoders aber vermutlich nicht in das
allgemeingültige Konzept Deines Wiki-Artikels.
Sie erfüllt aber folgende Kriterien:
- kein externer Interrupt, der den AVR zumüllen kann
- zyklische Abfrage, vom Timer-Interrupt synchronisiert
- Reaktion auf steigende und fallende Flanke des "besseren" Schalters
- Vermeidung der Flankenprüfung des "labrigen" Schalters
- Entprellung (Übernahme erst nachdem der neue Zustand bestätigt ist)
- funktioniert mit diesen Zweitewahl-Teilen
> Muss ja schliesslich nur die> C-Version umgestrickt werden.
Für C bin ich nicht zuständig.
>> MFg> Falk
Ich werde das Rad wohl neu erfinden. So lernt man am meisten. Aber
trotzdem Danke für Lösungsvorschläge.
Nachdem ich mir das Diagramm aus dem Datenblatt genauer angeschaut habe,
bin ich zu folgender Feststellung gekommen:
Im UZS, wenn:
nach (B=1 UND A=1) folgt (B=1 UND A=0)
oder
nach (B=0 UND A=0) folgt (B=0 UND A=1)
Gegen UZS, wenn:
nach (B=1 UND A=0) folgt (B=1 UND A=1)
oder
nach (B=0 UND A=1) folgt (B=0 UND A=0)
Nun lege ich mir also eine LUT an. Das Programm springt zuerst an die
Adresse, die dem alten Zustand entspricht und von dort aus - je nach
neuem Zustand - zu der Zieladresse. Dort befindet sich ein Sprungbefehl,
entweder nach "CW" oder nach "CCW". Wenn alter Zustand gleich dem neuen
ist, wird erst gar nicht in der Tabelle gesucht. Das sollte
funktionieren ...
Nun muss ich die Eingabe noch entprellen. Das möchte ich über eine
UND-Verknüpfung realisieren. Es sollen die letzten drei bis vier Eingabe
vergliechen werden. Aber da weiß ich noch nicht ganz genau, wie ich das
Umsetze. Vielleicht so, wie hier:
Beitrag "Re: Drehimpulsgeber (wiedereinmal.)"
Zur Erinnerung: Ich kenne mich zwar auch in C aus, programmiere das hier
aber in Assembler.
@ Maxim (Gast)
>Nachdem ich mir das Diagramm aus dem Datenblatt genauer angeschaut habe,>bin ich zu folgender Feststellung gekommen:
Die Codes
00
01
11
10
werden zyklisch durchlaufen, entweder nach oben oder unten (mit
Über/Unterlauf). Gray-Code eben.
>Nun lege ich mir also eine LUT an. Das Programm springt zuerst an die>Adresse, die dem alten Zustand entspricht und von dort aus - je nach>neuem Zustand - zu der Zieladresse. Dort befindet sich ein Sprungbefehl,>entweder nach "CW" oder nach "CCW". Wenn alter Zustand gleich dem neuen>ist, wird erst gar nicht in der Tabelle gesucht. Das sollte>funktionieren ...
Naja, geht auch. Aber da du ja von C-Ahnung hast, solltest du dir
vielleicht lieber die Version der D.S.E FAQ anschauen und in ASM
umstetzen. Das ist nur eine kleine Datentabelle mit 16 Einträgen.
MFG
Falk
Oh, ich habe die "unmöglichen" Kombinationen gar nicht beachtet. Falls
sie doch auftreten, muss ich sie abfangen. Also doch lieber ALLE
Zustände beachten. Dann sind es genau 16 ...
Also hier schonmal der PAP für die Entprellung. Habe auf UND-Verknüpfung
verzichtet. Diese Variante kommt mir etwas sicherer vor. Es wird
insgesamt achtmal der Zustand ausgelesen. Dann werden die ersten vier
Werte mit den letzten vier durch ein exklusives ODER verknüpft. Ist das
Ergebnis 0, so waren die ersten vier Werte exakt die letzen vier. Dann
wird einer der Werte übernommen und in das Status-Register RPE_ST
geschrieben.
Jetzt muss natürlich noch vergliechen werden, ob die neuen Werte im
Bit0/1 den alten im Bit2/3 gleichen. Falls nicht, hat sich der Drehgeber
bewegt. Dann muss anhand einer LUT entschieden werden, in welche
Richtung der das tat. Sind die Bits 0/1 gleich 2/3, hat sich nichts
getan ...
T.S. wrote:
> Hannes>>> Für C bin ich nicht zuständig.>> Ich habe Dich aber mit dem Kernighan/Ritchie heute erwischt. ;)>> Torsten
Da benutzt doch einer einfach mein Kürzel...tztztztz
T. S. wrote:
> T.S. wrote:>> Hannes>>>>> Für C bin ich nicht zuständig.>>>> Ich habe Dich aber mit dem Kernighan/Ritchie heute erwischt. ;)
Olle Petze... ;-)
>>>> Torsten
Beste Grüße,
Hannes
>>> Da benutzt doch einer einfach mein Kürzel...tztztztz
Umgekehrt wird ein Schuh draus, Du benutzt Torstens Kürzel, mit dem er
schon seit Jahren hier im Forum (und auch Anderswo) unterwegs ist. Ehe
man sich mit einem Namen registriert, schaut man erstmal, ob der nicht
bereits vergeben ist... ;-)
...
Tim S. wrote:
> Danke Hannes für deine Worte :-)
Danke, dass Du sie nicht falsch verstanden hast... ;-)
> So jetzt haben wir das Ding..haha
Prima, ist für alle Beteiligten eine gute Lösung...
Bit- & Bytebruch,
Hannes
Zurück zur Sache. Habe ein kleines Problem mit dem Befehl IJMP. Der
Debugger meldet "AVR Simulator: Invalid opcode 0xffff at address
0x00007e". Weiß jemand, was es zu bedeuten hat? Der Befehl befindet sich
in der Routine INTR_RPE, fast ganz unten.
Quellcode im Anhang.
Also ich habe rausgefunden, dass ich hier:
LDI ZL, LOW(RPE_TABLE*2)
die Adresse der Tabelle nicht mir Zwei multiplizieren darf. Dann läuft
das Programm.
Aber in anderen Programmen muss ich das machen. Woran liegt das?
Maxim wrote:
> Also ich habe rausgefunden, dass ich hier:> LDI ZL, LOW(RPE_TABLE*2)> die Adresse der Tabelle nicht mir Zwei multiplizieren darf. Dann läuft> das Programm.>> Aber in anderen Programmen muss ich das machen. Woran liegt das?
Nur der Befehl LPM arbeitet byteweise im wordorientierten Flash (siehe
Befehlsbeschreibung LPM) und braucht daher das Doppelte der realen
Adresse. IJMP, ICALL greift wordorientiert auf den wordadressierten
Flasch zu und braucht die Adresse korrekt.
...
>Nur der Befehl LPM arbeitet byteweise im wordorientierten Flash (siehe>Befehlsbeschreibung LPM) und braucht daher das Doppelte der realen>Adresse. IJMP, ICALL greift wordorientiert auf den wordadressierten>Flasch zu und braucht die Adresse korrekt.
Das wußte ich nicht, aber jetzt erklärt sich der Rest von selbst.
Mein Programm ist fertig. Im Debugger funktioniert es auch. Aber in der
Realität will es noch nicht. Zur Kontrolle schaltet eine Drehung im UZS
Pin PB0 an und eine gegen den UZS schaltet ihn wieder aus. In der
Simulation klappt es.
Das sind die zwei Routinen:
DEB_RPE: Entprellung der Signale
http://www.cipoint.homepage.t-online.de/DEB_RPE.jpg
INTR_RPE: Interpretierung der Drehrichtung
http://www.cipoint.homepage.t-online.de/INTR_RPE.jpg
Nachdem ich die Abtastrate verringert habe, indem die Routine nun nicht
in der Hauptschleife, sondern durch einen Timer aufgerufen wird,
funktioniert mein Programm.
Interessant wäre zu wissen, warum es in der Hauptschleife nicht ging? Im
Grunde war da nur die Abtastrate um einiges höher. Kann eine zu hohe
Abtastrate kontraproduktiv sein?
Das Programm funktioniert perfekt ohne Prescaler. Es gab keinen einzigen
Aussetzer. Dürfte aber etwas an der Ressourcen knabbern. Dann werde ich
wohl einen kleinen ATtiny dafür anheuern.