Forum: Mikrocontroller und Digitale Elektronik Verständnisproblem mit PeDas Drehencoder-Routine


von Johannes S. (demofreak)


Lesenswert?

Moin,

ich habe ein kleines Verständnisproblem mit der Drehencoder-Routine von 
Peter Dannegger 
http://www.mikrocontroller.net/articles/Drehgeber#Solide_L.C3.B6sung:_Beispielcode_in_C

Ich verwende einen Encoder mit Rastung aller zwei Schritte, daher wollte 
ich die encode_read2()-Funktion verwenden. Das funktioniert wunderbar, 
wenn man nach oben dreht, nach unten passiert allerdings schlicht gar 
nix.
1
ISR( TIMER0_COMP_vect )             // 1ms for manual movement
2
{
3
  int8_t new, diff;
4
 
5
  new = 0;
6
  if( PHASE_A )
7
    new = 3;
8
  if( PHASE_B )
9
    new ^= 1;                   // convert gray to binary
10
  diff = last - new;                // difference last - new
11
  if( diff & 1 ){               // bit 0 = value (1)
12
    last = new;                 // store new as next last
13
    enc_delta += (diff & 2) - 1;        // bit 1 = direction (+/-)
14
  }
15
}
16
17
int8_t encode_read2( void )         // read two step encoders
18
{
19
  int8_t val;
20
 
21
  cli();
22
  val = enc_delta;
23
  enc_delta = val & 1;
24
  sei();
25
  return val >> 1;
26
}

Statt dass enc_delta bei jeder Abfrage gelöscht wird wie in 
encode_read1(), wird ja in encode_read2() - falls ich das richtig 
verstehe - ein Einzelschritt behalten und im nächsten Durchlauf dann der 
zweite Einzelschritt im Timer-ISR aufaddiert.
In negativer Richtung wird durch
1
enc_delta = val & 1
aber enc_delta jedesmal gelöscht (-1 & 1 = 0), der nächste Teilschritt 
setzt enc_delta im Timer-ISR wieder auf -1, und so wiederholt sich das.

Ich habe schon einen Knoten im Vorderhirn. Wie löst man das geschickt, 
dass auch in negativer Richtung zwei Schritte aufaddiert werden? Oder 
falls ich das alles missinterpretiere, wo liegt mein Fehler?

Gruß,

/Hannes

Edit: hm, irgendwie in der falschen Rubrik gelandet, bitte mal 
verschieben. Danke. ;-)

: Verschoben durch Moderator
von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>Ich verwende einen Encoder mit Rastung aller zwei Schritte, daher wollte
>ich die encode_read2()-Funktion verwenden.

Passt.

>Das funktioniert wunderbar,
>wenn man nach oben dreht, nach unten passiert allerdings schlicht gar
>nix.

Dann ist möglicherweise dein Drehgeber falsch angeschlossen oder defekt. 
Prüfe, ob die beiden Signale wie erwartet phasenverschoben ankommen. Im 
einfachsten Fall mit zwei LEDs mit Vorwiderständen, welche an deine 
Signale und VCC geklemmt werden. Wenn man dann den Drehgeber festhält 
und langsam dreht, muss in beide Richtungen das phasenverschobene 
Schalten der Signale sichtbar sein.

>verstehe - ein Einzelschritt behalten und im nächsten Durchlauf dann der
>zweite Einzelschritt im Timer-ISR aufaddiert.
>In negativer Richtung wird durch

Genau.

>Ich habe schon einen Knoten im Vorderhirn. Wie löst man das geschickt,
>dass auch in negativer Richtung zwei Schritte aufaddiert werden?

Indem man den Code einfach so lässt und benutzt. Er funktioniert ;-)
Das Problem liegt woanders.

von Klaus I. (klauspi)


Lesenswert?

Johannes S. schrieb:
> In negativer Richtung wird durch
1
enc_delta = val & 1
aber
> enc_delta jedesmal gelöscht (-1 & 1 = 0), der nächste Teilschritt setzt
> enc_delta im Timer-ISR wieder auf -1, und so wiederholt sich das.

Nur wenn wirklich jeder Schritt auch abgefragt wird und enc_delta damit 
quasi gelöscht wird.

> Ich habe schon einen Knoten im Vorderhirn. Wie löst man das geschickt,
> dass auch in negativer Richtung zwei Schritte aufaddiert werden? Oder
> falls ich das alles missinterpretiere, wo liegt mein Fehler?

Soweit ich gerade durchblicke, hast Du da im Prinzip schon recht. Der 
eine Teilschritt wird zwischengespeichert und die Richtungsinformation 
ist weg. Im praktischen Betrieb wird das aber kaum auffallen.

Allerdings leide ich auch gerade unter akuter Hirnverknotung, ist schon 
eine Zeit her das ich mir das genau angesehen habe. :o)

Grüße
Klaus

von Uwe S. (de0508)


Lesenswert?

Hallo,

die Ausleseroutine |encode_read2()|ruft man einmal auf und addiert den 
Rückgabewert zu einer Variable vom Type int8_t.
1
' in der main-loop
2
static int8_t encoder_step += encode_read2();

Ist |encoder_step <> 0|, dann führt man seine Menüfunction oder was auch 
immer aus.
Hat man den |encoder_step| abgearbeitet, setzt man den Wert in 
|encoder_step| neu.

Also bei mir encoder_step += (encoder_step< 0 ? +1 : -1).

von Johannes S. (demofreak)


Lesenswert?

Falk Brunner schrieb:
> Dann ist möglicherweise dein Drehgeber falsch angeschlossen oder defekt.
> Prüfe, ob die beiden Signale wie erwartet phasenverschoben ankommen. Im

Wenn ich Einzelschritte mit encode_read1() auslese, klappt es 
einwandfrei. Ich werde mir das aber mit den LEDs nochmal angucken, 
obwohl ich mir eigentlich kaum vorstellen kann, wie man einen 
Drehencoder falsch anschließen will. Möglich ist freilich alles, bei mir 
sowieso. ;-)

> Indem man den Code einfach so lässt und benutzt. Er funktioniert ;-)
> Das Problem liegt woanders.

Das vermute ich ja auch, z.B. hier:

Klaus I. schrieb:
> Nur wenn wirklich jeder Schritt auch abgefragt wird und enc_delta damit
> quasi gelöscht wird.

...und genau das tue ich nämlich der Faulheit halber. Aller 1ms wird die 
ISR gezündet und auch gleich encode_read2() (bzw. read1()) mit 
abgearbeitet. Genau genommen ist das bei mir keine ISR, sondern ein 
Abschnitt in der Mainloop, der bei einem gesetzten 1ms-Flag, welches in 
der Timer-ISR gesetzt wird, einmal durchlaufen wird, aber das tut ja 
nichts zur eigentlichen Sache.

Uwe S. schrieb:
> die Ausleseroutine |encode_read2()|ruft man einmal auf und addiert den
> Rückgabewert zu einer Variable vom Type int8_t.
>
1
> static int8_t encoder_step += encode_read2();
2
>

Das ist ja das Problem. Wenn ich rückwärts drehe, kommt nie etwas 
anderes als 0 zurück.

Gruß,

/Hannes

von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>Wenn ich Einzelschritte mit encode_read1() auslese, klappt es
>einwandfrei.

D.h. es kommt bei Rückwärtsdrehung auch -1 zurück?

>> Nur wenn wirklich jeder Schritt auch abgefragt wird und enc_delta damit
>> quasi gelöscht wird.

>...und genau das tue ich nämlich der Faulheit halber.

???

>Das ist ja das Problem. Wenn ich rückwärts drehe, kommt nie etwas
>anderes als 0 zurück.

Lass dir einfach mal die Variable enc_delta irgendwo ausgeben, wenn du 
nur ein paar LEDs hast, reichen die unteren 4 Bit. Also DIREKT die 
Variable, ohne Nutzung von encode_read()!

Dann muss man bei Vor/Zurück drehen das Bitmustr entsprechend laufen 
sehen.

von Johannes S. (demofreak)


Lesenswert?

Falk Brunner schrieb:
> D.h. es kommt bei Rückwärtsdrehung auch -1 zurück?

Genau.

>>...und genau das tue ich nämlich der Faulheit halber.
> ???

Der Timer-ISR läuft mit 1kHz, und das Auslesen von Enc_delta mittels 
encode_read2() auch. Damit hat das Delta gar keine Chance, 
aufzuaddieren.

> Lass dir einfach mal die Variable enc_delta irgendwo ausgeben, wenn du
> nur ein paar LEDs hast, reichen die unteren 4 Bit. Also DIREKT die
> Variable, ohne Nutzung von encode_read()!
>
> Dann muss man bei Vor/Zurück drehen das Bitmustr entsprechend laufen
> sehen.

Hab ich gemacht, deswegen komme ich ja drauf, dass das nicht 
funktioniert. ;-)

Enc_delta wird beim Rückwärtsdrehen -1 (0b11111111), dann in read2() mit 
1 ver-AND-et (das Ergebnis wird für die Rückgabe rechtsgeschoben und es 
bleibt 0, ergo gibt dieser Durchlauf wie gewünscht nichts zurück), und 
auf die resultierende +1 wird im nächsten Timer-ISR wieder -1 
draufaddiert. Das wird zu 0 (encode_read2 gibt freilich nix zurück und 
"merkt" sich auch nix), und in der nächsten Drehung wird enc_delta 
wieder zu -1. Damit beginnt das Spiel von vorn.

Denkfehler?

/Hannes

von Johannes S. (demofreak)


Lesenswert?

So,

ich habe es jetzt dahingehend korrigiert, dass es auch dann 
funktioniert, wenn der Wert zu oft mit read2() ausgelesen wird. Genau 
daran liegt es nämlich.

Begründung nochmal:
Wenn man mit großer Geschwindigkeit den Encoder ausliest, d.h. wenn man 
ihn schon ausliest, sobald enc_delta auf -1 springt, dann macht 
encode_read2() aus enc_delta = -1 nun enc_delta = +1 (durch das & 1).
Der Rückgabewert von read2() ist korrekterweise null, denn der 
Drehzyklus ist noch nicht abgeschlossen.
Dreht man weiter, so wird enc_delta wieder -1, und zusammen mit der 
falschen "gemerkten" +1 gibt das null.
Dreht man wieder weiter, kommt wieder -1, und das Spiel beginnt von 
vorn.

Ich habe das jetzt wie folgt abgeändert:
1
int8_t encode_read2( void )         // read two step encoders
2
{
3
  int8_t val;
4
 
5
  cli();
6
  val = enc_delta;
7
  ASR val, 1                           // arithmetic shift right
8
  if( val )
9
    enc_delta = 0;
10
  sei();
11
12
  return val;
13
}

Keine Ahnung, ob es in C ein ASR gibt, ich beherrsche C nicht und hab 
das nur adaptiert. Jedenfalls wird val erst durch 2 geteilt, und wenn 
dort was über bleibt, wird enc_delta wieder auf null gesetzt, sonst wird 
der bestehende enc_delta behalten. Bei mir geht das so bisher, wenn das 
noch irgendwo falsch ist, bitte ich um Hinweis, damit ich das bei mir 
noch ändern kann. ;-)

/Hannes

von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>ich habe es jetzt dahingehend korrigiert, dass es auch dann
>funktioniert, wenn der Wert zu oft mit read2() ausgelesen wird. Genau
>daran liegt es nämlich.

Glaub ich nicht ;-)
Ich hab den Code auch schon merhfach benutzt und dieses Problem nicht 
bemerkt.

>ihn schon ausliest, sobald enc_delta auf -1 springt, dann macht
>encode_read2() aus enc_delta = -1 nun enc_delta = +1 (durch das & 1).

Ja.

>Der Rückgabewert von read2() ist korrekterweise null, denn der
>Drehzyklus ist noch nicht abgeschlossen.

NEIN!

-1 einmal ARITHMETISCH nach recht schieben ergibt -1! Klingt komisch, 
ist aber so! Damit ist das KORREKT.

>Dreht man weiter, so wird enc_delta wieder -1,

Nein, es wird um 1 verringert.

> und zusammen mit der
>falschen "gemerkten" +1 gibt das null.

Genau. Aber das ist NICHT falsch gemerkt sondern OK, wenn gleich ein 
Trick.
D.h. negative Zahlen werden durch & 1 modulo gerechnet und ins positive 
gedreht. Das ist aber für die Gesamtwirkung egal.

>Dreht man wieder weiter, kommt wieder -1, und das Spiel beginnt von
>vorn.

Was aber OK ist, denn du bekommts nach je zwei Codewechslen in 
Rückwärtsrichtung einmal eine -1 aus encode_read2(). Das passt.

>Keine Ahnung, ob es in C ein ASR gibt,

Sicher, ganz einfach schieben. Wenn die Variable vorzeichenbehaftet ist, 
macht das der Compiler richtig. Und genau das ist im bestehenden Code 
drin.

>der bestehende enc_delta behalten. Bei mir geht das so bisher, wenn das

Aber nicht mit dem oben gezeigten Code. ASR gibt es so als Befehl nicht 
in C.

Also ich vermute eher, dass du, warum auch immer, ein Problem mit dem 
Vorzeichen hast. Im Code vom Wiki sind sowohl enc_delta als auch val in 
der Funktion als int8_t definiert, also VORZEICHENBEHAFTET. Damit geht 
das. Möglicherweise hast du irgendwelche wilden Compilerschalter 
aktiviert, die das ignorieren oder aushebeln. Oder du hast den Code 
verändert. Mit welcher Entwicklungsumgebung arbeitest du? Welcher 
Compiler mit welcher Version?

Poste vollständigen Code im Anhang, dann kann man das Problem vielleicht 
finden.

von Johannes S. (demofreak)


Lesenswert?

Falk Brunner schrieb:
> -1 einmal ARITHMETISCH nach recht schieben ergibt -1! Klingt komisch,
> ist aber so! Damit ist das KORREKT.

Ha! Das ist der Punkt.

Normales Shift right (>>) ist laut 
http://de.wikipedia.org/wiki/Bitweiser_Operator#C_und_C.2B.2B in C/C++ 
undefiniert, sofern man negative Werte rechts schiebt, und darum bin ich 
drauf reingefallen. Laut dem englischen Artikel 
http://en.wikipedia.org/wiki/Arithmetic_shift#Non-equivalence_of_arithmetic_right_shift_and_division 
soll ein arithmetisches Shift right genau das machen, was der gcc 
offenbar tut, nämlich abrunden und nicht gegen Null runden (also -1 >> 
1 = -1).

> Aber nicht mit dem oben gezeigten Code. ASR gibt es so als Befehl nicht
> in C.

Klar, sagte ich ja. Das war nur symbolisch eingesetzt, ich kann wie 
gesagt kein C.

Der Fehler liegt also eindeutig im fehlenden Verständnis des 
arithmetischen Shift right bei negativen Zahlen, und da bin ich offenbar 
in guter Gesellschaft. :-D

Es läuft also genau anders herum:

- read2() gibt direkt beim ersten Dreher -1 zurück, drückt aber +1 in 
den Skat (weil mit & 1 verknispelt)
- beim zweiten Dreher kommt 0 zurück, weil auf +1 ein -1 addiert wird.
- beim dritten Dreher sind wir wieder am Start

Hab ich das jetzt gecheckt?

/Hannes

von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>Normales Shift right (>>) ist laut
>http://de.wikipedia.org/wiki/Bitweiser_Operator#C_... in C/C++
>undefiniert, sofern man negative Werte rechts schiebt, und darum bin ich
>drauf reingefallen. Laut dem englischen Artikel

Ich kenn den C-Standard nicht, glaub das aber nicht so recht.

>http://en.wikipedia.org/wiki/Arithmetic_shift#Non-...
>soll ein arithmetisches Shift right genau das machen, was der gcc
>offenbar tut, nämlich abrunden und nicht gegen Null runden (also -1 >>
>1 = -1).

Beim Schieben wird nie gerundet, nur abgeschnitten. Also IMMER 
abgerundet, wenn man so will.

>- read2() gibt direkt beim ersten Dreher -1 zurück, drückt aber +1 in
>den Skat (weil mit & 1 verknispelt)
>- beim zweiten Dreher kommt 0 zurück, weil auf +1 ein -1 addiert wird.
>- beim dritten Dreher sind wir wieder am Start

>Hab ich das jetzt gecheckt?

Yo, Man!

Aber was ist nun mit deiner Anwendnung? Funktioniert die oder nicht?

von Johannes S. (demofreak)


Lesenswert?

Falk Brunner schrieb:
> Aber was ist nun mit deiner Anwendnung? Funktioniert die oder nicht?

Ja, tut sie. Das war eine Verbindung zweier Fehler, die ich nicht 
korrekt durchschaut habe. Jetzt, wo ich das eine begriffen habe, habe 
ich auch den anderen gefunden.

Danke.

/Hannes

von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>Ja, tut sie. Das war eine Verbindung zweier Fehler, die ich nicht
>korrekt durchschaut habe. Jetzt, wo ich das eine begriffen habe, habe
>ich auch den anderen gefunden.

Aber wie machst du das, wenn du kein C kannst? :-0

von Johannes S. (demofreak)


Lesenswert?

Das ist kein C, sondern LunaAVR. :-D

Ich portiere nur laufend hin und her, damit ich hier überhaupt Antworten 
bekommen kann.

/Hannes

von Falk B. (falk)


Lesenswert?

@Johannes S. (johannes_s94)

>Das ist kein C, sondern LunaAVR. :-D

Ach so.

>Ich portiere nur laufend hin und her, damit ich hier überhaupt Antworten
>bekommen kann.

OMG! Dann poste doch lieber das ORIGINAL! Damit kann man was anfangen 
und du machst keine weiteren Fehler beim "Umschreiben".

von Johannes S. (demofreak)


Lesenswert?

Der Fehler ist nicht beim Umschreiben passiert, sondern liegt im 
Verhalten des Compilers. Wenn ich mir nicht sicher wäre, dass ich das 
Portieren kann, hätte ich es sicher auch direkt als Luna-Code gepostet 
(dass ich behaupte, kein C zu können, heißt nur, dass ich mir nicht 
zutraue, direkt aus dem Stand in C zu programmieren, aber lesen kann ich 
es schon ein wenig).

Ich nehme mal an, dass du genau wie ich auf die Nase gefallen wärst, 
weil du nicht damit gerechnet hättest, dass Luna (derzeit) bei 
Verwendung des arithmetischen Schiebeoperators ASR aus -1 eine 0 
rechtsschiebt, so wie ich nicht drauf gekommen bin, dass gcc bei >> 
gleich arithmetisch schiebt statt stumpf binär links Nullen 
reinzudrücken.

BTW: OMG ist wenigstens die richtige Anrede. :-P

Aber auf jeden Fall danke nochmal, ohne dich wäre ich nicht drauf 
gekommen.

/Hannes

von Falk B. (falk)


Lesenswert?

@ Johannes S. (johannes_s94)

>weil du nicht damit gerechnet hättest, dass Luna (derzeit) bei
>Verwendung des arithmetischen Schiebeoperators ASR aus -1 eine 0
>rechtsschiebt,

Na dann schreib mal einen schönen Bugreport an den Mann im Mond . . . 
;-)

von Johannes S. (demofreak)


Lesenswert?

Falk Brunner schrieb:
> Na dann schreib mal einen schönen Bugreport an den Mann im Mond . . .
> ;-)

Jawollja, Tante Olja! ;-)

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.