Encoder.h macht zu große Sprünge

Hallo miteinander,

ich habe mir ein paar KY-040 besorgt.
Beim Testen zählt die LIB Encoder.h immer Vier schritte.

altePosition = 0
1 Raster weiter drehen, dann bekomme ich die Ausgabe.

0
1
2
3
4

Ich verstehe nicht warum immer genau 4 weiter gezählt wird, jedoch werden auch alle zahlen dazwischen mit ausgegeben.

Das ist mein Code:

#include <Encoder.h>  // von Paul Stoffregen

const int CLK = 6;
const int DT = 5;
const int SW = 2;
long altePosition = -999;

Encoder meinEncoder(DT,CLK);

void setup() {
  Serial.begin(9600);    
  pinMode(SW, INPUT);  
  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE);
} // SETUP

void loop() {
    long neuePosition = meinEncoder.read();  
        if (neuePosition != altePosition) {
          altePosition = neuePosition;       
          Serial.println(neuePosition);
        } // IF 
} // LOOP

void Interrupt() {
  Serial.println("Switch betaetigt");
} // IBTERRUPT

Gruß Mücke

Welche lib? Die von Paul Stoffregen?

Sorry, ja die habe ich mir Installiert.

Der Grund: Encoder Library, for Measuring Quadarature Encoded Position or Rotation Signals
Dort der Abschnitt: Understanding Quadrature Encoded Signals

1 Like

Ach man....

Dein Decoder zählt alle Phasenwechsel.
Wobei dein Encoder 4 Phasenwechsel pro Rastung erzeugt.
Logisch, oder?

1 Like

Als ich die Lib von Stoffregen mit KY-040 getestet habe hat sich der Code bei richtungswechsel ständig durch bouncing verzählt. Es funktionierte nur sehr unzuverlässig.

Die library NewEncoder läuft dagegen absolut zuverlässig

vgs

Unbewiesene Tatsachenbehauptung.

Ok, das leuchtet ein, da mein KY-040, 20 Raster pro Umdrehung hat, ist jeder Raster 4 Veränderungen groß.
Die Simulation die auf der Seite ist, finde ich sehr gut.

@combie, ja das ist mir jetzt auch klar geworden.
Jetzt stelle ich mir die Frage wie ich das richtig erkenne und ausgeben kann.
Das Ergebnis nur durch 4 zu teilen reicht ja nicht, da ich 4 mal ein Ereignis habe.

@StefanL38, wenn ich es zum laufen bekomme teste ich es.

[EDIT]bisher noch keine Lösung gefunden auch nicht im anderen Forum das @my_xy_projekt verlinkt hatte.

Es gibt eine Menge Demo-codes online die in interrupt-routinen etwas auf der seriellen Schnittstelle ausgeben.

Als Demo-Code funktioniert das auch.

aber

das sollte man grundsätzlich nicht machen!

wie der name schon sagt interrupt-routine Kurz "ISR" : Der sonstige Programmablauf wird mittendrin unterbrochen und dann wird die ISR abgearbeitet. Während die ISR abgearbeitet wird sind (andere) interrupts deaktiviert.
Deshalb sollte das abarbeiten von ISRs immer so schnell wie möglich fertig sein 0,x Millisekunden Flag-variable setzen oder Variable++ und gut is.

Wenn ISR dann so

#include <Encoder.h>  // von Paul Stoffregen

const int CLK = 6;
const int DT = 5;
const int SW = 2;
long altePosition = -999;

boolean switchMsgPrinted = true;

Encoder meinEncoder(DT, CLK);

void setup() {
  Serial.begin(9600);
  pinMode(SW, INPUT);
  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE);
} // SETUP

void loop() {
  long neuePosition = meinEncoder.read();
  
  if (neuePosition != altePosition) {
    altePosition = neuePosition;
    Serial.println(neuePosition);
  } // IF

  if (switchMsgPrinted == false) {
    Serial.println("Switch betaetigt");
    switchMsgPrinted = true; // auf true setzen damit es nur EINMAL ausgegeben wird 
  }
} // LOOP


void Interrupt() {
  // flag auf false setzen als Signal Text "Switch betaetigt" 
  // muss neu ausgegeben werden
  switchMsgPrinted = false;
} // INTERRUPT

Da kann man dann auch gleich das prellen (bouncing) des Tasters
und die Funktion vom Parameter CHANGE schön demonstrieren
CHANGE heißt löse einen interrupt aus wenn der Taster kontakt bekommt low-HIGH change
und
löse noch einen Interrupt aus wenn der Taster losgelassen wird HIGH-low
und wenn der Taster prellt dann passiert das gleich mehrmals.

vgs

Das betrifft den Taster.
Leider wird der ab und an auch zu oft erkannt.
Daher habe ich dafür etwas eigenes geschrieben: Wahrscheinlich nicht das beste, doch es liefert bisher immer richtige Ergebnisse.

Nur die Abfrage des Tasters (Drücken)

// Muecke #10

// #define CLK 2  // Drehung
// #define DT 3   // Drehung


// Taster
#define Taster_Pin 4        // Bezeichnung: SW
bool    Taster_Status = 0; 
bool    Taster_Tmp    = 1; 
int     Taster_Point  = 0;

void setup() {
  Serial.begin(9600);   
  pinMode(Taster_Pin, INPUT);
}

void loop() {
  Taster_Status = digitalRead(Taster_Pin);
  Taster_Auswertung(); 
  Ausgabe(); 
} // LOOP

void Taster_Auswertung() {
  if (Taster_Status != Taster_Tmp){
    Taster_Tmp = Taster_Status;
    if (Taster_Status == 0) { Taster_Point = Taster_Point + 1 ; }
  } // IF
} // TASTER-AUSWERTUNG


void Ausgabe() {
    Serial.print("Taster_Point => ");  
    Serial.print(Taster_Point); 
    Serial.print("\t Taster_Status => ");  
    Serial.print(Taster_Status); 
    Serial.print("\t Taster_Tmp => ");  
    Serial.println(Taster_Tmp); 
} // AUSGABE

Die Ausgabe habe ich mir als Debuggen eingebaut, um zu sehen was denn eigentlich wann wie dargestellt wird.

Jetzt habe ich immer noch das Problem beim Drehen des Knopfes, das hier 4 Flanken erkannt werden, wie ich das in den greif bekommen kann habe ich noch keine Idee und auch im Netz nichts gefunden :frowning:

@StefanL38: dein Code im Post 9 reagiert zum teil zu oft :frowning: das hatte ich auch wenn ich die Zeile:
attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE);
abändere auf:
attachInterrupt(digitalPinToInterrupt(SW), Interrupt, RISING);
Daher hatte ich den Code selbst nochmal geschriben, doch das mit den zuerst erkennen PIN A und dann B das ist mir irgend wie zu Hoch.

Das ist das mechanische Prellen des Schalters.
Mechanische Schalter sollte man immer entprellen.

Wenn es absolut zuverlässig immer genau 4 weiterzählt

#include <Encoder.h>  // von Paul Stoffregen

const int CLK = 6;
const int DT = 5;
const int SW = 2;
long altePosition = 0;
long neuePosition = 0;

long lastPos = 0;
long Zaehler = 0;
long alterZaehler = 0;

boolean switchMsgPrinted = true;

Encoder meinEncoder(DT, CLK);

void setup() {
  Serial.begin(115200);
  pinMode(SW, INPUT);
  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE);
} // SETUP


void loop() {
  neuePosition = meinEncoder.read();

  if (lastPos < neuePosition) {
    if (neuePosition % 4 == 0) {
      Zaehler++;
      lastPos = neuePosition;
    }
  }

  if (lastPos > neuePosition) {
    if (neuePosition % 4 == 0) {
      Zaehler--;
      lastPos = neuePosition;
    }
  }

  if (Zaehler != alterZaehler) {
    alterZaehler = Zaehler;
    Serial.println(Zaehler);
  } // IF

  if (switchMsgPrinted == false) {
    Serial.println("Switch betaetigt");
    switchMsgPrinted = true; // auf true setzen damit es nur EINMAL ausgegeben wird
  }
} // LOOP


void Interrupt() {
  // flag auf false setzen als Signal Text "Switch betaetigt"
  // muss neu ausgegeben werden
  switchMsgPrinted = false;
} // INTERRUPT

Aber vielleicht schaust du dir wirklich andere encoder-libraries an
vgs
Dann muss man jetzt mal aufs ganze schauen:

welchen Mikrocontroller verwendest du?
In was für einem Projekt soll der rotary-Encoder eingesetzt werden?
Braucht dein Projekt interrupt-fähige Pins?

Die NewEncoder-library verwendet interrupts. Das heißt wenn man NewEncoder benutzen will dann sind zwei Interrupts dafür belegt.

1 Like

Das verstehe ich nicht :see_no_evil: habe noch nicht mal ein Idee was da genau passieren könnte.

Habe deinen Code getestet, Zählen tut er sehr gut.
DANKE dafür.

Arduino Mega (Name TMP :wink:)
Im Projekt selbst würde ich wenn möglich ein Arduino UNO verwenden

Habe Mehrere Schrittmotoren Treiber die ich ansprechen möchte (DIR, PUL, ENA jeweils mit 5V).

Dafür habe ich mir eine Kleine Box vorgestellt bei der ein Display, 2 Drehknopf, 2 Wahlschalter und ein Startknopf habe, an der Box kommen dann 6 Kabel raus an denen die anschließe für die Schrittmotoren Treiber sind.
So kann ich schnell und einfach die Motoren so bewegen wie ich es möchte.

Und bis jetzt habe ich die Wahlschalter, die Drehknöpfe und das Display schon bekommen.
Dafür teste ich erst einmal alles Einzel wie es klappt bevor ich alles in einen Code packen kann.

Es ist also für sich ein kleines Projekt, wenn man ein Schritt verloren geht am Drehknopf ist das nicht so schlimm, da man ab Display abliest was eingestellt ist.

Ich würde jetzt mal so spontan die Frage mit NEIN beantworten! da der Code hoffentlich auch nicht all zu lange wird.

Das habe ich auch gelesen, und der Arduino UNO hat wenn ich es richtig gelesen habe nur 2 Interrupts. daher hatte ich die für ich erst einmal auf die Seite gelegt.

ps. ich möchte jetzt aber nicht das Projekt in diesem Thema vervollständigen, wenn ich es zum laufen gebracht habe, wollte ich das als gerammte Vorstellen, bzw. wenn ich Probleme habe für das Gesamte Projekt dann ein Thema aufmachen.

Das ist der Modulo-Operator

5 % 4 = 1 5 geteilt durch 4 ergibt 1 rest 1
6 % 4 = 2 6 geteilt durch 4 ergibt 1 rest 2
7 % 4 = 3 7 geteilt durch 4 ergibt 1 rest 3

486 % 4 = 2 486 geteilt durch 4 ergibt 121 rest 2
Der Modulo-Operator gibt den ganzzahligen Rest zurück

vgs

1 Like

Drehencoder für manuelle Eingabe sind Kontakte und Schleifer. Diese prellen. Innerhalb einer Interruptroutine ist es sehr schwierig zu entprellen. Außerdem ist die Taktfrequenz der Signale sehr gering weil man den Encoder nicht besonders schnell betätigen kann.

Darum soll/darf man Schaltert, Taster und Kontakte nicht mittels Interruptroutine abfragen.
Grüße Uwe

Die Aussage bezieht sich auf den Taster

Die NewEncoder-library funktioniert super zuverlässig mit mechanischen Encodern.
Sowohl an Arduinos als auch an ESP8266 / ESP32
Und die fragt die Schaltzustände der Kontakte über interrupts ab

Warum sollte das Prinzip des Entprellens eines Tasters in einer ISR nicht funktionieren
Hand-Eingabe Taster werden jetzt wirklich nicht mit hoher Frequenz gedrückt.

Da kann man abfragen ob der letzte ISR-Aufruf wenigstens 50 Millisekunden her ist.

#include <Encoder.h>  // von Paul Stoffregen

const int CLK = 6;
const int DT = 5;
const int SW = 2;
long altePosition = 0;
long neuePosition = 0;

long lastPos = 0;
long Zaehler = 0;
long alterZaehler = 0;

volatile unsigned long lastCall;

boolean switchMsgPrinted = true;

Encoder meinEncoder(DT, CLK);

void setup() {
  Serial.begin(115200);
  pinMode(SW, INPUT);
  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, RISING);
} // SETUP


void loop() {
  neuePosition = meinEncoder.read();

  if (lastPos < neuePosition) {
    if (neuePosition % 4 == 0) {
      Zaehler++;
      lastPos = neuePosition;
    }
  }

  if (lastPos > neuePosition) {
    if (neuePosition % 4 == 0) {
      Zaehler--;
      lastPos = neuePosition;
    }
  }

  if (Zaehler != alterZaehler) {
    alterZaehler = Zaehler;
    Serial.println(Zaehler);
  } // IF

  if (switchMsgPrinted == false) {
    Serial.println("Switch betaetigt");
    switchMsgPrinted = true; // auf true setzen damit es nur EINMAL ausgegeben wird
  }
} // LOOP


void Interrupt() {
  // flag auf false setzen als Signal Text "Switch betaetigt"
  // muss neu ausgegeben werden
  if (millis() - lastCall > 50) {
    switchMsgPrinted = false;
  }
  lastCall = millis();  
} // INTERRUPT

vgs

Hallo,

will ja nicht meckern, aber das hier macht überhaupt keinen Sinn. Man kann den Taster auch gleich in loop per millis Intervallmäßig abfragen.

if (switchMsgPrinted == false) {
    Serial.println("Switch betaetigt");
    switchMsgPrinted = true; // auf true setzen damit es nur EINMAL ausgegeben wird
}

void Interrupt() {
  // flag auf false setzen als Signal Text "Switch betaetigt"
  // muss neu ausgegeben werden
  if (millis() - lastCall > 50) {
    switchMsgPrinted = false;
  }
  lastCall = millis();  
}

@ Mücke.

Meinste nicht auch das du millis nach Jahren kennen und anwenden können solltest? Du brauchst nur aller 20-40ms den Taster abfragen. Mehr ist das nicht. Wenn dir das noch zu kompliziert ist musste die Bounce2 Lib von Thomas Ouellet Fredericks nehmen. Alles ohne Interrupt.

Für den Encoder benötigt man auch keinen Interrupt. Das höchste der Gefühle sind, dass manche genau aller 1ms die Pins mittels Timer abfragen. Solange die loop aber nicht blockiert ist auch das nicht notwendig. Das heißt innerhalb 1ms sollte die loop locker durch sein. Beim Encoder kommt es viel mehr darauf an das die Pins "zeitgleich" bzw. direkt hintereinander abgefragt werden. Zudem der hier noch Hand gedreht wird und sowieso keine hohen Drehzahlen erwartet werden.

Ah OK, Danke für das Beispiel ich glaube ich habe es verstanden.

@Doc_Arduino: die Codes sind fasst alle nicht von mir!
Wie in #12 schon geschrieben, glaube ich nicht das ich hier Interrupt arbeiten muss.
Das das Programm sehr klein bleiben wird, und nichts zeit Kritisches dabei gemacht wird.

Die Abfrage des Tasters hatte ich ohne Interrupt gelöst: #10

Nur das mit der Drehrichtung hatte bisher noch nicht geklappt, doch das Klappt dank @StefanL38 Code sehr gut, das muss ich nur noch zusammen basteln.

:wink: und ja millis wenn du meinst ob ich ein Intervall abfrage machen kann, dann ja, das habe ich schon ein paar mal :slight_smile: zuletzt beim LED-Tisch :see_no_evil:

Heute komme ich leider nicht dazu weiter zu machen, ..., daher wird es im lauf der Woche passieren, das ich Taster und Drehgeber zusammen setze und teste. Werde dann entsprechend berichten und Darstellen.

Genau. Mücke hat weiter oben geschrieben

Jetzt kommt es darauf an Wie das Zusammenspiel von Drehencoderbedienung und Schrittmotoren laufen lassen ausssehen wird. Die meisten Schrittmotor-libraries sind blockierend.

Wenn das Einstellen per Drehencoder immer nur dann gemacht wird wenn die Schrittmotoren gerade nicht drehen dann klappt das mit dem Abfragen durch loop() .
Wenn man die Drehung der Schrittmotoren per Tastendruck stoppen möchte und eine blockierende stepper-library vewendet, dann muss es per interrupt sein.

Wenn man während die Schritmotoren laufen über den Drehencoder die Drehzahl verändern möchte dann muss es meines erachtens nach eine nicht-blockierende stepper-library sein und der Drehencoder muss über interrupt abgefragt werden.

@Muecke Aus dieser Erklärung kannst du sehen das es immer gut ist einen Überblick über das Gesamtptojekt zu geben.

vgs

das ist richtig was du schreibst.

ich hatte vor bei diesem Projekt keine Schrittmotor-LIB zu verwenden.
sondern alles im Intervallen abzufragen, so das alles Parabel laufen kann.

Wobei ich die Veränderung wären des Betriebes des Motors nicht vorgesehen habe.

Es ist jedoch auch so das ich erst meinen Drehknopf verstehen muss um etwas damit anfangen zu können, daher muss ich nur mit dem etwas Spielen und arbeiten. bevor ich den mit etwas anderen zusammen bringe, denn dann wies ich später nicht woher Fehler kommen und verliere zu schnell den Überblick. ... ich bin bei so was sehr langsam :frowning:

OK alles zusammen in loop.

Dann bietet sich an die loop() mit möglichst kurzer Durchlaufzeit zu betreiben und
einen Zeittakt mit möglichst hoher Frequenz einzubauen.
Damit meine ich einen microseconds()-basierten timer. Immer wenn die Intervallzeit vorbei ist den code ausführen der die Schritt-signale erzeugt. Wenn die Schrittmotoren mit höherer Drehzahl und womöglich noch mit Mikroschritten 1/8 1/16 usw. laufen sollen dann ergibt das Taktfrequenzen die man auf diese Weise nicht mehr erzeugen kann.
Texte auf ein Display schreiben verbraucht dann auch zu viel Zeit wenn die Display-Ausgabe während der Schrittausgabe erfolgen soll oder man muss die maximale Schrittfrequenz so weit heruntersetzen, dass die Textausgabe für ein einzelnes Zeichen innerhalb eines Taktes durchgeführt werden kann.

Dafür würde ich dann keinen Arduino Uno nehmen.
Wahrscheinlich macht es Sinn die Schritterzeugung in einem Microcontroller zu machen und Display, Einstellungen vornehmen auf einem zweiten.
Da würde ich dann für 2x 7,95 Euro zwei Seeeduino XIAO nehmen.

Noch einmal:
ist das jetzt so eine Art Testbox zum Schrittmotoren testen ob sie im Prinzip funktionieren oder hast du da bestimmte Anforderungen?

Mit Anforderungen meine ich Dinge wie:
maximale Schrittfrequenz
maximale Gesamtzahl an Schrittmotoren die gleichzeitig aber mit unterschiedlichen Drehzahlen laufen sollen
Echtzeitausgabe von aktuell eingestellten Drehzahlen
Echtzeitausgabe von noch auszuführenden Schritten bis der Schrittmotor angehalten wird
In Echtzeit veränderbare Drehzahl der Schrittmotoren durch drehen am Encoder

Wenn diese Anforderungen alle erfüllt werden müssten dann wird das entweder ein Ultraspezial-Computer mit einem aktuellen Inter-Atom-Prozessor mit 2 GHz auf dem ein Echtzeitbetriebssystem läuft aber schon lange kein Microcontroller mehr.

Microcontroller sind ja schon schnell aber Schritt-Signal-Erzeugung braucht enorm Rechenpower.
Ein Rechenbeispiel:
Schrittmotor soll mit Drehlzahl 3000 U/min = 50 Umdrehungen pro Sekunde im 1/32-Mikroschritt-Modus laufen

Drehzahl * Schritte pro Umdrehung mal Mikroschritte/Vollschritt
50 * 200 * 32 = 320.000 Schritte pro Sekunde 320 kHz Taktfrequenz

alle 1/320000 = 0,000003125 Sekunden ein Schritt.
Alle 3,125 micro-Sekunden ein Schritt alle 0,003125 Millisekunden ein Schritt

Jetzt einmal anders herum
Nehmen wir an die loop() läuft im 0,1 Millisekundentakt
= 10000 Durchläufe pro Sekunde

Erreichbare Drehzahl:
Im Halbschrittmodus macht das 400 Schritte pro Umdrehung
10000/400 = 25 Umdrehungen pro Sekunde * 60 = 1500 Umdrehungen pro Minute im Halbschrittmodus
bei 1/8 Schritt-Modus
10000 / 1600 * 60 = 375 Umdrehungen pro Minute

Wenn du im Microschrittmodus hohe Drehzahlen brauchst dann würde sich anbieten einen Teensy 3.2 Microcontroller zu nehmen. Der kann sehr hohe Taktfrequenzen erzeugen.

Aber du hast immer noch nicht genau beschrieben was du machen willst.
Wenn die Schrittmotoren zwar mit ganz hohen Drehzahlen laufen sollen
es aber überhaupt nicht darauf ankommt ob sie nun 10 oder 20 sekunden zum Beschleunigen brauchen und ob sie nun 10-20 Sekunden länger laufen oder nicht dann könnte man ein gaaaanz anderes Konzept verfolgen: Taktsignalerzeugung mit einem externen Timerbaustein wie NE555 und digital einstellbarem Poti. Das würde dann auch ein Microcontroller der auf 1 MHz runtergetaktet ist hinbekommen.

vgs