Mehrere Encoder, ein externer Interrupt

Moin,

Feiertagsbastelei und ich zerbreche mir den Kopf darüber, wie ich (beliebig) viele Encoder an meinen Mega anschließen kann und dabei sparsam mit Interrupts umgehen kann. Ich habe gerade etwas über port change interrupts gelesen, aber davon hat der Mega ja leider auch nicht beliebig viele und daher ist in meinem Kopf die Schaltung im Anhang entstanden.

Erklärung: Port 2 wird mit attachInterrupt auf CHANGE gesetzt und in der ISR lese ich die Encoder-Pins aus. Alle Input Ports sind auf Pull-Up gesetzt.

Die Schaltung funktioniert, aber die Frage ist: Ist das ein guter Weg? Will man das eleganter lösen?

Würde mich über über Meinungen und Impulse sehr freuen. :slight_smile:

Viele Grüße
Bollie

Du brauchst keine externen Interrupts. Man kann mehrere Encoder bequem in einem Timer Interrupt (z.B. alle 1-2ms) pollen. Das geht auch extrem schnell wenn man z.B. die digitalWriteFast Bibliothek verwendet. Mit direktem Port-Zugriff kann man auch 8 Pins auf einmal einlesen; d.h. 4 Encoder in einem Takt. Aber digitalRead() geht auch

Welche Encoder hast Du? Solche auf Motoren die sich relativ schnell drehen oder Encoder für mauelle Eingabe?

Der Kontroller hat interrups für alle Pin. Nur bei einigen kann der Interrupt direkt einem Pin zugeordnet werden ( zB bei den Pins 2 und 3 beim UNO). Bei den anderen Pins mußt Du erst herausbekommen welches Pin den Interrupt verursacht hat.

Siehe https://code.google.com/archive/p/arduino-pinchangeint/

zB auch ein MCP23009 hat einen Interuptausgang der von allen 8 I/O Pins ausgelöst werden kann. Bei einem Interruptsignal mußt Du hier erst den Port auslesen um draufzukommen welches Pin ihn ausgelöst hat.

Grüße Uwe

uwefed: Der Kontroller hat interrups für alle Pin

Beim Mega ist das nicht der Fall. Es gibt nur 3 Interruptmasken Register und Interrupt-Vektoren dafür. Also 3 * 8 = 24 Interrupts

uwefed: Welche Encoder hast Du? Solche auf Motoren die sich relativ schnell drehen oder Encoder für mauelle Eingabe?

Bournes Encoder für die manuelle Eingabe.

Ich experimentiere gerade mit den internen Interrupts. Wie Serenifly bereits sagte, hat der Mega nicht für alle Pins Change-Interrupts. Allerdings habe ich in meinem Anwendungsfall acht digitale Pins gezählt, die ich nutzen könnte. Das käme ja für vier Encoder genau hin.

Der Vorschlag mit dem Timer-Interrupt klingt auch ganz nett.

Danke für Euren Input!

Wenn es Encoder für manuelle Eingaben sind dann ist Interrupt nicht gut: Es sind mechanisceh Kontakte und darum prellfähig. Du mußt entprellen. Die Rotationsfrequenz ist so gering daß es kein Interrupt baucht. Grüße Uwe

Hallo,

ich habe manuelle Drehgeber von Alps, mechanische Kontakte, die muss ich nicht entprellen, weil die Position durch den Graycode klar ist. Erst hatte ich mich über die Funktionsweise schlau gemacht, dann den Code von Peter Dannegger auf Arduino angepasst. Läuft ohne Interrupt mit reinem Polling. Nur falls die loop zu lange benötigt für irgendwelche Dinge ist eine Interruptabfrage aller X ms notwendig bzw. sinnvoll.

Doc_Arduino: Nur falls die loop zu lange benötigt für irgendwelche Dinge ist eine Interruptabfrage aller X ms notwendig bzw. sinnvoll.

Ein vom Timer getriggerter Interrupt um die Kontrolle in gewissen Zeiabständen zu aktivieren ist etwas ganz anderes als durch ein Signal der Encoder einen interrupt aufzurufen. Grüße Uwe

Er meint dass es u.U. auch reicht ohne Timer Interrupt zu Pollen wenn man sicherstellt dass loop() schnell genug ist

@Serenifly Falls Du das an meine Adresse geschrieben hast, das ist mir bewußt. Grüße Uwe

Hallo,

der TO möchte genau das, davon bin ich überzeugt. :) Wenn normales pollen nicht ausreichend ist, dann kann er mit nur einem Timer-Interupt jedem x beliebigen Pin mit Drehencodern betreiben und pollt mittels Interrupt. Hatte nur nochmal aufgegriffen was Serenifly schon geschrieben hatte mit dem Zusatzhinweis das ein entprellen für die beiden Phasen nicht notwendig ist. Nur den zusätzlichen Taster am Alps entprelle ich wie üblich. Mehr wollte ich gar nicht sagen. :o

Auf dem Arudino wird so einiges los sein. UART interrupt, i2c-Display, dass irgendwie auch alles ekelig ausbremst, etc. Im Moment geht es mit den pin change interrupts ganz gut. Aber ja, ich denke, ich würde mich mit einem Timer-Interrupt auch wohler fühlen.

Hallo,

bevor du dich jetzt in den Interrupt versteifst, lasse dir folgendes sagen. Interrupts sind nicht immer die Allheillösung. Man muss sich Gedanken machen was denn wieviel Zeit kostet. Wenn man alles mit Interrupts erschlagen würde, würden diese sich dann gegenseitig blockieren. Ist auch nicht Sinn der Sache.
Bei der seriellen verzögert das senden je nachdem wieviel man rausschickt. Rechne mal aus wieviele Bytes wieviel Zeit bei verschiedenen Baudraten benötigen. Damit hat man erstmal ein Gefühl dafür. Das einlesen kostet keine Zeit, weil das nur reagiert wenn wirklich ein Byte ankam und wartet ohne Blockierung auf das nächste. Die I2C Display blockiert in dem Sinne auch nicht, ständiges aktualisieren bringt auch nichts, sonst flimmert die Anzeige. Aller >500ms ist ausreichend, eigentlich 1s. Und wenn du nicht mit einer Übersetzung und Kurbel am Encoder drehst, und die loop schnell bleibt, sollte weiterhin reines Polling ausreichend sein. Ich vertrete die Ansicht, dass Interrupts nur sinnvoll sind wenn man sie wirklich benötigt und nicht pauschal verwendet nur weil sie vorhanden sind.

zur loop Zeitmessung eine Funktion vom User GuntherB. Wird in der loop an beliebiger Stelle einmal aufgerufen.

/*********************************************************************************
** LoopTiming()   by GuntherB              **
**********************************************************************************
** Funktion um die Loopaufrufzeiten zu ermitteln        **
** wird in der Loop aufgerufen              **
** benötigt ca (AL * 4 + 10 )Byte RAM           **
*********************************************************************************/
void LoopTiming(){
  const int AL = 100; // Arraylänge
  static unsigned long Timestamp[AL];
  static int Nr, Min=0xFFFF, Max, Avg;
  Timestamp[Nr++]=millis();
  if (Nr >= AL){  // Array voll, Daten auswerten
     for (int i = 1; i<AL; i++)  {
      Max = max(Max, Timestamp[i]-Timestamp[i-1]);
      Min = min(Min, Timestamp[i]-Timestamp[i-1]);
      Avg += Timestamp[i]-Timestamp[i-1];
    }
    Avg = Avg / (AL-1);
    Serial.println(F("Loop wird aufgerufen "));
    Serial.print(F("durchschnittlich alle "));Serial.print(Avg);Serial.println("ms");
    Serial.print(F("minimaler Abstand     "));Serial.print(Min);Serial.println("ms");
    Serial.print(F("maximaler Abstand     "));Serial.print(Max);Serial.println("ms");
    Min = 0xFFFF;
    Max = 0;
    Avg = 0;
    Nr = 0;
  }
}

Was für ein Schrottkode, hast du da nichts besseres gefunden?

Verbraucht in der dargestellen Version 410 Byte RAM, rechnet mit 16 Bit Ints, ...

Hallo,

Wie bitte? Schrott kann er erstmal nicht sein, denn er funktioniert. Wenn du dich daran störst, dann nicht nur meckern, sondern eine bessere Lösung anbieten. Dann kann man drüber reden.

Doc_Arduino:
Wie bitte? Schrott kann er erstmal nicht sein, denn er funktioniert.

Wenn man denn zufällig 420 Bytes frei hat,
nur 100 loops mitteln will (ups die Methode)
bei sehr schnellen loops in die Röhre schauen will (ups millis())
bei sehr langsamen loops in die Röhre schauen will (ups int)

Schrott bleibt Schrott.

So könnte man die mittlere Laufzeit von loop() etwas resourcenschonender ermitteln.

Speicherbedarf 8 Byte, kein Minimum oder Maximum, keine Ausgabe alle 100 loops, sondern nur einmal in der Sekunde.

void setup() {
  Serial.begin(115200);
}
void loop() {
  static unsigned long lastSecond;
  static unsigned long loopsPerSecond;
  unsigned long topLoop = millis();
  loopsPerSecond++;

  // codeToTest

  if (topLoop - lastSecond >= 1000) {
    lastSecond = topLoop;
    Serial.print(loopsPerSecond);
    Serial.print(F(" lps "));
    unsigned long nanosPerLoop = 1000000000L / loopsPerSecond;
    Serial.print(nanosPerLoop);
    Serial.print(F(" nS "));
    Serial.print(nanosPerLoop / 65);
    Serial.println(F(" cycles"));
    loopsPerSecond = 0;
  }
}
187171 lps 5342 nS 82 cycles
186981 lps 5348 nS 82 cycles
186791 lps 5353 nS 82 cycles
186981 lps 5348 nS 82 cycles
186791 lps 5353 nS 82 cycles
186981 lps 5348 nS 82 cycles
186791 lps 5353 nS 82 cycles
186982 lps 5348 nS 82 cycles
186982 lps 5348 nS 82 cycles
186790 lps 5353 nS 82 cycles
186982 lps 5348 nS 82 cycles
186790 lps 5353 nS 82 cycles
186982 lps 5348 nS 82 cycles

Hallo,

naja schön, cleverer Ansatz, wirklich, aber damit hat man nur einen Durchschnittswert. Das ist nicht Sinn der Sache und der Vergleich ist damit unfair. Zur Fehlersuche sind min und max schon sehr hilfreich. Man sieht sonst nicht ob die loop zwischendurch wegen irgendwas länger benötigt wie normal. Deswegen die Einspeicherung der Zwischenwerte. Die Zeiteinheit für Sekunden ist übrigens ein kleines "s". ;)

Das entscheidende Problem deines Ansatzes ist jedoch das die Berechnung und Ausgabe aller einer Sekunde mit in den nächsten Durchschnitt eingeht. Die eine Testausgabe soll 200ms dauern. Das kann nicht stimmen. Auch dein nackter Testcode kann keine 5ms dauern. Das geht in 0ms über die Bühne. Die Messfunktion selbst darf keinen Einfluss auf die eigentliche Messung haben!

void setup()
{
  Serial.begin(500000);   
}

void loop() {
  static unsigned long lastSecond;
  static unsigned long loopsPerSecond;
  unsigned long topLoop = millis();
  loopsPerSecond++;

  Serial.print("1234567890");         // Testausgabe 200ms ?

  if (topLoop - lastSecond >= 1000) {
    lastSecond = topLoop;
    Serial.println();
    Serial.print(loopsPerSecond);
    Serial.print(F(" lps "));
    unsigned long nanosPerLoop = 1000000000L / loopsPerSecond;
    Serial.print(nanosPerLoop);
    Serial.print(F(" nS "));
    Serial.print(nanosPerLoop / 65);
    Serial.println(F(" cycles"));
    loopsPerSecond = 0;
  }
}

Wo ist die Ausgabe von deiner Version und das läuffähige Beispiel?

Bitte genauso eine leere loop messen...

Die eine Ausgabe geht in den Durchschnitt der nächsten Sekunde ein, ist aber - nicht gefüllter Ausgabepuffer vorausgesetzt - völlig belanglos, jedenfalls in einer ganz anderen Größenordnung als das Blockieren an Serial durch das Messprogramm, wenn man alle 100 loops eine fette Ausgabe macht und eine leere loop hat.

Hallo,

die leere loop haste selbst gemessen in #16. Ergebnis 5ms. Ein anderes Ergebnis erhalte ich auch nicht. Was nicht sein kann. Testcode aus #17 ist lauffähig so wie er ist und erzeugt: 4993 lps 200280 nS 3081 cycles 200ms, was auch nicht sein kann.

Überlege nochmal was der Unterschied zwischen beiden Messfunktionen ist. Es ist völlig belanglos wenn während der Messausgabe die loop blockiert wird. Interessiert niemand der die loop Zeiten überprüfen möchte. Denn diese Blockierung geht in die nächste Messung nicht ein. Dafür wird die reine loopdauer exakt erfasst und ausgewertet. Sie verfälscht die Messung nicht. Das ist der Sinn der Übung.

Deine Messfunktion verfälscht die eigentliche Messung. Warum das nun so extrem falsch ist kann ich im Moment auch nicht sagen. Nur das sie deutlich daneben liegt sieht man. Denn eigentlich soll sie die Zeit messen für die eine Serial.print Ausgabe, was sie irgendwie nicht kann. Warum auch immer.

So einfach ist das also nicht wie behauptet. Deshalb würdest du gut daran tun deine "Schrott Behauptung" zurückzunehmen. ;)