Drehgeber Codeschnipsel gibts ja schon jede Menge hier im Forum.
Allerdings sind diese Methoden meiner Meinung nach alle eher für Motor
Positionierungs Aufgaben geeignet und weniger für eine präzise
Menüsteuerung.
Hier deshalb mal eine etwas andere Variante speziell optimiert für
rastende Drehgeber mit einem Zwischenschritt. Eventuell gibt es auch
Drehgeber mit mehr als einem Zwischenschritt zwischen den Rastpunkten.
Mir sind bisher aber nur solche mit einem Zwischenschritt untergekommen.
Trotzdem sollte man - bevor man jetzt einfach den Code kopiert und sich
dann wundert warum es nicht funzt - den Drehgeber einmal ganz genau
untersuchen.
Also bitte als erstes einfach einmal die Pins einlesen und über LCD oder
serielle ausgeben.
1
// drehgeber an PD2 und PD3
2
DDRD&=~(1<<PD2)|(1<<PD3);
3
PORTD|=(1<<PD2)|(1<<PD3);
4
5
uint8_tz=0;
6
7
while(1)
8
{
9
uint8_ty=(PIND>>2)&3;
10
11
if(y!=z&3)
12
{
13
z=(z<<2)|y;
14
15
lcd_gotoxy(0,0);
16
17
for(inti=0;i<8;i++)
18
{
19
if(z&(1<<(7-i)))
20
{
21
lcd_write('1');
22
}
23
else
24
{
25
lcd_write('0');
26
}
27
}
28
}
29
}
Dreht man nun den Geber sollte ein erkennbares Muster angezeigt werden:
0010110100... oder 0001111000... je nach dem in welche Richtung gedreht
wird
Bei meinen Drehgebern hat sich nun folgendes ergeben:
- Rastpunkte liegen auf 00 und 11
- dazwischen ist immer ein Zwischenschritt 01 oder 10
Sollte der Drehgeber auf 01 und 10 rasten ist das nicht weiter tragisch
und kann über XOR 1 korrigiert werden.
Falls der Drehgeber nur drei verschieden Zustände ausgibt anstatt vier,
sind die Anschlüsse höchstwahrscheinlich vertauscht. TIP: Hat man den
Geber aus einer alten Stereoanlage ausgelötet oder aus einem anderen
Grund kein Datenblatt zur Hand, kann man den Geber and drei IO Pins
anschließen. Dann der Reihe nach einen Pin auf Masse ziehen und die
beiden anderen Pins testen. Bei einer Variante sollte sich das korrekte
Graycode Muster mit vier Zuständen ergeben.
Schaut man sich das ausgegebene Bitmuster genau an sollte nun eigentlich
auch klar sein wie man das Muster in + und - Impulse umwandeln kann:
aus 001011 und 110100 wird -
aus 000111 und 111000 wird +
1
int8_trotary=0;
2
3
voidrotary_check(void)
4
{
5
staticuint8_tz=0;// shift register, speichert die letzten drei zustände
6
7
uint8_ty=(PIND>>2)&3;// beide drehgeber pins einlesen ( hier PD2 und PD3 )
8
9
if((z&3)!=y)// testen ob sich etwas geändert hat
10
{
11
z=((z<<2)|y)&0x3f;// die zwei neuen bits reinshiften und ergebnis auf 6 bits begrenzen
12
13
// muster vergleichen und ausgabe wert gegebenfalls hoch oder runter zählen
14
if(z==0b001011||z==0b110100)
15
rotary--;
16
elseif(z==0b000111||z==0b111000)
17
rotary++;
18
}
19
}
Das Praktische ist dass bei dieser Methode einfaches Kontaktprellen
automatisch ignoriert wird. (Wirklich!) Tiefpass filter sind trotzdem zu
empfehlen damit man auch bei Gewitter (o.Ä.) möglichst keine Störungen
bekommt.
Außerdem gibt's keine Halbschritt- und Rundungsfehler (wie bei der
Tabelle-und-dann-einfach-durch-zwei-teilen-Methode) da hier automatisch
immer mit den Rastpunkten syncronisiert wird.
Wie und wann man die Abfrage Routine nun aufruft bleibt einem selbst
überlassen. Bei den neueren Atmels bietet sich natürlich der Pin Change
Interrupt an. Ansonsten kann man aber auch entweder im Hauptprogramm
oder über einen Timer Interrupt pollen. Ich habe hier gerade einen alten
M16 deshalb habe ich den geber an PD2/3 (INT0/1).
1
// alter atmega16 ohne pinchange interrupt, deshalb drehgeber an INT0 und INT1
2
ISR(INT0_vect)
3
{
4
rotary_check();
5
}
6
7
ISR(INT1_vect)
8
{
9
rotary_check();
10
}
11
12
intmain(void)
13
{
14
DDRD=(1<<PD7);// led
15
PORTD=(1<<PD2)|(1<<PD3)|(1<<PD7);// pullups für drehgeber und led ein
16
17
MCUCR|=(1<<ISC00)|(1<<ISC10);// INT0 + INT1 any edge
18
GICR|=(1<<INT0)|(1<<INT1);
19
20
asmvolatile("sei");
21
22
.
23
.
24
.
25
}
Wenn man einen Interrupt Handler verwendet zur Abfrage (Timer oder
PinChange egal) sollte man übrigens nicht direkt auf den Ausgabe Wert
zugreifen. Sondern - damit auch wirklich keine Schritte verloren gehen -
besser folgenden Schnipsel verwenden:
1
// zum sicheren auslesen des drehgeber wertes im hauptprogram
2
int8_trotary_get_and_clr(void)
3
{
4
int8_tr;
5
asmvolatile(
6
"in __tmp_reg__, __SREG__ \n\t"// cpu status register sichern
7
"cli \n\t"// interrupts aus
8
"lds %0, rotary \n\t"// wert laden
9
"sts rotary, __zero_reg__ \n\t"// und löschen
10
"out __SREG__, __tmp_reg__ \n\t"// interrupts wieder zulassen
m4444x schrieb:> Das Praktische ist dass bei dieser Methode einfaches Kontaktprellen> automatisch ignoriert wird.
Zum Glück macht das der Gray-Code schon von sich aus, weil der
Hammingabstand benachbarter Code-Worte 1 ist. Dafür wurde der Code
erfunden ;-)
F.G. schrieb:> m4444x schrieb:>> Das Praktische ist dass bei dieser Methode einfaches Kontaktprellen>> automatisch ignoriert wird.>> Zum Glück macht das der Gray-Code schon von sich aus, weil der> Hammingabstand benachbarter Code-Worte 1 ist. Dafür wurde der Code> erfunden ;-)
Ja schon, aber.. :)
Angenommen ich habe hier einen richtig heftig prellenden Drehgeber. Das
Muster an den Pins des µc könnte dann ungefähr so aussehen:
00 01 00 01 00 01 11 01 11 01 11 01 11
Wertet man nun die jeweils vier letzten Bits aus ergibt sich daraus:
++ -- ++ -- ++ ++ -- ++ -- ++ -- ++
00 01 00 01 00 01 02 01 02 01 02 01 02
Jeder Preller ergibt also einen Puls. Natürlich kann man das
kompensieren mit einer Division durch 2. Problematisch wird es dann
allerdings trotzdem wenn irgendwo ein Puls verloren gehen sollte. Dann
stimmt die Synchronisierung mit den Rastpunkten des Drehgebers nicht
mehr überein. Das äußert sich dann z.B so dass zwar ein Wert eingestellt
werden kann, nimmt man allerdings die Hand vom Knopf, springt der Wert
einen hoch oder runter. Oder aber man drückt den Knopf runter um eine
Eingabe zu bestätigen und schwups quasi gleichzeitig mit der Bestätigung
verspringt der Wert um +-1.
Also aufpassen bei der Division dass kein Bit unter den Tisch fällt.
Ebenso beim Abfragen des Wertes und anschließendem Nullsetzen. Funkt der
Interrupt dazwischen ist die Synchronisierung weg.
Die oben vorgestellte Routine vermeidet alle diese Probleme automatisch.
Gleiche Sequenz an den Eingängen:
00 01 00 01>00 01 11<01 11 01 11 01 11
ergibt:
++
00 00 00 00 00 00 01 01 01 01 01 01 01
Wie man sieht kann ein Bit vor- und zurück prellen soviel es will. Ein
Impuls wird erst generiert wenn auch das zweite Bit kippt. Eine Division
mit Auswertung des Carry Bits ist nicht nötig. Außerdem kann die
Synchronisierung mit den Rastpunkten nicht verloren gehen.
Einen Nachteil gibt es natürlich auch: Funktioniert nur mit rastenden
Drehimpulsgebern mit zwei Bit Schritten pro Rastung bzw mit einem
"Zwischenschritt".
Sind es mehr Schritte pro Rastung müsste man wieder durch zwei
dividieren. Oder man erweitert die Muster Erkennung auf 10 bit (zB
0010110100) was aber keine Vorteile bringen würde. Im Gegenteil bei mehr
als 6 Bits ist das ganze nicht mehr automatisch immun gegen
Kontaktpreller.
m4444x schrieb:> Jeder Preller ergibt also einen Puls.
Genau genommen führt jeder Preller (z.B. 00 01 00) zu zwei
Zustandsänderungen, die sich gegenseitig aufheben.
F.G. schrieb:> m4444x schrieb:>> Jeder Preller ergibt also einen Puls.>> Genau genommen führt jeder Preller (z.B. 00 01 00) zu zwei> Zustandsänderungen, die sich gegenseitig aufheben.
Bei großen Wertebereichen spielt das sicher keine Rolle, ob es zwei
Pulse gibt die sich zu 0 addieren oder ob es gar keinen Puls gibt.
(Motorsteuerung, meinetwegen auch Volume Regler einer Stereoanlage)
Nervig wird es aber unter Umständen, wenn man einen von drei Menu
Punkten selektieren will...
Hier nochmal der Code im Context. Eine kleine Beispiel-App, bei der ein
geheimer Code zum Öffnen eines Safe abgefragt wird. Der Code wird dabei
ganz klassisch über links und rechts Drehung des Drehgebers eingestellt.
Habe jetzt spaßeshalber mal bei Reichelt noch ein paar Drehimpulsgeber
bestellt. Ergebnis:
STEC11B02 :: ALPS STEC11B Drehimpulsg., 15/30, horiz., OT
rastet auf 00 und 11, also jeweils ein Zwischenschritt pro Rastung
bestens geeignet für die oben vorgestellte Methode (außerdem ca. 50 Cent
günstiger)
STEC11B09 :: ALPS STEC11B Drehimpulsg., 20/20, horiz., MT
rastet nur auf 11, also jeweils drei Zwischenschritte pro Rastung
Wozu das gut sein soll, die drei Zwischenschritte, ist mir noch nicht so
ganz klar. Ein einfaches Vergleichen mit 1101000111 (+1) bzw. 1110001011
(-1), bringt hier nichts, da Kontaktpreller im mittleren Bereich (bei
00) dazu führen, dass das Muster nicht erkannt wird. Wahrscheinlich sind
diese Geber eher geeignet für die andere Methode, bei der die
Zwischenschritte addiert werden und anschließend durch die Anzahl der
Schritte pro Rastung geteilt wird.
Der Panasonic von Pollin ist übrigens auch einer mit einem
Zwischenschritt. Rastpunkte liegen ebenfalls bei 00 und 11. Allerdings
neigt dieser Geber scheinbar etwas zum Wackeln. Ich habe zumindest einen
Rastpunkt gefunden, bei dem ich durch leichtes Antippen zwischen 00 und
01 hin- und herschalten konnte. Sollte aber kein Problem darstellen,
wenn - wie bei der hier gezeigten Methode - automatisch nur auf 00 und
11 synchronisiert wird.
Moin,
ich bin noch ein Neuling, was die Programmierung mit dem Atmel angeht.
Daher bitte ich um Nachsicht. Ich habe ein ähnliches Problem mit dem
Drehgeber
http://www.reichelt.de/STEC12E08/3/index.html?&ACTION=3&LA=446&ARTICLE=73923&artnr=STEC12E08&SEARCH=STEC12E08
gehabt. Ich konnte mit den hier vorgestellten Codeschnipsel einfach
nicht präzise genug einstellen. Vor dem 'einrasten' wurde bereits
gestellt.
Daher habe ich den mal einfach 'ausgelesen', indem ich entsprechende
Routinen programmiert habe und bin auf diese beiden Bitmuster gestoßen:
#define RIGHT 0b11010010
#define LEFT 0b11100001
Ich hänge einfach mal meinen C-Code ran und ein Bild von der Hardware.
Es klappt soweit ganz gut. Nur bin ich mir ziemlich sicher, dass gerade
am Code noch ordentlich was zu optimieren gibt. Neben einigen magic
numbers können da bestimmt noch ganz böse Anfängerfehler wegoptimiert
werden, weil ich durch die ganzen Register noch nicht richtig
durchblicke. Eure (hoffentlich nicht vernichtende) Meinung würde mich
sehr interessieren.
Danach würde ich das Ganze dann mal mit Assembler angehen....
LG, Heiko
Als ich den Quellcode mit Atmel Studio kompiliert habe, wurden
Linksdreher nicht mehr erkannt. Problem war, dass ich für 'enc_delta'
negative Werte verwendet hatte, die nicht in einer Switch-Anweisung
erkannt werden können.
Daher habe ich den Code erneut angepasst.
Nun muss ich nur noch die richtigen Kondensatoren für den Uhrenquarz
finden. Läuft ziemlich ungenau. Auf dem Breadboard mit einem anderen
ATmega88pa und einen anderen Uhrenquarz des gleichen Fabrikats und mit
keiner 4x7 Segment lief es sehr genau. Vielleicht nehme ich die Teile
vom Breadboard und löte sie auf die Lochplatine.
Heiko J. schrieb:> Problem war, dass ich für 'enc_delta' negative Werte verwendet hatte,> die nicht in einer Switch-Anweisung erkannt werden können.
Auch wenn's schon 'ne Weile her ist:
Natürlich können in einer Switch-Anweisung auch negative Werte verwendet
werden -- da geht alles, was int ist.
@Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
>Natürlich können in einer Switch-Anweisung auch negative Werte verwendet>werden -- da geht alles, was int ist.
INT-eressant!