C Drehencoder Routine

Janiiix3

Aktives Mitglied
28. Sep. 2013
1.333
10
38
Hannover
Sprachen
  1. ANSI C
  2. C#
Hallo,

ich habe hier eine Routine von der Seite "www.mikrokontroller.net" in verwendung und musste leider feststellen, dass es manchmal noch zu prellern kommt. Habe meinen entsprechenden Timer auf 1ms CompareMatch gestellt.
Ist das normal? Encoder solch eine schlechte Qualität? Kann ich noch was anderes/besser machen?
Würde mich mal über Ratschläge freuen.

P.S
Habe erstmal keinen C zwischen den "Phasen" geschaltet.




CodeBox C
// Dekodertabelle für wackeligen Rastpunkt
// halbe Auflösung
const int8_t table[16] PROGMEM = {0,0,-1,0,0,0,0,1,1,0,0,0,0,-1,0,0};

// Dekodertabelle für normale Drehgeber
// volle Auflösung
//const int8_t table[16] PROGMEM = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};


int8_t encode_read( void )  // Encoder auslesen
{
    int8_t val;
    // atomarer Variablenzugriff
    cli();
    val = enc_delta;
    enc_delta = val & 1;
    sei();
    return val >>1;
}

ISR(TIMER0_COMPA_vect)  // 1ms fuer manuelle Eingabe
{
  static int8_t last=0;  // alten Wert speichern
  
  last = (last << 2)  & 0x0F;
  if (PHASE_A) last |=2;
  if (PHASE_B) last |=1;
  enc_delta += pgm_read_byte(&table[last]);
}
 
Eventuell ist das Intervall für die TimerISR zu kurz, probiere es zum Beispiel mit 10ms oder testweise auch höher. Möglichst eine große Zeit, aber noch so klein, dass du alle Änderungen beim Encoder mitbekommst, wenn du schnell drehst.

Falls du damit Erfolg hast, könntest du in der ISR eine Deadtime einbauen. Immer dann, wenn eine Änderung von PhaseA oder PhaseB erkannt wird, den Timer langsamer laufen lassen (über Prescaler) oder den Comparewert ändern, so dass der nächste ISR Aufruf erst nach 10 oder 20ms kommt. Wenn keine Änderung erkannt wurde, ein kurzes Intervall einstellen.

Dirk :ciao:
 
Okay. Ist das denn eine effektive Methode mit der Tabelle solch einen Encoder abzufragen? Es tut mir echt leid, wenn ich das Forum hier spamme aber hier lerne ich ne Menge von euch.
 
Das cli und sei brauchst du nicht, da du die Routine eh aus einer ISR heraus aufrufst wo Interrupts eh schon deaktiviert sind. Es ist sogar eher kontraproduktiv da so die Interrupts innerhalb der ISR aktiviert werden. (würdest du die Routine nicht innerhalb der ISR aufrufen sollten die drin bleiben)

Ich würde die Zeiten auch erhöhen, weil, ja, die Dinger prellen wie Sau. Vor allem wenn du zwischen den Schritten bist.
 
Das cli und sei brauchst du nicht, da du die Routine eh aus einer ISR heraus aufrufst wo Interrupts eh schon deaktiviert sind. Es ist sogar eher kontraproduktiv da so die Interrupts innerhalb der ISR aktiviert werden. (würdest du die Routine nicht innerhalb der ISR aufrufen sollten die drin bleiben)

Die Funktion encode_read wird nicht in der ISR aufgerufen, sie wird sicher im Hauptprogramm regelmäßig aufgerufen. Hier muss man also auf jedenfall verhindern, dass die ISR bei der Variable enc_delta dazwischenfunkt.
 
Shit, hast recht, verlesen. Muss also drin bleiben (in diesem Fall)
 
:offtopic:
Nur noch einmal kurz zu cli() sei().
Besser ist es, wenn man sich folgendes angewöhnt:
Interrupts nur dann global wieder aktivieren, wenn sie das auch zuvor waren.



CodeBox C
uint8_t int_reg = SREG;
cli();
// hier der kritische Bereich
SREG = int_reg;


Oder <util/atomic.h> verwenden
http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
 
Mich würde aber echt nochmal interessieren wie ihr die Methode findet, würdet ihr es auch so machen, evtl. anders?
 
Mich würde aber echt nochmal interessieren wie ihr die Methode findet, würdet ihr es auch so machen, evtl. anders?

Mir erscheint die Methode gut, Erfahrung habe ich damit aber nicht. Man merkt sich den letzten Zustand von A und B. Mit denen und dem aktuellen Zustand adressiert man das Array (2^4 = 16 Werte). Der älteste Zustand ist immer MSB, der jüngste LSB. Im Array kann man entscheiden, ob -1, 0 oder +1 erkannt werden soll.

Jetzt wäre noch interessant, was du genau mit encode_read machst.

Was ich bei der Funktion noch nicht sofort verstehe ist
enc_delta = val & 1;
return val >>1;
Es wird aber schon einen Sinn haben, wenn jemand anderes den Code erfolgreich einsetzt ;)

Wenn es soweit funktioniert und du anscheinend nur Prellen hast, würde ich an deiner Stelle an den Intervallzeiten "schrauben", so wie wir es schon beschrieben haben.

Andere Möglichkeit:
Du könntest eine Phase an einen EXTINT anschließen (fallende oder steigende Flanke) und bei einem EXT Interrupt die andere Phase prüfen. Falls der Encoder prellt, hilft dir das aber nicht, du müsstest hier auch eine Deadtime verwenden. Ich hatte diese Methode in Verbindung mit einem optischen Encoder (HP) verwendet, der prellt aber nicht, das hat (ohne Deadtime) sehr gut funktioniert.

Dirk :ciao:
 
Guten Abend liebe Bastler und Programmierer.

Habe jetzt ein neues Problemchen, wo ich nicht mehr so recht weiter weiß.
Hier ist ein Video von meinem Problem. Es geht um ein neuen Drehencoder (den ich vorher noch nie im Einsatz hatte), dort ist mir aufgefallen, dass wenn ich ziemlich arg langsam drehe, er entweder prellt oder sonst was tut... Aufjedenfall erkennt er zwei Schritte (Drehung links und rechts, man sieht es kurz aufblitzen)... Drehe ich schneller, funktioniert alles super, keine Macken oder Richtungswechsel.
Abtasten tue ich mit ca. 1kHz.

Kann mir echt nicht erklären woran das genau liegen mag... Hat eventuell von euch jemand eine Idee?

https://www.dropbox.com/s/zxia6szz41vul8z/20170125_171138_1.mp4?dl=0
 
Das habe ich gerade mal mit dem Oszilloskop versucht. Es sieht verdammt so aus als wenn die Schalter richtig doll prallen, wenn ich langsam drehe... Also zwischen dem Rastpunkt rum Eier...
 
Klar, mechanische Kontakte prellen beim Kontaktwechsel. Und Je langsamer Du am kritischen Punkt rumholperst (hin und herwackelst), desto länger dauerts.
@dino03 hatte diesbezüglich mal'ne Methode vorgestellt, wo der Zustand jeder Phase in eine Binärzahl (Byte, ggf auch mehr Bits) eingerollt wird. Die Zahl ist nur gültig, wenn sie nur Einsen oder Nullen enthält.
 
Ich bin immer davon ausgegangen, je schneller ich drehe um so größer ist auch das prellen.
Habe mich gestern schon mal mit @dino03 kurz darüber unterhalten.

Die Drehkodierung, klappt. Nur halt wenn ich (zeichne es mal kurz auf...)
|---hier rum spiele---| // | = Rastung
Also zwischen zwei Rastungen bleibe und dort halt hin und her schalte... Normalerweise ist das ja sehr selten der Fall (wenn man eben normal dreht)...
Müsste man doch auch irgendwie raus filtern können?
 
Normalerweise liegen die "Wechselpunkte" zwischen den Rastpunkten (wie bei Dir), es gibt aber auch "billige" Encoder, wo sie genau auf den Rastpunkten liegen...
 
Wenn es wirklich das prellen sein sollte,
Wie filtere ich das am besten Softwaremäßig raus?
 
@Dirk
Hast du noch eine Idee, wie ich das Prellen weg bekommen könnte? Externe Hardware wie z.B -> R/C Glied, bremsen mich bei zu hohen Frequenzen aus.

Was könnte ich da Softwaremäßig noch realisieren?
 
Was könnte ich da Softwaremäßig noch realisieren?

Stelle vielleicht noch einmal die relevanten aktuellen Softwarebereiche von dir hier rein. Vielleicht hat noch jemand Ideen. Im Moment fehlt mir die Zeit, ich schau mir das heute Abend eventuell mal an.
 
Stelle vielleicht noch einmal die relevanten aktuellen Softwarebereiche von dir hier rein. Vielleicht hat noch jemand Ideen. Im Moment fehlt mir die Zeit, ich schau mir das heute Abend eventuell mal an.

Das wäre echt mega nett. Leider eiere ich da schon zu lange drum rum und es wird einfach nicht besser :'(

Hier mal der aktuelle Zwischenschritt (hoffentlich verständlich)



CodeBox C

   

/* ~ 1kHz */

    if((Encoder.Bit & (1<<7)) == 0) // Auswertung findet gerade statt
    {
        Encoder.Last = ((Encoder.Last << 2) & 0x0F); // verschiebe letzten Input um 2

        if((READ_PIN(GPIOA,GPIO_Pin_0))) // lese eingang
        Encoder.Last |= 0x02; // eingang wurde erkannt, eintragen

        if((READ_PIN(GPIOA,GPIO_Pin_1)))// lese eingang
        Encoder.Last |= 0x01; // eingang wurde erkannt, eintragen
        Encoder.NewResult += Encoder.Table[Encoder.Last]; // soll incrementiert oder decrementiert warden?
    }

 

 

 Encoder.Bit |= (1<<7); // Bit setzen, damit der Timer nicht dazwischen funkt

    if(Encoder.OldResult > Encoder.NewResult)
    {
        Encoder.Bit = 1<<0;
        Encoder_Out(1,0);
    }
    else if(Encoder.OldResult < Encoder.NewResult)
    {
        Encoder.Bit = 1<<1;
        Encoder_Out(0,1);
    }
    Encoder.OldResult = Encoder.NewResult;
    Encoder.Bit &= ~(1<<7); // Bit löschen, Auswertung beendet

 
Hallo Janiiix,

ich vermute der obere Codeblock ist in der Timer ISR, der untere Block im Hauptprogramm innerhalb einer while(1) Schleife.

Die TimerISR "sperrst" du durch
Encoder.Bit |= (1<<7);

Das wird jetzt nicht der Fehler sein, aber du überschreibst das Bit 7 in den folgenden If-Blöcken.
Encoder.Bit = 1<<0;
Encoder.Bit = 1<<1;
Nicht, dass du dir hier später mal einen Fehler einbaust. Das hat im Moment wahrscheinlich keine ungünstige Auswirkung.

Ich gehe sonst davon aus, dass dein Code soweit in Ordnung ist. Das Verfahren habe ich nun nicht überprüft, das wird wohl aber in Ordnung sein, da es aus einem anderen funktionierenden Projekt stammt.


In der TimerISR "pollst" du ja praktisch die beiden Pin-Zustände. Da ist wahrscheinlich für den verwendeten Inkremenalgeber die Polling-Frequenz von ca. 1kHz zu hoch. Die Kontakte prellen vielleicht länger als 1ms, besonders dann wenn du extra langsam drehst. Wenn du zwischen den Rastpunkten bist und langsam drehst schließen oder öffnen die Kontakte eventuell nicht richtig. Da gibt es sicher auch Qualitätsunterschiede bei den Inkrementalencodern. Optisch ist deiner ja nicht oder?

Ich würde noch einmal folgendes probieren:
Interrupt-Zeit der ISR zum Testen erhöhen (5ms, 10ms ...). Wenn es dann geht, die kleinste Zeit nehmen, so dass du noch ausreichend schnell drehen kannst. Wird es bei gößeren Zeiten besser, reicht dir aber die max. Drehgeschwindigkeit nicht, dann eventuell einen besseren Inkrementalencoder verwenden.

Verwendest du externe Pullup-Widerstände oder interne?
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)