Interrupt Bug (Mehrere Encoder an einem Interrupt)

Hallo zusammen!

Ich habe gerade etwas Seltsames entdeckt, vielleicht kann mir jemand erklären was hier schief läuft. Ich versuche mal kurz den Kontext zu erklären:

In meinem Projekt verwende ich einen Arduino Mega 2560, eine Adafruit Neotrellis LED/Button Matrix und insgesamt neun Encoder um verschiedene Einstellungen der Matrix zu steuern.

Um die Encoder auszuwerten verwende ich die OneButton.h und EncoderStepCounter.h Bibliotheken.
Mein Projekt besteht im Prinzip aus mehreren Lichtlaufleisten, die von verschiedenen Timing Funktionen gesteuert werden, zwischen denen mit den Encoder umgeschaltet werden kann.

In einem Test-Sketch, der nur die Encoder behandelt, funktionieren diese problemlos. Zusammen mit dem Code, der die LED Matrix steuert, werden die Encoder nicht mehr korrekt ausgelesen.

Soweit so gut - offenbar werden die Encoder im Zusammenspiel mit den Neotrellis nicht mehr schnell genug ausgelesen. Eine Möglichkeit der EncoderStepCounter Bibliothek das Problem zu beheben ist nun mit externen Interrupt Pins zu arbeiten. Der betreffende Encoder soll damit mit jeder Bewegung die Interrupt Funktion aufrufen und wird dadurch flüssig ausgelesen. Das funktioniert für einen einzelnen Encoder auch super. Da das Mega 2560 Board nun jedoch nur sechs externe Interrupt Pins besitzt, habe ich mit verschiedenen Möglichkeiten gespielt alle neun Encoder zu versorgen.

Jetzt zu dem mir nicht erklärbaren seltsamen Verhalten: Bei meinen Versuchen bin ich irgendwann an einen Punkt gekommen, wo ich fälschlicherweise dem attachInterrupt Befehl einen Pin zugewiesen habe, an dem gar kein Encoder (und auch nichts anderes) hängt. Das Erstaunliche daran ist, dass meine Encoder nun aber plötzlich alle funktionieren wie sie sollen. :o

Anbei der Code für die Encoder, der kommt so mit aufsteigender Nummerierung (ENC0-ENC8) mehrfach in meinem Sketch vor. An dem im Code definierten Interrupt Pin 3 hängt wie gesagt gerade nichts. Aufgefallen ist mir die ganze Sache nur, weil ich mehrfach zwischen Pin 3 und Pin 13 für den ersten Encoder gewechselt habe (3 ist ein externer Interrupt Pin, 13 kann man als PinChangeInterrupt verwenden).

Kann jemand erklären warum das funktioniert, obwohl es das eigentlich nicht sollte?

#include "OneButton.h"
#include "EncoderStepCounter.h"

const int CLK0 = 12;
const int DT0 = 13;
const int BTN0 = 11;

int pos0A = 0;
int pos0B = 0;

int btnState0 = 0;
bool btnChanged0 = false;

EncoderStepCounter ENC0(CLK0, DT0);
OneButton button0(BTN0, true);


void setup() {

  Serial.begin(115200);
  ENC0.begin();

  attachInterrupt(3, interrupt, CHANGE); //sorgt dafür, dass die Encoder richtig ausgelesen werden

  button0.attachClick(click0);

}

void loop() {
  ENC0.tick();
  button0.tick();
  encoderMenu();
  

//Neotrellis Zeug


  Serial.print(pos0A);
  Serial.print("\t");
  Serial.println(pos0B);
  
}


void interrupt()
{
  ENC0.tick();
}

void click0()
{ btnState0 = !btnState0;
  btnChanged0 = true;
}

void encoderMenu()
{
  if (btnState0 == 0)
  { if (btnChanged0 == true)
    { ENC0.setPosition(pos0A);
      btnChanged0 = false;
    }

    pos0A = ENC0.getPosition();
    if (pos0A > 14)
    {
      ENC0.setPosition(15);
    }
    if (pos0A < 1)
    {
      ENC0.setPosition(0);
    }
  }

  /* ----- Enc0 Menu 1 -----*/
  if (btnState0 == 1)
  { if (btnChanged0 == true)
    { ENC0.setPosition(pos0B);
      btnChanged0 = false;
    }

    pos0B = ENC0.getPosition();
    if (pos0B > 14)
    {
      ENC0.setPosition(15);
    }
    if (pos0B < 1)
    {
      ENC0.setPosition(0);
    }
  }
}

(deleted)

Peter-CAD-HST:
probier mal aus dem IDE das Encoder Library - NoInterrupts Example aus.

Vllt klappt das damit.

Danke für die Antwort :slight_smile: Also das Beispiel kenne ich, mit der Encoder.h Bibliothek habe ich vorher auch schon rumexperimentiert. Die ist für meine Zwecke etwas umständlich, da die mir pro Encoder Schritt vier Impulse ausgibt. Die EncoderStepCounter Bibliothek gibt pro Schritt nur einen Impuls aus. Ich verwende die Encoder im Prinzip als Stufenschalter, deswegen passt das so besser. Ich kann mit dem Beispiel das Problem des fehlerhaften Auslesens der Encoder reproduzieren, aber leider auch nicht lösen.

Vielleicht noch mal konkreter formuliert:

Wenn ich die Zeile "attachInterrupt(3, interrupt, CHANGE);" herauskommentiere, werden alle meine Encoder fehlerhaft ausgelesen.
Wenn ich den ersten Encoder an Pin 3 hänge, wird dieser richtig ausgelesen, da er den Interrupt auf Pin 3 aktiviert. Das kann ich jedoch nicht mit allen 9 Encodern machen, da ich nur 6 Interrupt Pins zur Verfügung habe.
Wenn ich die Zeile drin lasse, aber nichts an Pin 3 hängt, funktionieren alle Encoder so, als würde jeder einzelne an einem eigenen Interrupt Pin hängen.

seele27:

void loop() {

ENC0.tick();
}

void interrupt()
{
  ENC0.tick();
}

Kannst Du mal erklären, was in den Zeilen passieren soll?

Ich kann zumindest mal interpretieren was da passieren sollte. :wink:

Aus der EncoderStepCounter.ccp:

 // Reads the pins and detects rotation
void EncoderStepCounter::tick() {
  // Get actual position change
  bool posvalue;
  EncDir direction;
  if (!CheckEncoderPos(posvalue, direction)) return;

  // For half step encoders, each half step change counts
  if (encoder_type == HALF_STEP) {
    encoderpos += direction;
  }

  // For full step encoders, only the "one position" counts but we have to
  // check the "zero position" also for reliability.
  else {
    if (posvalue == 0)
      last_zero_dir = direction;
    else if (last_zero_dir == direction) {
      last_zero_dir = UNKNOWN_DIR;
      encoderpos += direction;
    }
  }
}

ENC0.tick im Loop checkt ob sich der Encoder bewegt. Das allein ist jedoch zu langsam um alle Schritte auszulesen. Teilweise werden sogar negative Schritte als positive ausgegeben.

In der Bibliothek gibt es deshalb ein Beispiel für die Verwendung von Interrupts:

 void setup() {
  Serial.begin(9600);
  // Initialize encoder
  encoder.begin();
  // Initialize interrupts
  attachInterrupt(ENCODER_INT1, interrupt, CHANGE);
  attachInterrupt(ENCODER_INT2, interrupt, CHANGE);
}

// Call tick on every change interrupt
void interrupt() {
  encoder.tick();
}

Das Aufrufen von ENC0.tick im Interrupt sorgt schließlich dafür, dass bei jeder Änderung am festgelegten Interrupt Pin der Encoder neu ausgelesen wird. Damit werden alle Bewegungen korrekt ausgelesen.

Ich habe gestern Abend auch noch mal getestet, was passiert, wenn ich den .tick Befehl aus dem Loop raus nehme. Der Sketch zeigt das gleiche Verhalten wie zuvor: Irgendwas ruft den Interrupt 3 ab, ohne das etwas an dem Pin hängt.

Ich habe die vage Vermutung, dass die Sache aufgrund falscher Syntax passiert. In der Referenz zu attachinterupt() steht folgendes:

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) (recommended)
attachInterrupt(interrupt, ISR, mode) (not recommended)
attachInterrupt(pin, ISR, mode) (not recommended)

Ohne digitalPinToInterrupt(pin) benutzt der Befehl attachInterrupt() demnach nicht wie ich dachte Pin 3 als Interrupt, sondern auf dem Mega 2560 Digital Pin 20 (SDA).

An Pin 20 hängt jedoch wiederum auch nichts.

Da die NeoTrellis am I2C Port oben rechts auf den Board mit Pins SDL und SDA verbunden sind, drängt sich mir eine Vermutung auf: Kann es sein, dass die Datenübertragung am SDA Port den Interrupt auf Pin 20 auslöst? Sind die mehrfach vorhandenen SDA / SDL Pins auf dem Board alle miteinander verbunden?