Ich habe den Encoder-Code von Peter Dannegger von hier:
http://www.mikrocontroller.net/articles/Drehgeber
ausprobiert und der funktioniert super, nur muss man (bei meiner
Hardware) am Encoder nicht besonders schnell drehen (von Hand) bis
Schritte verloren gehen bzw. nichts mehr ausgewertet wird bis man wieder
langsamer dreht. Der Code in der Hauptschlaufe wo der Encoder aufgerufen
wird sieht wie folgt aus:
1
signedchartemp=encode_read2();
2
if(temp==1){
3
// Value is increasing
4
FREQ-=FREQSTEP;
5
}elseif(temp==-1){
6
// Value is decreasing
7
FREQ+=FREQSTEP;
8
}
9
SetFont(FIX_FONT12X16);
10
LCDSoftText(dtostrf(FREQ,2,4,string),0,15);
Ich habe schon versucht, den Interrupt häufiger auftreten zu lassen als
im Originalcode von Peter, aber ohne merkbaren Unterschied.
Sieht hier jemand Verbesserungspotential? Ich bin nicht einmal sicher,
wo der Flaschenhals genau ist...
Grüsse
Martin
Kannst du mal die komplette Funktion posten, es fehlen die
Funktionsköpfe um den Zusammenhang zu erkennen.
falls du
SetFont(FIX_FONT12X16);
LCDSoftText(dtostrf(FREQ,2,4,string),0,15);
im Interrupt machst, dann hast du ein Problem.
Im Interrupt bitte nur zählen, sonst nix.
Falls du den Encoder pollst, dann ist da der Flaschenhals.
Hilft dir das weiter?
Hab mal kurz das Beispiel angeschaut. Da läuft ja ein Timer... whuaaahh,
der den Encoder abfrägt.
Sinnigerweise setzt man einen Interrupt auf die Flanken der
Encodersignale und wertet im Interrupt die Signale aus.
Was hast du denn für einen Encoder, wie oft kommen die Pulse?
Interrupt auf die Flanken des Encoders macht nur Sinn wenn dieser nicht
prellt...sonst haste einen Haufen unnützer Interrupts.
Die Polling-Methode von Peda ist schon echt gut, und mit 1kHz
Interrupt-Frequenz hatte ich dann auch keine verlorenen Pulse mehr
(zumindest nicht bei schneller Handdrehung).
Wichtig ist halt nur die Zählung im Int zu machen, alles andere in der
Hauptschleife... und LCDs können da die entscheidende Bremse sein.
Gruß
Fabian
>>> in der Hauptschlaufe wo der Encoder aufgerufen wird
Dort ist die Encoder-Routine am flaschen Platz. Die gehört in den
Timer-Interrupt. Das steht aber in dem angesprochenen Artikel... :-/
>> Da läuft ja ein Timer... whuaaahh, der den Encoder abfrägt.
Das funktioniert völlig problemlos, ich habe den code noch ein wenig
erweitert, dass bei schnellem Drehen am Knopf der Wert sich schneller
ändert (dynamische Geschwindigkeitsanpassung). Das fühlt sich dann super
an... ;-)
> Ich nochmal, die Encoder-Schaltung sieht bei mir so aus, wie im Anhang.
Meines Erachtens unnötig viel Aufwand...
> Sinnigerweise setzt man einen Interrupt auf die Flanken der> Encodersignale und wertet im Interrupt die Signale aus.
Nein, das machen nur die Leute die das Prinzip nicht verstanden haben.
Interrupt ist nicht nur ungenauer, es verursacht eine nicht
vorhersehbare Systemlast. Das will man vermeiden.
> Wichtig ist halt nur die Zählung im Int zu machen, alles andere in der> Hauptschleife...
Yep. Ich habe so Motor mit Encoder laufen die selbst in monatelangen
Dauerbetrieb keine Position verlieren.
> und LCDs können da die entscheidende Bremse sein.
Garantiert.
Olaf schrieb:
> Nein, das machen nur die Leute die das Prinzip nicht verstanden haben.> Interrupt ist nicht nur ungenauer, es verursacht eine nicht> vorhersehbare Systemlast. Das will man vermeiden.
Das kann ich jetzt nicht nachvollziehen. Der Interrupt kommt doch nur,
wenn der Encoder sich ändert und stellt sicher, dass ich alle Pulse
mitbekomme. OK, die Entprellungsgeschichte muss man berücksichtigen,
aber was nützt mir eine vorhersehbare Systemlast, wenn dann nicht mehr
gezählt wird.
Ein incrementaler Encoder arbeitet meist in der Praxis erst dann
zufriedenstellend, wenn an einem Eingang der Zustand auch "flackern"
darf, ohne dass die Auswertung dabei durcheinander kommt. Mechanisch
läßt sich das "Flackern" nämlich nicht vermeiden, einzig die Auswertung
muß es beherrschen. Reine Interrupt-Lösungen, ohne eine geeignete
Hardware davor, können dieses Problem nicht lösen. Als Folge wird der
Zähler ungenau, oder er läuft plötzlich in eine Richtung los.
Daniel V. schrieb:
> wenn der Encoder sich ändert und stellt sicher, dass ich alle Pulse> mitbekomme. OK, die Entprellungsgeschichte muss man berücksichtigen,
Wie machst du denn das?
Wie berücksichtigst du denn die Entprellerei?
Schau dir doch mal die ganzen 'Entprell-Lösungen' an. Meistens bedeutet
das: Im Interrupt ist ein delay, der das System ein paar Millisekunden
blockiert, dann werden die Interrupt-Flags zurückgesetzt, der Puls
gezählt und die ISR ist beendet.
Dreht dein Benutzer heftig am Rad und prellt der Encoder im vertretbaren
Rahmen, dann befindet sich der µC praktisch nur noch im delay, den du
brauchst um den Eingang zu entprellen. Dementsprechend steht dein
restliches Programm still. Nichts geht mehr.
Baust du dein Programm hingegen so auf, dass der µC alle paar
Zig-Nano-Sekunden mal kurz einen Blick auf die Eingänge wirft, muss
nicht gewartet werden. Bei einem normalen Encoder kann dein Benutzer gar
nicht so schnell kurbeln, als das dir das bei dem kurzen
Nachseh-Intervall entgehen würde. Baust du ihm auch noch eine dynamische
Geschwindigkeitsanpassung ein, dann will er auch gar nicht mehr schnell
kurbeln.
Praktisch alles, was mit Benutzerinteraktion zu tun hat, ist aus Sicht
eines µC eine extreme Zeitlupe. Es reicht völlig aus, ab und an wieder
mal nachzusehen, ob der Benutzer irgendetwas gemacht hat. Wenn du wissen
willst, ob du deinen Rasen mähen musst, schaust du ja auch nur 1 mal am
Tag nach und bleibst nicht Rasenmäher bei Fuss neben der Grünfläche
stehen um nur ja den Zeitpunkt nicht zu verpassen.
> Bei einem normalen Encoder kann dein Benutzer gar> nicht so schnell kurbeln, als das dir das bei dem kurzen> Nachseh-Intervall entgehen würde.
Das passiert leider doch, wenn der Encoder genau an der Stelle eines
Flankenwechsels zum stehen kommt. Genau dann "flackert" ein Signal und
würde Deinen Interrupt hoffnungslos überfordern. Wird dann vor dem
Weiterdrehen der letzte Flankenwechsel nicht erkannt, dann macht Dein
Zähler einen Fehler. - So geht es also nicht vernümftig.
Ok das gab ja ganz schön viele Antworten. Will mal versuchen so gut wie
möglich darauf einzugehen. Zuerst mal meine Interrupt-Routine:
1
ISR(TIMER0_COMP_vect){// 1ms for manual movement
2
3
// encoder
4
int8_tnew,diff;
5
6
new=0;
7
if(PHASE_A)
8
new=3;
9
if(PHASE_B)
10
new^=1;// convert gray to binary
11
diff=last-new;// difference last - new
12
if(diff&1){// bit 0 = value (1)
13
last=new;// store new as next last
14
enc_delta+=(diff&2)-1;// bit 1 = direction (+/-)
15
}
16
17
// taster
18
get_taster(0,PIND&(1<<PD5));// encoder-switch
19
get_taster(1,PIND&(1<<PD0));// sw1
20
get_taster(2,PIND&(1<<PD1));// sw2
21
get_taster(3,PIND&(1<<PD2));// sw3
22
get_taster(4,PIND&(1<<PD3));// sw4
23
}
die so initialisiert wird:
1
voidencode_init(void){
2
3
int8_tnew;
4
5
new=0;
6
if(PHASE_A)
7
new=3;
8
if(PHASE_B)
9
new^=1;// convert gray to binary
10
last=new;// power on state
11
enc_delta=0;
12
TCCR0=1<<WGM01^1<<CS01^1<<CS00;// CTC, XTAL / 64
13
OCR0=(uint8_t)(XTAL/64.0*1e-3-0.5);// 1ms
14
TIMSK|=1<<OCIE0;
15
}
Die Tasterabfrage ist nicht von Peda aber der Rest ist 1:1 übernommen.
Der Code, den ich im ersten Beitrag gepostet habe, also der Aufruf von
encode_read2() und dessen Auswertung, steht im main() so wie Peda es
auch im Beispiel gemacht hat. Das sieht dann komplett so aus:
1
intmain(void){
2
3
// *********************************************
4
// setup ports
5
// *********************************************
6
// PORTA CONFIG
7
// A3 PTT IN
8
DDRA=0b00000000;// port a output 1=output
9
PORTA=0b00001000;// port a pullups 1=active
10
11
// PORTB CONFIG
12
// B5 ENC_B IN
13
// B4 ENC_A IN
14
// B3 MISO IN
15
// B2 MOSI OUT
16
// B1 SCK OUT
17
DDRB=0b00000110;// port b output 1=output
18
PORTB=0b00110000;// port b pullups 1=active
19
20
// PORTC CONFIG
21
// C7 DC_DISP OUT
22
// C6 RES_DISP OUT
23
// C5 CS_DISP OUT
24
DDRC=0b11100000;// port c output 1=output
25
PORTC=0b00000000;// port c pullups 1=active
26
27
// PORTD CONFIG
28
// D7 CE_PLL OUT
29
// D6 LE_PLL OUT
30
// D5 ENCSW IN
31
// D3 SW4 IN
32
// D2 SW3 IN
33
// D1 SW2 IN
34
// D0 SW1 IN
35
DDRD=0b11000000;// port d output 1=output
36
PORTD=0b00101111;// port d pullups 1=active
37
38
// PORTE CONFIG
39
// CtcssPort = all out
40
DDRE=0b11111111;// port e output 1=output
41
PORTE=0b00000000;// port e pullups 1=active
42
43
// PORTF CONFIG
44
// F7 JTAG_TDI IN
45
// F6 JTAG_TDO IN
46
// F5 JTAG_TMS IN
47
// F4 JTAG_TCK IN
48
// F0 RSSI IN
49
DDRF=0b00000000;// port f output 1=output
50
PORTF=0b11110000;// port f pullups 1=active
51
// *********************************************
52
// end setup ports
53
// *********************************************
54
55
// *********************************************
56
// setup spi
57
// *********************************************
58
// SPI CONFIG
59
// Bit7: SPIE -> Enables SPI interrupt
60
// Bit6: SPE -> setting it enables SPI
61
// Bit5: DORD -> if bit is set first comes LSB, if cleared MSB
62
// Bit4: MSTR -> Master/Slave select, 1 = master
63
// Bit3: CPOL -> clock polarity (refer to datasheet)
64
// Bit2: CPHA
65
// Bit1: SPR1 ->
66
// Bit0: SPR0 -> Clock rate selection (refer to table in datasheet)
67
// Enable SPI, Master, set clock rate fck/16
68
SPCR=0;
69
SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
70
SPSR=(0<<SPI2X);
71
// *********************************************
72
// end setup spi
73
// *********************************************
74
75
// *********************************************
76
// various inits
77
// *********************************************
78
//float display
79
charstring[7];
80
// encoder
81
encode_init();
82
sei();
83
// glcd
84
GLCD_Init();
85
GLCD_ClearScreen();
86
SetFont(ARIAL_14_BOLD);
87
LCDSoftText("HELLO WORLD!",0,0);
88
// Taster konfigurieren (#define NUM_TASTER 3 in taster.h)
89
tasten[0].mode=TM_SHORT;
90
tasten[1].mode=TM_SHORT;
91
tasten[2].mode=TM_SHORT;
92
tasten[3].mode=TM_SHORT;
93
tasten[4].mode=TM_SHORT;
94
// *********************************************
95
// end various inits
96
// *********************************************
97
98
while(1){
99
100
// encoder
101
signedchartemp=encode_read2();
102
if(temp==1){
103
// Value is increasing
104
FREQ-=FREQSTEP;
105
}elseif(temp==-1){
106
// Value is decreasing
107
FREQ+=FREQSTEP;
108
}
109
// end encoder
110
111
// taster
112
signedchartast=taster;
113
switch(tast){
114
default:
115
caseNO_TASTER:
116
break;
117
case0:
118
/* Taster 0 */
119
break;
120
case1:
121
/* Taster 1 kurz gedrueckt */
122
break;
123
case2:
124
/* Taster 2 */
125
break;
126
case3:
127
/* Taster 3 */
128
break;
129
case4:
130
/* Taster 4 */
131
break;
132
}
133
if(tast!=NO_TASTER)
134
taster=NO_TASTER;
135
// end taster
136
137
SetFont(FIX_FONT12X16);
138
LCDSoftText(dtostrf(FREQ,2,4,string),0,15);
139
}
140
}
Eine andere Möglichkeit der Ausgabe sehe ich nicht, es handelt sich
nicht um einen Versuchsaufbau auf dem Steckbrett, wo einfach etwas
geändert werden kann. Ausserdem gehört das Display zur Schaltung,
letztendlich kann darauf auch nicht verzichtet werden. Dass das
LCD-Schreiben etwas zeitaufwändig ist, besonders da noch ein Float
geschrieben wird, wird schon so sein. Aber ich kenne keine Möglichkeit
das anders zu lösen...
Einen externen Interrupt wollte ich eben nicht verwenden, weil ich nicht
darauf angewiesen sein wollte, den Encoder unbedingt an einem
Interrupt-Pin anzuschliessen.
Ich denke die Bedenken die ihr hattet wegen der Encoderroutine im
Interrupt sind unbegründet, oder habe ich euch falsch verstanden?
Grüsse
Martin
Martin Geissmann schrieb:
> Ich denke die Bedenken die ihr hattet wegen der Encoderroutine im> Interrupt sind unbegründet, oder habe ich euch falsch verstanden?
Nein, das passt schon so.
XTAL hast du auf den richtigen Wert gesetzt?
Welche Prozessorfrequenz hast du eigentlich?
Hallo!
Ich habe das Problem gelöst, indem ich das Rundensignal, dass mein
Incrementalgeber ausgibt, genutzt habe. Es Synchronisiert die Impulse
und setzt den Wert auf ein Vielfaches der Impulse je Umdrehung, die der
Geber sonst ausgibt. Somit werden nur die Fehler, die in der letzten
Runde auftraten, wirksam. Natürlich geht das ganze vorwärts und
rückwärts. Ich habe übrigens auch die Möglichkeit der Interrupts für
diese Aufgabe benutzt.
Das Programm ist in BASCOM geschrieben und erfüllt seinen Zweck als
PID-Regler hervorragend.
Gruß
Guido
Was ist, wenn temp nicht +1 oder -1 ist, sondern höher? Die PeDa
Encoder-Funktionen führen auch darüber Buch, wenn du nicht rechtzeitig
zur Abfrage kommst (weil der µC zb mit lCD Ausgabe zu lange beschäftigt
war) und der Benutzer in der Zwischenzeit 2 oder mehr Rastungen gedreht
hat.
Also besser so
Martin Geissmann schrieb:
> signed char temp = encode_read2();> if(temp == 1) {> // Value is increasing> FREQ -= FREQSTEP;> } else if(temp == -1) {> // Value is decreasing> FREQ += FREQSTEP;> }
Du solltes bercksichtigen, dass temp auch andere Werte als -1, 0, 1
annehmen kann.
kannst du dir sparen. Auch Abfragen sind nicht kostenlos!
Weis einfach den Wert zu, den taster auf jeden Fall haben soll und gut
ists.
Und versuche dich an übliche C Konventionen zu halten.
Namen ausschliesslich in Grossbuchstaben sind immer Makros und
umgekehrt: Makros haben immer einen Namen ausschliesslich in
Grossbuchstaben.
Eine Variable namens FREQ geht gar nicht.
Das erleichtert die Unterscheidung was ein Makro ist und was nicht. Und
das wiederrum kann manchmal den Unterschied zwischen 'funktioniert
problemlos' und 'Ich such jetzt seit 5 Stunden einen Fehler und finde
ihn nicht' ausmachen.
Lothar Miller schrieb:
> ich habe den code noch ein wenig> erweitert, dass bei schnellem Drehen am Knopf der Wert sich schneller> ändert (dynamische Geschwindigkeitsanpassung)
Wäre das nicht eine schöne Erweiterung für den Artikel?
Die Prozessorfrequenz ist 8MHz. Das ist normalerweise meine Frequenz
weil ich noch etwa 500 8MHz-Quarze habe...
@Karl Heinz: Das wird wohl das Problem bzw. die Lösung sein, da habe ich
offenbar wirklich etwas nicht richtig verstanden. Nun habe ich die
Auswertung
1
if(temp==1){
2
// Value is increasing
3
FREQ-=FREQSTEP;
4
}elseif(temp==-1){
5
// Value is decreasing
6
FREQ+=FREQSTEP;
7
}
durch
1
FREQ-=temp*FREQSTEP;
ersetzt. Viel einfacher und tut auch wirklich schon viel besser! Ist das
was du gemeint hast?
Grüsse
Martin