Das Thema Drehgeber ließ mir nun doch keine Ruhe mehr. Also habe ich einen von diesen (Billig-)Drehgeber von Pollin (Best.Nr. 240 313) ausgekramt und an mein AVR-Bastelboard nach angehängtem Plan angeschlossen.
R1 ist lediglich ein Schutzwiderstand, falls einer der Eingänge PD4..PD6 versehentlich doch mal als Ausgang konfiguriert ist. Es befinden sich bis auf die aktivierten internen PullUp-Widerstände des AVR keine weiteren Bauteile (zwecks Entprellung) im Signalweg des Dreh-Encoders.
Wie ich schon im vorigen Post schrieb, setze ich diesen Drehgebertyp in einem Projekt in der von dort beschriebenen Weise mittels externem Interrupt ein. Das Prinzip und auch der Nachteil dieser Lösung ist, dass jeder Signalwechsel am Interrupteingang die zugehörige Interrupt-Service-Routine (ISR) ausführen lässt. Was heute noch scheinbar einwandfrei funktioniert, kann bei einem ausgelutschtem Drehgeber mit ggf. starkem Kontaktprellen zu einer unkontrollierbaren Prozessorlast führen. Aus diesem Grund werden in Expertenkreisen generell Kontakte mittels einer Timer-Interrupt gesteuerten Routine entprellt.
Die Bascom-eigene Lösung mit dem Encoder-Befehl arbeitete bei mir noch nie zufriedenstellend - egal ob in der Hauptprogrammschleife oder per Timerinterrupt ausgeführt - traten immer Sprünge oder Impulsverluste am Drehgeber auf. Daraufhin habe ich die untenstehende Lösung ausgearbeitet. Beim Testen konnte ich bisher absolut keine Fehler erkennen. Kritik und Verbesserungsvorschläge sind gerne willkommen.
Lösungsweg:
Zur Entprellung werden die beiden Drehgebersignalpaar (A,B) alle 0.5ms in der Timer0-ISR abgefragt und im Schieberegister-Byte ROTARY im Format AAAABBBB abgelegt. Nur wenn alle A's und alle B's gleich sind, also (0000_0000), (0000_1111), (1111_1111) oder (1111_0000) gilt das Encodersignalpaar als stabil.
Außer dem Kontaktprellen besitzt dieser Drehgebertyp aber noch ein weiteres zu beseitigendes Übel:
Grundsätzlich stehen in den Rastungen des Drehknopfes die Signalpaarungen (A,B)=(0,0) bzw. (A,B)=(1,1) stabil an. Einige der 16 Rastungen sind aber so unpräzise, das schon durch bloßes Anfassen des Drehknopfes ein Signalwechsel zu (A,B)=(1,0) bzw. (A,B)=(0,1) und beim Loslassen wieder zurück nach (A,B)=(0,0) bzw. (A,B)=(1,1) erfolgt. Dies wird in vielen Routinen (so auch in meiner vom Externen Interrupt gesteuerten Routine) fälschlicherweise als Drehbewegung des Knopfes interpretiert.
Um diesen Fehler zu beheben muß eine Encoderauswertung drei aufeinanderfolgende Signalpaarungen berücksichtigen. Bei mir geschieht das mittels den Variablen
ROTARY: Signalpaarung (A,B) in der aktuellen Rastung
ROTARY1: (A,B) im Übergang zwischen zwei Rastungen (Drehrichtungserkennung)
ROTARY2: Signalpaarung (A,B) in der vorherigen Rastung
Befindet sich der Drehknopf in der Rastung mit (A,B)=(0,0) bzw. (A,B)=(1,1) so muß bei einer Drehung um eine Rastung anschliessend genau die entgegengesetzte Signalpaarung vorliegen, nämlich (A,B)=(1,1) bzw. (A,B)=(0,0). D.h. eine Drehbewegung kann nur stattgefunden haben wenn ROTARY ungleich ROTARY2 ist. Abhängig von der Signalpaarung ROTARY1 beim Übergang zwischen den Rastungen (A,B)=(0,1) bzw. (A,B)=(1,0) wird die Drehrichtung erkannt.
Zur Auswertung des Encoders werden nach vollzogener Rastung entsprechend der Drehrichtung die Flags R_Flag bei Rechtsdrehung und L_Flag bei Linksdrehung gesetzt und der Encoderwert ROTARY_VALUE bei Rechtsdrehung inkrementiert bzw. bei Linksdrehung dekrementiert und können in der Hauptprogrammschleife abgefragt und zurückgesetzt werden.
Code:
'*******************************************************************************
'*** Testprogramm für POLLIN Dreh-Encoder PANASONIC EVEQDBRL416B ***
'*** Best.Nr. 240 313 ***
'*******************************************************************************
'*** Author: Screwdriver ***
'*** Datum: 28.06.2009 ***
'*******************************************************************************
'*******************************************************************************
'*** Konfiguration ***
'*******************************************************************************
'AVR
$regfile = "m644def.dat"
$crystal = 20e6
'System-Timer für periodische Encoder-Abfrage
Config Timer0 = Timer , Prescale = 256
On Timer0 Isr_timer0
Enable Timer0
Const Timer0_reload = 217 'Wert für 0.5ms
'Drehencoder
Config Portd.4 = Input : Set Portd.4 : Rotary_button Alias Pind.4 'Taste
Config Portd.5 = Input : Set Portd.5 : Ph_a Alias Pind.5 'Encoder Signal A
Config Portd.6 = Input : Set Portd.6 : Ph_b Alias Pind.6 'Encoder Signal B
'*******************************************************************************
'*** Variablen ***
'*******************************************************************************
'Variablen
Dim Rotary As Byte 'Aktuelles Rasterwert (A,B)
Dim Rotary1 As Byte 'Übergangswert (A,B) zwischen Rastungen
Dim Rotary2 As Byte 'Letzter Rasterwert (A,B)
Dim R_flag As Bit , L_flag As Bit 'Flags für Links- / Rechtsdrehung
Dim Rotary_value As Byte 'Zahlenwert des Encoders
Dim Rotary_key As Byte : Rotary_key = &HFF 'Schieberegister Taste, &HFF: nicht gedrückt
Dim Press_flag As Bit 'Flag Taste wurde gedrückt
Dim Release_flag As Bit 'Flag Taste wurde losgelassen
'*******************************************************************************
'*** HAUPTPROGRAMM ***
'*******************************************************************************
Enable Interrupts
Do
'Beispiel zur Auswertung der Flags und des Encoder-Werts im Hauptprogramm
If R_flag = 1 Then 'Drehung nach Rechts
Reset R_flag
Print "Rechts"
Print Rotary_value
End If
If L_flag = 1 Then 'Drehung nach Links
Reset L_flag
Print "Links"
Print Rotary_value
End If
If Press_flag = 1 Then 'Taste wurde gedrückt
Reset Press_flag
Print "gedrueckt"
End If
If Release_flag = 1 Then 'Taste wurde losgelassen
Reset Release_flag
Print "losgelassen"
End If
Loop
End
'*******************************************************************************
'*** ISR_TIMER0 ***
'*** Periodische Auswertung des Dreh-Encoders ***
'*******************************************************************************
Isr_timer0:
Timer0 = Timer0_reload
Shift Rotary , Left , 1 'Schieberegister für Entprellung (A,B)
Rotary.4 = Ph_a 'Phase A lesen -> Bit 4
Rotary.0 = Ph_b 'Phase B lesen -> Bit 0
If Rotary <> Rotary2 Then 'Neues Signalpaar (A,B) ?
Select Case Rotary
Case &B0000_0000 'Aktuell (A,B)= (0,0)
If Rotary1 = &B1111_0000 Then 'Übergang war (A,B)= (1,0), also
Set R_flag 'Rechtsdrehung
Incr Rotary_value 'Encoder-Wert erhöhen
Elseif Rotary1 = &B0000_1111 Then 'Übergang (A,B)= (0,1), also
Set L_flag 'Linksdrehung
Decr Rotary_value 'Encoder-Wert verringern
End If
Rotary2 = &B0000_0000 'Aktuelles (A,B) in vorheriges (A,B)
Case &B0000_1111 'Übergang (A,B)= (0,1) zwischen Rastungen
Rotary1 = &B0000_1111 'erkennen und in abspeichern
Case &B1111_0000 'Übergang (A,B)= (1,0) zwischen Rastungen
Rotary1 = &B1111_0000 'erkennen und abspeichern
Case &B1111_1111 'Aktuell (A,B)= (1,1)
If Rotary1 = &B0000_1111 Then 'Übergang war (A,B)= (0,1), also
Set R_flag 'Rechtsdrehung
Incr Rotary_value 'Encoder-Wert erhöhen
Elseif Rotary1 = &B1111_0000 Then 'Übergang war (A,B)= (1,0), also
Set L_flag 'Linksdrehung
Decr Rotary_value 'Encoder-Wert verringern
End If
Rotary2 = &B1111_1111 'Aktuelles (A,B) in vorheriges (A,B)
End Select
End If
Shift Rotary_key , Left , 1 'Schieberegister für Entprellung Taste Encoder
Rotary_key.0 = Rotary_button 'Taste lesen -> Bit 0
If Rotary_key = &B1000_0000 Then 'Taste wurde gedrückt
Set Press_flag
Elseif Rotary_key = &B0111_1111 Then 'Taste wurde losgelassen
Set Release_flag
End If
Return
Lesezeichen