www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Encoder/Drehgeber zu langsam


Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
signed char temp = encode_read2();
if(temp == 1) {
   // Value is increasing
   FREQ -= FREQSTEP;
} else if(temp == -1) {
   // Value is decreasing
   FREQ += FREQSTEP;
}
SetFont(FIX_FONT12X16);
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

Autor: DanVet (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: docean (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
wie lange brauch dein LCD Kram?

kannst du mal rausnnehmen und den wert über uart und/oder leds ausgeben?

Autor: DanVet (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: DanVet (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ich nochmal, die Encoder-Schaltung sieht bei mir so aus, wie im Anhang.

Autor: Fabian B. (fabs)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>>>  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...

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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.

Autor: Daniel V. (danvet)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Horst (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Horst (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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.

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok das gab ja ganz schön viele Antworten. Will mal versuchen so gut wie 
möglich darauf einzugehen. Zuerst mal meine Interrupt-Routine:
ISR(TIMER0_COMP_vect) {          // 1ms for manual movement

  // encoder
  int8_t new, diff;
 
  new = 0;
  if(PHASE_A)
    new = 3;
  if(PHASE_B)
    new ^= 1;              // convert gray to binary
  diff = last - new;          // difference last - new
  if(diff & 1) {            // bit 0 = value (1)
    last = new;              // store new as next last
    enc_delta += (diff & 2) - 1;    // bit 1 = direction (+/-)
  }

  // taster
  get_taster(0, PIND & (1<<PD5));    // encoder-switch
  get_taster(1, PIND & (1<<PD0));    // sw1
  get_taster(2, PIND & (1<<PD1));    // sw2
  get_taster(3, PIND & (1<<PD2));    // sw3
  get_taster(4, PIND & (1<<PD3));    // sw4
}

die so initialisiert wird:
void encode_init(void) {

  int8_t new;
 
  new = 0;
  if(PHASE_A)
    new = 3;
  if(PHASE_B)
    new ^= 1;              // convert gray to binary
  last = new;              // power on state
  enc_delta = 0;
  TCCR0 = 1<<WGM01^1<<CS01^1<<CS00;    // CTC, XTAL / 64
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);  // 1ms
  TIMSK |= 1<<OCIE0;
}

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:
int main(void) {

  // *********************************************
  // setup ports
  // *********************************************
  // PORTA CONFIG
  // A3 PTT        IN 
  DDRA =  0b00000000;                          // port a output 1=output
  PORTA = 0b00001000;                          // port a pullups 1=active

  // PORTB CONFIG
  // B5 ENC_B      IN 
  // B4 ENC_A      IN 
  // B3 MISO       IN
  // B2 MOSI       OUT
  // B1 SCK        OUT
  DDRB =  0b00000110;                          // port b output 1=output
  PORTB = 0b00110000;                          // port b pullups 1=active
  
  // PORTC CONFIG
  // C7 DC_DISP    OUT
  // C6 RES_DISP   OUT
  // C5 CS_DISP    OUT 
  DDRC =  0b11100000;                        // port c output 1=output
  PORTC = 0b00000000;                        // port c pullups 1=active

  // PORTD CONFIG
  // D7 CE_PLL     OUT
  // D6 LE_PLL     OUT
  // D5 ENCSW      IN
  // D3 SW4        IN
  // D2 SW3        IN
  // D1 SW2        IN
  // D0 SW1        IN
  DDRD =  0b11000000;                                       // port d output 1=output
  PORTD = 0b00101111;                        // port d pullups 1=active
  
  // PORTE CONFIG
  // CtcssPort = all out
  DDRE =  0b11111111;                        // port e output 1=output
  PORTE = 0b00000000;                        // port e pullups 1=active

  // PORTF CONFIG
  // F7 JTAG_TDI   IN
  // F6 JTAG_TDO   IN
  // F5 JTAG_TMS   IN
  // F4 JTAG_TCK   IN
  // F0 RSSI       IN
  DDRF =  0b00000000;                        // port f output 1=output
  PORTF = 0b11110000;                        // port f pullups 1=active
  // *********************************************
  // end setup ports
  // *********************************************

  // *********************************************
  // setup spi
  // *********************************************
  // SPI CONFIG
  // Bit7: SPIE -> Enables SPI interrupt
  // Bit6: SPE  -> setting it enables SPI  
  // Bit5: DORD -> if bit is set first comes LSB, if cleared MSB
  // Bit4: MSTR -> Master/Slave select, 1 = master
  // Bit3: CPOL -> clock polarity (refer to datasheet)
  // Bit2: CPHA
  // Bit1: SPR1 ->
  // Bit0: SPR0 -> Clock rate selection (refer to table in datasheet)
  // Enable SPI, Master, set clock rate fck/16
  SPCR = 0;
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
  SPSR = (0<<SPI2X);
  // *********************************************
  // end setup spi
  // *********************************************

  // *********************************************
  // various inits
  // *********************************************
  //float display
  char string[7];
  // encoder
  encode_init();
  sei();
  // glcd
  GLCD_Init();
  GLCD_ClearScreen();
  SetFont(ARIAL_14_BOLD);
  LCDSoftText("HELLO WORLD!",0,0);
  // Taster konfigurieren (#define NUM_TASTER 3 in taster.h)
  tasten[0].mode = TM_SHORT;
  tasten[1].mode = TM_SHORT;
  tasten[2].mode = TM_SHORT;
  tasten[3].mode = TM_SHORT;
  tasten[4].mode = TM_SHORT;
  // *********************************************
  // end various inits
  // *********************************************
  
  while(1) {  

    // encoder
    signed char temp = encode_read2();
    if(temp == 1) {
       // Value is increasing
       FREQ -= FREQSTEP;
    } else if(temp == -1) {
       // Value is decreasing
       FREQ += FREQSTEP;
    }
    // end encoder

    // taster
    signed char tast = taster;
    switch(tast) {
       default:
       case NO_TASTER:
          break;
       case 0:
          /* Taster 0 */
          break;
       case 1:
          /* Taster 1 kurz gedrueckt */
          break;
       case 2:
          /* Taster 2 */
          break;
       case 3:
          /* Taster 3 */
          break;
       case 4:
          /* Taster 4 */
          break;
    }
    if(tast != NO_TASTER)
       taster = NO_TASTER;
    // end taster

    SetFont(FIX_FONT12X16);
    LCDSoftText(dtostrf(FREQ,2,4,string),0,15);
  }
}

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Guido Scheidat (flintstone)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
   signed char temp = encode_read2();
    if(temp == 1) {
       // Value is increasing
       FREQ -= FREQSTEP;
    } else if(temp == -1) {
       // Value is decreasing
       FREQ += FREQSTEP;
    }

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
   int8_t temp = encode_read2();
   if( temp != 0 )
     FREQ -= temp * FREQSTEP;

Autor: Petra Dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
PS
So was
    if(tast != NO_TASTER)
       taster = NO_TASTER;
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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Martin Geissmann schrieb:
> ISR(TIMER0_COMP_vect) {          // 1ms for manual movement
> ...
>   get_taster(0, PIND & (1<<PD5));    // encoder-switch
>   get_taster(1, PIND & (1<<PD0));    // sw1
>   get_taster(2, PIND & (1<<PD1));    // sw2
>   get_taster(3, PIND & (1<<PD2));    // sw3
>   get_taster(4, PIND & (1<<PD3));    // sw4
> }

Wie sieht denn "get_taster" aus?

Hoffentlich nicht mit riesigen Delays.


Peter

Autor: Micha (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Wäre das nicht eine schöne Erweiterung für den Artikel?
Irgendwo kursiert das da noch rum.... such, grübel, gruschtel, hmmm...
Ah, hier im Beitrag "Re: Dynamische "Beschleunigung" bei Encoder-Eingabe"

Interessant auch der letzte Satz zum Thema "Pin-Change-Interrupt" von 
Peter Dannegger im Beitrag "Re: Dynamische "Beschleunigung" bei Encoder-Eingabe"

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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
if(temp == 1) {
   // Value is increasing
   FREQ -= FREQSTEP;
} else if(temp == -1) {
   // Value is decreasing
   FREQ += FREQSTEP;
}

durch
FREQ -= temp*FREQSTEP;

ersetzt. Viel einfacher und tut auch wirklich schon viel besser! Ist das 
was du gemeint hast?

Grüsse
Martin

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.