Forum: Mikrocontroller und Digitale Elektronik ASM: Encoder auswerten


von Axel H. (mf-futzi)


Angehängte Dateien:

Lesenswert?

Hallo an alle,

ich möchte in ASM meinen Encoder auswerten. Ich wollte das so machen, 
wie es Peter Dannegger hier im Forum beschrieben hat.
Beitrag "Drehgeber auslesen"

Ich habe zwar schon mehrfach seine Beschreibung duchgelesen und versucht 
aus seinem C-Code etwas herauszulesen. Aber an einer Stelle komme ich 
einfach nicht weiter.

Das habe ich bis jetzt gemacht:
Testboard mit ATMEGA 8, 2 Leds und 4-zeiliges LCD angeschlossen. Durch 
die Leds kann ich den Zustand der Encoder-Pins erkennen. Im LCD lasse 
ich mir den Dezimalwert (umgewandelter Gray-Code) der Pins anzeigen. 
Richtige Reihenfolge ist nun 0-1-2-3-0 bzw 0-3-2-1-0.
Ich verwende einen STEC11B03 Encoder von Reichelt mit Rastung. Rastung 
ist bei 0 und 2.

Mein Code im Timer-Interrupt (Codeausschnitt siehe Anhang).

Es wäre schön, wenn mir jemand sagen könnte, wie der Code nun weiter 
gehen muss.

Axel

von sinusgeek (Gast)


Lesenswert?

Hi Axel

Ich gehe davon aus, Du möchtest einen handbedienten rastenden 
mechanischen Drehgeber auswerten.

So wie Du angefangen hast, wird das nur Pfusch. Schau Dir mal Falks 
Artikel im Wiki an:
http://www.mikrocontroller.net/articles/Drehgeber

Um das umzusetzen, brauchst Du erstmal einen Timer, der Dir etwa alle 
1ms einen Interrupt auslöst. Ob nun die Drehgeberauswertung in der ISR 
erfolgt, oder durch einen Merker (Semaphore, Flag) synchronisiert wird, 
ist erstmal Nebensache, das entscheidet sich, wenn der Rest der Aufgaben 
des Programms bekannt ist.

Wenn Du dann (ohne Warteschleifen!) in der Lage bist, einen Job alle 1ms 
aufzurufen, dann kannst Du Dich um die eigentliche Drehgeberauswertung 
kümmern.

Dazu
- liest man den Zustand ein,
- maskiert alle anderen Bits aus,
- ORt den um 2 Bits verschobenen Zustand vom letzten mal dazu und
  erhält eine 4-Bit-Zahl.
- Man setzt den Z-Pointer (für LPM, also mit doppeltem Wert der Adresse)
  auf eine Tabelle mit 16 Einträgen, in der die Zählwerte (-1, 0, +1)
  der Zustandswechsel stehen,
- addiert den bereits ermittelten Zustandsmix als Offset dazu,
- pickt sich mit LPM den Zählwert (das Inkrement) aus der Tabelle
- und addiert diesen zum aktuellen Zählerstand.
Der aktuelle Zählerstand wird also um 1 vermindert (-1), um 1 erhöht 
(+1)oder bleibt gleich (0).

Ein Beispiel findest Du hier:
Beitrag "Kleiner Funktionsgenerator mit Tiny2313"

Der relevante Codeblock ist dieser:
1
drehgeber:          ;wertet Drehgeberbewegungen aus
2
 in wl,dgpin                ;Drehgeber-Port einlesen
3
 andi wl,dgneumsk           ;nur die benutzten Bits werten
4
 lsl dgalt                  ;altes Drehgeber-Bitmuster
5
 lsl dgalt                  ;nach oben schieben
6
 or dgalt,wl                ;neue Drehgeberbits einblenden
7
 andi dgalt,dgaltmsk        ;Index begrenzen (uralt löschen) 
8
 ldi zl,low(dgtab*2)        ;Tabelle mit
9
 ldi zh,high(dgtab*2)       ;Dregheber-Increment-Werten
10
 add zl,dgalt               ;Index addieren
11
 adc zh,null                ;evtl. Übertrag berücksichtigen
12
 lpm wl,z                   ;Inkrement-Wert holen (0, +1 oder -1)
13
 ;in wl steht das Drehgeber-Inkrement, das die Drehrichtung angibt
14
 tst wl                     ;wurde der Drehgeber gedreht?
15
 brne drehgeber00           ;ja...
16
 rjmp drehgeber_end         ;nein, weiter...
17
drehgeber00:        ;am Drehgeber wurde (wirklich) gedreht
18
 mov dgi,wl                 ;Drehgeber-Increment übernehmen
19
drehgeber0:         ;am Drehgeber wurde (virtuell) gedreht
20
 add dgz,dgi                ;Drehgeber-Increment aufaddieren
21
 ;... (hier war die Begrenzung des Zählumfangs und noch etwas mehr)
22
drehgeber_end:
23
 ret                        ;fertig, zurück...
24
25
dgtab:      ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
26
                    ;aa nn,     aa nn
27
.db     0, 0        ;00 00,     00 01
28
.db     1, 0        ;00 10,     00 11
29
.db     0, 0        ;01 00,     01 01
30
.db     0,-1        ;01 10,     01 11
31
.db    -1, 0        ;10 00,     10 01
32
.db     0, 0        ;10 10,     10 11
33
.db     0, 1        ;11 00,     11 01
34
.db     0, 0        ;11 10,     11 11

Die Routine wird als Unterprogramm im festen Zeitraster von etwa 1ms 
aufgerufen, dieses Tempo reicht für handbetätigte rastende Drehgeber. Da 
es verschiedene Ausführungen von Drehgebern gibt, muss die Tabelle zum 
Drehgeber passen. Die Beispieltabelle passt zu den preiswerten 
Drehgebern von Pollin (die mit dem dicken Knopf).

Die Deklaration der verwendeten Konstanten und Registernamen findest Du 
im verlinkten Originalcode.

~

von sinusgeek (Gast)


Lesenswert?

> - addiert den bereits ermittelten Zustandsmix als Offset dazu,

Hier ist natürlich Index gemeint und nicht Offset, sorry

~

von Axel H. (mf-futzi)


Lesenswert?

Hallo  sinusgeek,

danke erstmal für deine Antwort.
> So wie Du angefangen hast, wird das nur Pfusch.
Also wenn du das bezüglich des LCDs und der Leds meinst, das habe ich 
nur zur Überprüfung meiner Werte angeschlossen. Wenns funktioniert 
entferne ich das LCD und die LEDs wieder.

Ich hab schon eine sehr hohe Meinung von Peters Codes und Beiträgen. 
Deshalb hab ich in diese Richung angefangen.

Habe mir auch einige andere Beiträge über Encoder hier aus dem Forum 
ausgedruckt und angesehen. Leider hab ich absolut null Plan von C, kann 
das also nicht für mich in ASM umwandeln.
Werde mir aber erstmal deinen Code und den Beitrag von Falk zu Gemüte 
führen. Ich wollte aber auch verstehen wie's funktioniert und nicht 
einfach kopieren und einfügen.

Axel

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Axel Hüser wrote:

> Das habe ich bis jetzt gemacht:
> Testboard mit ATMEGA 8, 2 Leds und 4-zeiliges LCD angeschlossen. Durch
> die Leds kann ich den Zustand der Encoder-Pins erkennen. Im LCD lasse
> ich mir den Dezimalwert (umgewandelter Gray-Code) der Pins anzeigen.
> Richtige Reihenfolge ist nun 0-1-2-3-0 bzw 0-3-2-1-0.

Da bist du doch schon sehr weit. Du kannst schon die Drehrichtung 
feststellen (aufsteigende oder absteigende Zahlenreihe) und bekommst 
beim Drehen sich ändernde Werte. Beides kannst du auswerten, um eine 
Variable und damit einen Ausgang am AVR o.ä. zu verändern. Was willst du 
denn mit dem Drehgeber beeinflussen?

von spess53 (Gast)


Lesenswert?

Hi

Noch was zu Gemüte führen:

TIMER0_COMPA:         push r24                   ; Timer0 CompareA
                      in r24,SREG                ; Aufruf 1ms Takt
                      push r24
                      push r25
                      push ZL
                      push ZH
                      in r24,PINB                ; Tasten
                      in r25,PINE                ; Drehgeber
                      com r24
                      com r25                    ; beide invertieren
                      swap r24                   ; Werte nach unteres 
Nibble
                      lsr r24                    ; nach Bit 2:0
                      andi r24,0b00111000        ; Rest ausblenden
                      swap r25                   ; werte nach oberes 
Nibbble
                      lsr r25                    ; nach Bit 5:3
                      andi r25,0b00000111        ; Rest Ausblenden
                      or r24,r25                 ; zusammensetzen
                      push r24                   ; Merken
                      in r25,GPIOR0              ; alter Wert
                      push r25                   ; Merken
                      out GPIOR0,r24             ; neuen Wert speichern
                      eor r25,r24                ; Änderungen?
                      and r25,r24                ; nur wenn Taste 
gedrückt
                      out GPIOR1,r25             ; Änderungen Speichern

                      lds ZL,ticksL
                      lds ZH,ticksH
                      adiw ZH:ZL,1
                      sts ticksH,ZH
                      sts ticksL,ZL
                                                 ; Encoder bearbeiten
                      pop r25                    ; alter Wert
                      pop r24                    ; neuer Wert
                      andi r24,0b00000011        ; nur Encoderbits
                      andi r25,0b00000011
                      lsl r25
                      lsl r25
                      or r24,r25                 ; als Adresse 
zusammensetzen
                      clr r25
                      ldi ZL, Low(steps<<1)
                      ldi ZH,High(steps<<1)
                      add ZL,r24
                      adc ZH,r25                 ; Tabellenadresse
                      lpm r25,Z                  ; Drehgeberwert
                      in r24,GPIOR2              ; alte Impulszahl
                      add r25,r24                ; + neue
                      out GPIOR2,r25             ; speichern

                      ser r24
                      sts ms_flag,r24            ; Flag setzen

                      lds r24,cd_flag            ; wenn cd_flag>0 kein
                      tst r24                    ; Count_down
                      brne TIMER0_COMPA10

                      lds r25,count_downH        ; Count Down
                      lds r24,count_downL
                      sbiw r25:r24,1
                      sts count_downH,r25
                      sts count_downL,r24
                      or r24,r25
                      brne TIMER0_COMPA10

                      lds ZL,cd_funcL            ; wird bei abgelaufenen
                      lds ZH,cd_funcH            ; count down ausgeführt
                      icall

                      ldi r24,1
                      sts cd_flag,r24


TIMER0_COMPA10:       pop ZH
                      pop ZL
                      pop r25
                      pop r24
                      out SREG,r24
                      pop r24
                      reti

steps:               .db 0 ,0 ,0 ,0
                     .db -1,0 ,0 ,1
                     .db 1 ,0 ,0,-1
                     .db 0 ,0 ,0 ,0


Ist etwas länger, weil noch einige andere Funktionen bearbeitet werden. 
Der Countdown stellt zum Beispiel einen veränderten Wert zurück, wenn 
innerhalb einer bestimmten Zeit der Taster des Encoders nicht gedrückt 
wird. Ist für einen ATMega1281. Als Speicher für alte Zustände dienen 
die GPIO-Register

MfG Spess

von Axel H. (mf-futzi)


Lesenswert?

Hi Stefan,

> Was willst du denn mit dem Drehgeber beeinflussen?

Ich will damit ein Menü im LCD steuern (= Zähler rauf oder 
runterzählen). D.h. mit Rechts- und Linksdrehen durchs Menü scrollen und 
mit dem Encoder-Taster den Menüpunkt aktivieren.



Axel

von sinusgeek (Gast)


Lesenswert?

> > So wie Du angefangen hast, wird das nur Pfusch.

Dies bezog sich einzig und allein darauf, dass ich auf den ersten Blick 
weder Interrupt-Vektoren noch eine Timer-ISR gesehen habe. Dies ist aber 
nach der in Falks Wiki-Artikel beschriebenen Methode erforderlich. Auch 
das darin enthaltene C-Programm von Peter Dannegger arbeitet mit 
Timer-Interrupt. C kann ich nicht, Peters Programm wird aber auch nicht 
viel anders arbeiten als die oben von mir vorgestellte ASM-Routine, die 
übrigens auch nur eine ADM-Umsetzung dessen ist, was Falk hier in 
irgendeinem Thread mal in Worten (als Algorithmus) formuliert hatte.

~

von Axel H. (mf-futzi)


Lesenswert?

Hallo  sinusgeek,

> ...dass ich auf den ersten Blick weder Interrupt-Vektoren noch eine Timer-ISR 
gesehen habe.

Das ist in meinem Programm schon soweit alles pefekt vorhanden. Ich 
wollte mich hier im Forum nur auf die Drehgeber-Auswertung beschränken 
und nicht seitenweise Programm-Code in den Anhang stellen, der für mein 
Problem nicht direkt relevant ist.

Also konkret scheitert es im Moment daran, den alten Zustand mit dem 
neuen Zustand zu vergleichen, bzw wie/wann ich eine Zustandsänderung 
abspeichere.



Axel

von sinusgeek (Gast)


Lesenswert?

> Das ist in meinem Programm schon soweit alles pefekt vorhanden.

Dann entschuldige bitte meine voreilige Äußerung.

Beide Drehgeberspuren (in Hardware) möglichst auf benachbarte Bits 
legen.  Vorteilhaft sind die Bits 0 und 1, oder auch 2 und 3 (dann muss 
man eben andersherum schieben). Andere Aufteilung geht auch, erfordert 
aber mehr Aufwand (Swap, lsl/lsr)

> Also konkret scheitert es im Moment daran, den alten Zustand mit dem
> neuen Zustand zu vergleichen,

Der Vergleich erfolgt dadurch, dass die zwei neuen Bits und die zwei 
alten Bits zu einem Nibble zusammengebastelt werden, das dann als Index 
auf das Array mit Inkrement-Werten genutzt wird...

Angenommen, die Spuren liegen an Bit 0 und 1. Dann müssen diese mit den 
(gemerkten) Bits der letzten Runde ergänzt werden, die dann in Position 
2 und 3 müssen. Somit hast Du eine 4-Bit-Zahl, die aus zwei neuen und 
zwei alten Bits besteht. Dies ist der Index auf das Array (Tabelle) im 
Flash. Für jede Bitkombination, die keinen oder beide Pegelwechsel 
enthält, wird in der Tabelle eine 0 eingetragen, denn diese sind 
Stillstand (keine Flanke) oder verboten (beide Flanken). Für jede 
einzelne Flanke, die durch Rechtsdrehung entsteht wird eine 1 
eingetragen. Für jede Flanke, die durch Linksdrehung entsteht, wird eine 
-1 (255) eingetragen. Da die Drehgeber unterschiedlich viele Flanken pro 
Rastung haben können, müssen bei einigen Typen einige Zählwerte 
zusätzlich auf 0 gesetzt werden.

> bzw wie/wann ich eine Zustandsänderung
> abspeichere.

Du brauchst eine Speicherzelle, in der Du den alten Zustand sicherst. 
Spess nahm dazu ein GPIOR (hat der Mega8 nicht), ich nahm ein Register, 
da noch welche frei waren. Das Register ermöglicht auch das einfache 
Verschieben der Bits (LSL/LSR). Wenn ich den letzten Status in diesem 
Register gespeichert habe, kann ich den neuen Status in ein Tempregister 
einlesen, die irrelevanten Bits durch AND(I) ausblenden, den gemerkten 
alten Zustand (im anderen Register) um zwei Bits nach links verschieben, 
die neuen Bits aus dem Tempregister mit OR dazu kopieren (Bit 0 und 1 
ist neuer Zustand, Bit 2 und 3 ist alter Zustand, Bit 4 und 5 ist uralt 
und wird gelöscht, Bit 6 und 7 wurde letztes mal schon gelöscht, als sie 
noch auf 4 und 5 waren) und mit AND(I) die unteren 4 Bits stehen lassen, 
also die oberen 4 Bits löschen. Die dabei entstehende Zahl ist 4 Bit 
breit (Werte 0..15) und enthält eine von 16 möglichen Konstellationen 
der Zustände alt-neu (sieha Absatz oben). Und nun wird einfach aus der 
LUT der Wert geholt, der dem Index entspricht. Der Index wird dabei 
nicht verändert und bleibt bis zur nächsten Runde gespeichert, in der er 
wieder um 2 Bits nach links geshiftet wird, wodurch Platz für die 2 
neuen Bits geschaffen wird.

Ich hoffe, dass diese (anders formulierte) Erklärung zum Verständnis 
beiträgt.

~

von Axel H. (mf-futzi)


Lesenswert?

Hallo  sinusgeek,

vielen Dank für die super ausfühliche Erklärung. Ich denke damit komme 
ich jetzt erstmal weiter.

Ich melde mich auf jedenfall nochmal, ob es geklappt hat. Hab ja noch 
ein paar Tage Urlaub ;-)


Axel

von Axel H. (mf-futzi)


Lesenswert?

Hallo,

ich habe nun erstmal die Encoder-Auswertung nach dem Prinzip von Peter 
Dannegger hinbekommen. In der Timer-ISR habe ich folgenden Code:

encoder:
  in     neu, TAS_Pin             ; Tastenport einlesen.
  andi   neu, (1<<Enc1)|(1<<Enc2) ; Nur Encoder-Pins berücksichtigen.

  ldi    temp1, 0b00000001        ; Wenn Bit1 gesetzt ist, Bit0
  sbrc   neu, 0b00000001          ; togglen. (=Gray-Code in Binär-
  eor    neu, temp1               ; Code ändern).

  cp     neu, last                ; Wenn neu und last gleich sind,
  breq   end_encoder              ; keine Änderung -> zum Ende,
                                  ; ansonsten hier weiter...

  sub    last, neu                ; Die Differenz zwischen 2 aufein-
                                  ; ander folgenden Codes ermitteln.

  andi   last, 0b00000010         ; Bit1 ausmaskieren und
  subi   last, 1                  ; 1 abziehen. Korrekte Ergebnisse:
  mov    temp1, last              ;  1 für Rechtsdrehen oder
                                  ; -1 (255) für Linksdrehen.

  andi   last, 0b00000001         ; Vom Ergebnis Bit0 ausmaskieren. Wenn
  tst    last                     ; Differenz 0 oder 2 ist (Bit0=0),
  breq   end_encoder              ; keine Änderung oder ungültig
                                  ; -> zum Ende springen.

  mov    encdir, temp1            ; Drehrichtung speichern.
  mov    last, neu                ; Neuer Zustand ist nun letzter
                                  ; Zustand
end_encoder:


Das Ergebnis dieser Routine ist die Drehrichtung 1 oder -1 (255). Ich 
speichere das Ergenis im Register encdir ab, und werte es im 
Hauptprogramm aus (ähnlich wie Tastenentprellung). Das hat den Vorteil, 
dass der Code im Timer-Interrupt sehr kurz bleibt und ich im 
Hauptprogramm die Möglichkeit habe, verschiedene Zählsprünge einstellen 
zu können (z.B.: 1,2,3,... oder 10, 20, 30,... oder a, b, c, ...). 
Funktioniert toll. Ich kann das im LCD überprüfen, in dem ich eine 
Variable rauf/runter zählen lasse.

Axel

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.