Drehencoder Abfragen

OP #5245713
Lesenswert?

Ich versuche einen einfachen Drehencoder für meine Navigation im Menü 
abzufragen und verzweifle an dieser einfachen aufgabe.

Der Code ist Simpel:
INT0 wird von pin 1 ausgelößt und pin 2 wird abgefragt um festzustellen 
ob vor- oder rückwärts gedreht wird.

Das Funktioniert aber extrem schlecht. Warum verstehe ich nicht.
Habe mit dem Oszi schon Geschaut, ob da was Prellt oder ähnliches.

Herunter zählt er relativ gut bis auf dass er gerne zahlen überspringt.
vorwärts zählt er fast gar nicht, egal ob schnell oder langsahm gedreht 
wird.


Hardware:
Mega328P in TQFP Gehäuse
Display
China Drehencoder (Prellt laut oszi nicht)


Code Komplett:
1
$regfile = "M328pdef.dat"
2
$crystal = 8000000
3
$hwstack = 128                                               
4
$swstack = 64                                               
5
$framesize = 8                                              
6

7
'$sim
8

9
Config Lcdpin = Pin , _
10
Db4 = Portd.5 , _
11
Db5 = Portd.7 , _
12
Db6 = Portb.0 , _
13
Db7 = Portb.1 , _
14
E = Portb.4 , _
15
Rs = Portd.1
16

17
Config Lcd = 16 * 2
18
Cursor Off
19

20

21
'Turn Encoder
22
Config Pind.3 = Input                                       'Button (int0)
23
Config Pind.2 = Input                                       'Input A (int1)
24
Config Pind.4 = Input                                       'Input B
25

26
Config Portb.5 = Output                                     'LED
27

28

29
Config Int0 = Rising
30
On Int0 Encoder_1
31
Config Int1 = Rising
32
On Int1 Encoder_1button
33

34

35
'Turn Encoder
36
Dim Encoder_value As Word
37
Dim Encoder_active As Bit
38
Dim Encoder_direction As Bit
39
Dim Encoder_button As Bit
40
Dim Encoder_min As Word
41
Dim Encoder_max As Word
42
Dim Encoder_step As Byte
43

44
Dim I As Word
45
Dim X As Byte
46

47
Dim T1 As Bit
48
Dim T2 As Bit
49
Dim Tc As Word
50

51
Cls
52
Enable Int0
53
Enable Int1
54
Enable Interrupts
55

56
Encoder_min = 0
57
Encoder_max = 65535
58
Encoder_step = 1
59
Encoder_value = 1000
60

61
Portb.5 = 1
62
Waitms 500
63
Portb.5 = 0
64

65

66

67
Do
68
Locate 1 , 1
69
Lcd "V: " ; Encoder_value ; "   "
70
Locate 1 , 9
71
Lcd "Ct: " ; Tc ; "   "
72
Locate 2 , 1
73
Lcd "D3:" ; Pind.3 ; " d4:" ; Pind.4
74
Loop
75

76

77

78
'Turn Encoder - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
79
Encoder_1:
80
Disable Int1
81
Portb.5 = 1
82

83
Tc = 0
84
X = 0
85

86
'Pin wird 1000 mal abgefragt um ein prelen auszuschließen
87
'Sollte unter 2ms dauern
88
For I = 0 To 1000
89
If Pind.4 = 1 Then
90
X = 1
91
Tc = Tc + 1
92
End If
93
Next
94

95

96
If X = 1 Then
97
Encoder_direction = 1
98
Encoder_value = Encoder_value + Encoder_step
99
'Incr Encoder_value
100
Else
101
Encoder_direction = 0
102
Encoder_value = Encoder_value - Encoder_step
103
'Decr Encoder_value
104
End If
105
Waitms 50
106
Bitwait Pind.2 , Reset
107
Bitwait Pind.4 , Reset
108
If Encoder_value < Encoder_min Then Encoder_value = Encoder_min
109
If Encoder_value > Encoder_max Then Encoder_value = Encoder_max
110

111
Portb.5 = 0
112
Enable Int1
113
Return
114

115

116

117
Encoder_1button:
118
Disable Int0
119
Encoder_button = 1
120

121
Portb.5 = 1
122
Waitms 10
123
Portb.5 = 0
124

125
Waitms 100
126
Bitwait Pind.3 , Reset
127
Enable Int0
128
Return
#5245742
Lesenswert?

Alternativ hier nochmals eine Stufe übersichtlicher in Python - also 
praktisch pseudocode: 
https://github.com/modmypi/Rotary-Encoder/blob/master/rotary_encoder.py
Schöne Erklärung gibs hier: 
https://www.modmypi.com/blog/how-to-use-a-rotary-encoder-with-the-raspberry-pi

Hab das erst diese Woche ausprobiert (musste allerdings die Wartezeit 
auf 0.001s reduzieren) und auch erfolgreich in Java nachgebaut.
C auf einem Microcontroler sollte auch gehen.

Um prellen solltest du dir keine Gedanken machen, oder funktioniert dein 
Drehgeber echt machanisch?
#5245747
Lesenswert?

Ich sehe nicht, daß die Pull-up Widerstände an den Eingängen für den 
Encoder eingeschaltet sind. Das ist wichtig, um eindeutige Pegelwechsel 
zu produzieren.

Also hier:
Config Pind.3 = Input                                       'Button 
(int0)
Config Pind.2 = Input                                       'Input A 
(int1)
Config Pind.4 = Input                                       'Input B

noch ergänzen:
Port.d = &B00011100


Herbert
Gast #5245755
Lesenswert?

Andy _. schrieb:
> Der Code ist Simpel:
> INT0 wird von pin 1 ausgelößt und pin 2 wird abgefragt um festzustellen
> ob vor- oder rückwärts gedreht wird.
>
> Das Funktioniert aber extrem schlecht.

Natürlich.

> Warum verstehe ich nicht.

Das liegt am Prinzip den flankengetriggerten Interrupts.

Frage es nach

http://www.dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29

ab und es funktioniert schlicht und einfach.
Gast #5245839
Lesenswert?

Andy _. schrieb:
> 'Pin wird 1000 mal abgefragt um ein prelen auszuschließen
> 'Sollte unter 2ms dauern

Also, erstmal eines: deinen Basic-Dialekt verstehe ich nicht wirklich, 
hab zuletzt in den 80er Jahren Basic angefaßt.

Aber so, wie ich es mir aus deinem Code herauszulesen glaube, machst du 
sehr seltsame und mir nicht nachvollziehbare Aktionen. Ich versuche mal, 
hier einen für die allermeisten Drehgeber passenden Pseudo-Code zu 
formulieren:

portbit A;   COMMENT: Eines der beiden Drehgebersignale
portbit B;   COMMENT: das andere Drehgebersignal
boolean R;   COMMENT: die aktuelle Drehrichtung
integer Z;   COMMENT: eben ein Auf- und Abwärtszähler

if detected (Flanke(A) = (H to L) or Flanke(A)= (L to H))
then do
  begin
    LET R = A xor B;
    COMMENT: R enthält jetzt die Richtung
    if R = true
    then LET Z = Z + 1;
    else LET Z = Z - 1;
    COMMENT: Z enthält den aktuellen Zählerstand
  end
end;

So, vielleicht nützt es ja was..

W.S.
OP #5245846
Lesenswert?

Klar ist es ein Mechanischer Drehencoder..
soweit ich das gesehen hab sind nur die teuersten optisch.
Ich werde sicher nicht mehr als 10€ für so einen blöden knopf ausgeben.
Diese China dinger haben ~50ct pro stk gekostet.

Pullups hab ich je einen 5,1k Widerstand nach GND

Wenn man meinen Code gründlich ließt sollte die Funktion schon Leicht 
erkennbar sein, da der Code ja minimal ist.



Hier die Funktion einmal erklärt:

Das Programm Läuft in der Schleife und Zeigt die Werte der Variable an, 
die mit dem Encoder Verändert werden kann.

Der Encoder ist an INT0 angeschlossen und lößt beim Drehen einen 
Interrupt aus.
Dieser Inerrupt Arbeitet ein Unterprogramm ab, welches zu Testzwecken 
den 2. Pin Des Drehencoders 1000mal abfragt.

Ist Pin2(Encoder) ein mal in allen Abfragen "High" wird die Variable um 
1 erhöt.
Ist Pin2(Encoder) "low" wird die Variable um 1 verringert.
Ende vom Interrupt

Nocheinmal einfach:
Encoder 1 = 1 & Encoder 2 = 1
Addieren (+)

Encoder1 = 1 & Endoder 2 = 0
Subtrahieren (-)


Meistens ist das signal vom Encoder 1000 von 1000 "High" nur selten 900 
von 1000.
Trotzdem zählt er oft gar nicht(trotz ausgeführten interrupts) oder nur 
sehr unzuverlässig aufwärts (+)

Runter (-) geht zielich gut.


Auf dem Oszi sehen die Signale wie auf der Zeichnung aus !!!
Sehr steile Flanken.

Deshalb ist es für mich völlig unklar, warum es nicht funktioniert.
Gast #5245852
Lesenswert?

Naja, dein Code fragt ja 1000 mal ab, sobald aber ein einziges Mal PIND4 
1 ist, wird X auf 1 gesetzt. Wenn du runterzählst, wird der Wert ja auch 
übernommen, wenn Tc entsprechend hoch ist. Zählst du aber hoch, reicht 
ein einziges Prellen aus, damit die Eingabe komplett verworfen wird.
Böse gesagt ist das kein Entprellen, sondern ein "Verprellen" ;-)
OP #5245858
Lesenswert?

Ja genau wie dein Code Funktioniert auch meiner.

Ich könnte da eine menge weg lassen, was aber nichts ändert.

Die 1000 abfragen waren nur um zu testen ob der encoder prellt.
Das tut er aber nicht.


Hier nochmal abgespeckt:
1
'Encoder Wurde Gedreht
2
Encoder_1:
3

4
'Ist der 2te Pin High ?
5
If Pind.2 = 1 Then
6
Encoder_direction = 1                                       'Drehrichtung = 1
7
Encoder_value = Encoder_value + Encoder_step                'Wert Addieren
8
Else
9
Encoder_direction = 0                                       'Drehrichtung = 0
10
Encoder_value = Encoder_value - Encoder_step                'Wert Subtrahieren
11
End If
12

13
Waitms 20                                                   'Warte um doppeltes zählen zu vermeiden
14
Bitwait Pind.2 , Reset                                      'Warte das Beide Pins "low" sind
15
Bitwait Pind.4 , Reset                                     'Warte das Beide Pins "low" sind
16
Waitms 20
17
Return
OP #5245862
Lesenswert?

Das hast du Falsch verstanden :-)

Wenn Pind.4 einmal "high" gewesen ist soll er ja hoch zählen...
Das Geht aber eben nicht.
Runter geht immer heißt wenn Pind.4 alle 1000 abfragen brav "low" war.

Genau das gegenteil was man erwarten würde :/

Ich kann den Code auch weg lassen...
Ändert überhaupt nichts :(

Dachte halt das der andere Pin Prellt und eventuell "low" ist , wenn er 
"high" sein sollte.
OP #5245893
Lesenswert?

Herbert B. schrieb:
> Andy _. schrieb:
>> Pullups hab ich je einen 5,1k Widerstand nach GND
>
> Dann sind es Pull-Down. Das aber nur nebenbei. Wenn der Encoder aber
> auch (wie normalerweise üblich) zwischen GND und den Eingängen liegt,
> dann schließt Du mit ihm lediglich die Pulldown-Widerstände kurz und
> sonst Nichts.
>
> Herbert

Das ganze ist ein größeres Projekt und ich habe von Schaltplan über 
Platine bis zur Software selber gemacht.

Die Widerstände halten die Eingänge des Controllers low.
Der Drehencoder bekommt dann die Versorgungsspannung und gibt die dann 
weiter.
Hab wie gesagt alles schom mit dem Oszi gemaessen.
Passt alles was an den Pins ankommt.
Gast #5248916
Lesenswert?

Wie soll das überhaupt mit zig ms Blockierzeiten im Code funktionieren? 
Zwischen 2 Rasten machen manche Drehencoder 4 Takte und die warten da 
nicht auf ein Basicprogramm.
Die ISR werden vermutlich zu lange verzögert bekommen dann einen eher 
zufälligen Pegel zur Auswertung.
Gast #5248931
Lesenswert?

Andy _HV & µC schrieb
>Ist Pin2(Encoder) ein mal in allen Abfragen "High" wird die Variable um
>1 erhöt.
>Ist Pin2(Encoder) "low" wird die Variable um 1 verringert.
>Ende vom Interrupt

Ob es ein Vorwärts oder Rückwärtsschritt ist, läst sich
eigentlich nur erkennen wenn man sich das vorhergehende
Bitmuster gemerkt hat und mit dem aktuellen vergleicht,
siehe Tabelle.


Schritt Vorwärts Rückwärts
    0     00       00
    1     10       01
    2     11       11
    3     01       10
    4     00       00
    5     10       01
    6     11       11
    7     01       10
    8     00       00
    9     10       01
   10     11       11
   11     01       10
#5248977
Lesenswert?

Nur der Verständnis halber:

Bascom hat einen integrierten Befehl, um Drehgeber auszuwerten.

https://avrhelp.mcselec.com/index.html?encoder.htm

Der Beispiel-Code ruft das ohne Interrupt einfach zyklisch in seiner 
Hauptschleife auf, und es finden sich haufenweise andere Beispiele, die 
einen Timer-Interrupt verwenden, um den "ENCODER"-Befehl zyklisch 
aufzurufen.

Mir ist kein einziges Bascom-Beispiel über den Weg gelaufen, das da auf 
die Idee kommt einen Flanken-Interrupt mit dranzumogeln.

Warum versuchst du das? NIH-Syndrom? Angst vor den fertigen 
Bascom-Routinen, oder dass du sonst nicht schnell genug drehen kannst?
OP #5249115
Lesenswert?

Andreas S. schrieb:
> Andy _. schrieb:
>> Passt alles was an den Pins ankommt.
>
> Schön.
>
> Und nach Deiner eigenen Ansicht ist Dein Softwarekonzept auch ganz toll
> und nicht etwa der letzte Dreck. Dann ist ja auch hier alles in Ordnung.
> Also wird Deine Lösung wohl auch richtig funktionieren. Herzlichen
> Glückwunsch!

Eine vernünftige Antwort währe auch schön, aber vernünftige sowie 
höfliche beiträge kann sich ja nicht jeder leisten.
So eine Hilfeeinstellung und unhöflichkeit braucht kein Forum.


Nun zu meiner Problemlösung:

Der Encoder hat wohl tatsächlich minimal geprellt, aber schneller als 
mein Altes Oszi messen kann (wenifer als ein paar uS).
Zusätzlich habe ich die Software so abgeändert, das das ganze jetzt 
Funktioniert.

Neuer Code:
1
Encoder_1:
2
Disable Int0
3
Waitus 100
4
Bitwait Pind.2 , Reset
5

6
If Pind.4 = 1 Then
7
Encoder_direction = 0
8
Encoder_value = Encoder_value - Encoder_step
9
Else
10
Encoder_direction = 1
11
Encoder_value = Encoder_value + Encoder_step
12
End If
13
Waitms 2
14
Bitwait Pind.2 , Reset
15
Bitwait Pind.4 , Reset
16
Enable Int0
17
Return
OP #5249132
Lesenswert?

batman schrieb:
> Jepp, die langen Blockierzeiten rausgeschmissen, Glückwunsch.

Die Wartezeiten waren nicht so das grundlegende problem eher die art wie 
ich den encoder abgefragt hab.

Ich habe den Vorgang mit dem ersten Bitwait komplett umgedreht und messe 
nun den zustand, wenn der erste pin "low" geht.

So ist genügend zeit für den Controller das Unterprogramm anzuspringen.

Der 2 pin fällt allerdingens schon 0,5ms nach dem ersten was bei 8Mhz 
Taktrate aber noch nicht allzu zeitkritisch ist.
#5249185
Lesenswert?

Andy _. schrieb:
> Die Wartezeiten waren nicht so das grundlegende problem eher die art wie
> ich den encoder abgefragt hab.

Die Wartezeiten verstehe ich nicht.
Wenn Interrupt, dann solle das Teil hardwaremäßig entprellt sein!
(Schaltungsvorschläge meist in den Datenblättern enthalten)

Die Verarbeitung im IR sieht dann bei mir (in C) so aus:
1
    if(ENC_IR){
2
        if(ENC_DIR == ENC_DIR_UP)  flags.encUp = 1;
3
          else flags.encDown = 1;
4

5
        mENC_IR_RST();          // clear IR flag
6
        return;
7
    }
Mehr braucht man meiner Meinung nach nicht.

(ENC_DIR ist der Pin, ENC_DIR_UP der Pegel und flags ein Bitfeld)
(((if(ENC_IR) weil nur ein Vector für viele IR)))

Wenn das Entprellen nicht in der Hardware gemacht werden soll,
dann besser gleich die hier schon mehrfach verlinkte Polling Methode...
Gast #5250022
Lesenswert?

Εrnst B schrieb:
>Was ist an der integrierten Lösung so schlecht, dass man das unbedingt
>selber implementieren will?

Manchmal will man etwas Lernen, man will Spielen und Experimentieren.
Man möchte herausfinden warum etwas funktioniert oder auch
nicht funktioniert. Es sind nicht alle Experten und die Experten waren
auch nicht von Anfang an Experten.
Gast #5250036
Lesenswert?

Günter Lenz schrieb:
> Manchmal will man etwas Lernen, man will Spielen und Experimentieren.
> Man möchte herausfinden warum etwas funktioniert oder auch
> nicht funktioniert. Es sind nicht alle Experten und die Experten waren
> auch nicht von Anfang an Experten.

Zum Lernen könnte man ja auch den Artikel vom Drehencoder lesen. Es wird 
seit Jahren immer wieder versucht den Int für Flankenwechsel zu utzen. 
Es wird seit Jahren immer wieder davon abgeraten. Er hat dan Fehler 
durch try and error zufällig beseitigt. Ich bezweifel das er gelernt hat 
was da in Hardware passiert. PullUP sind ja auch keine PullDown, die er 
benutzt.
Gast #5250070
Lesenswert?

Stephan schrieb:
> Zum Lernen könnte man ja auch den Artikel vom Drehencoder lesen.

Dazu müßte dieser Artikel aber erstmal aufgeräumt und in Ordnung 
gebracht werden.

Wer einen Artikel schreibt und dort zum Besten gibt, daß er nicht 
versteht, warum heutige Drehgeber so aufgebaut sind, wie sie sind 
(zitat: "Umso merkwürdiger ist es, dass hier die Rastpunkte oft genau 
auf dem Pegelwechsel einer Spur liegen, meist Spur B."), oder gar sowas 
schreibt (noch ein Zitat: "Es wird bisweilen die Auffassung vertreten, 
dass mit Hilfe von sog. Pin Change Interrupts Rechenzeit gespart werden 
kann."), der hat mit seinen Darstellungen wohl kaum einem Leser wirklich 
die nötigen Erkenntnisse gebracht.

W.S.
Gast #5250110
Lesenswert?

Stephan schrieb:
> Zum Lernen könnte man ja auch den Artikel vom Drehencoder lesen. Es wird
> seit Jahren immer wieder versucht ...

... Engstirnigkeit zu predigen: "Du mußt das genau so machen, sonst 
biste doof!"

W.S. schrieb:
> der hat mit seinen Darstellungen wohl kaum einem Leser wirklich
> die nötigen Erkenntnisse gebracht.

Wo er Recht hat, hat er Recht!

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren