Interrupt-Rotary Encoder-Sketch funktioniert auf Uno, nicht aber auf Mega2560

Hallo Leute,

ihr könnt mir sicher helfen…

Ich habe folgenden Sketch an einem Uno laufen, hätte ihn nun aber auch gern an einen Mega.
Nur mit Pins ändern ist es leider nicht getan, habe alle ext. Interrupts des Mega durchprobiert.

Kann mir wer sagen woran das liegt??

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
*/

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  Serial.begin(115200); // start the serial monitor link
}

void PinA(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void PinB(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void loop(){
  if(oldEncPos != encoderPos) {
    Serial.println(encoderPos);
    oldEncPos = encoderPos;
  }
}

Hi

Vll. findest Du hier die gesuchten Informationen:
[http://www.circuitstoday.com/arduino-mega-pinout-schematics]](http://www.circuitstoday.com/arduino-mega-pinout-schematics)
Denke, Die von Dir benutzten Interrupts liegen beim Mega auf anderen Pins.

Davon ab ist ein DrehEncoder schnarch-lahm - Den kann man auch pollen - ganz ohne Interrupts.
Aber es gibt auch Funktionen (oder Makros?), mit Denen man eine Pin-Nummer in einer Interrupt-Nummer umwandeln kann - heißt ähnlich pintointerrupt(Arduino_Pin) und gibt die Interrupt-Nummer, oder -1 (wenn ich mich recht entsinne), wenn der Pin KEINEN Interrupt unterstützt (die PCINT sind dort wohl nicht enthalten).

Für Heute ist's mir zu spät, vll. hilft's Dir so ja schon.

MfG

Edit URL-Tag geflickt ... 3x

Vermutlich liegt das Problem darin, daß die Pins bei beiden Controllern in verschiedenen Ports liegen. Kontrolliere mal, welche Arduino Pins am PIND hängen. Und dann prüfe, welche Pins interruptfähig sind. Bei attachInterrupt() sollte eigentlich ein digitalPinToInterrupt drinstehen.

SEI und CLI sind in ISR überflüssig, kosten nur Zeit. Schneller geht es auch mit dem PCINT statt attachInterrupt().

Hallo,

ich habe eine Vermutung bzw. bin ich mir sicher die Lösung zu haben.

Die Interruptnummer 0 und 1 sind zwar auf dem Uno und Mega die gleichen Pins 2 und 3, aber das Portregister ist verschieden. Du musst statt PIND auf dem Mega PINE abfragen und die Bitmaske anpassen.

Edit:
manchmal überschneiden sich die Antworten während man tippt

cli und sei in einer Interrupt Routine sind deswegen überflüssig, weil Interrupts in einer ISR automatisch gesperrt sind. static für deine beiden Pins 2 und 3 ist auch überflüssig. Mach sie lieber const byte statt static int.

Ansonsten kannst das hier einmal lesen. Drehgeber – Mikrocontroller.net

Pinouts.zip (1.58 MB)

Ist dir auch bewusst das.

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3

A2 und A3 sind nicht die digitalen Pins, nur mal zum Sicherheit dann wär es.

static int pinA = A2;
static int pinB = A3;

Das sind aber analoge Pins.

Hi,
Danke für den vielen Lesestoff :slight_smile: Einiges hat mir sehr geholfen die Materie besser zu verstehen, einiges auf das ich gestossen bin verstehe ich nicht mal ansatzweise, aber egal erstmal :slight_smile:
Ich habe leider noch sehr wenig Erfahrung mit Microcontrollern und deren Programmierung, das ist mein 2. Projekt
Danke auch für die anderen Tips den Sketch zu verbessern...

Ich glaube nun grundlegend zu verstehen wo das Problem ist...

ich habe den Atmega328p und den Atmega2560 verglichen....

@Doc_Arduino:

Bist du denn sicher dass ich PINE abfragen muss?

Der Atmega328p hat INT0 und INT1 an Adresse PD2 und PD3.

Der Atmega2560 hat INT0 und INT1 an Adresse PD0 und PD1.

Bei beiden ist also INT0 und INT1 an PortD

Müsste da nicht PIND gleich bleiben und sich nur die Bitmaske verändern? falls nicht hab ichs wohl doch nicht durchblickt.
Ich werds probiern....

Ach!! wenn ich Pin 2+3 beibehalten möchte muß ich PINE abfragen !!!! Ist zwar dann INT4 und INT5 und die liegen an PE4 und PE5.

Kann mir diese Aussage jemand bestätigen bitte? :wink:

Hallo,

mit den Interruptnummer bin auch schon auf die Schn... gefallen.

Es gibt dafür eine Tabelle.
https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Wenn man jetzt wie du die Funktion konfiguriert, dann muss man die Interruptnummer angeben.
attachInterrupt(1, PinB, RISING)
Bei dir Interrupt 0 und 1.
Diese liegen laut der Tabelle beim Uno und Mega auf den gleichen Pins 2 und 3.
Allerdings liegen die Pins 2 und 3 beim Uno und Mega nicht im gleichen Port-Register.
D vs. E. Deswegen die Pinoutübersicht.

Du könntest auch das angeben.
attachInterrupt( digitalPinToInterrupt(3), PinB, RISING)
Dann holt sich die Funktion digitalPinToInterrupt an Hand der Pinnummer die gesuchte Interruptnummer selbst.
Was am Ende wieder auf 0 und 1 hinausläuft.

Wenn wir hier über Pinnummern reden ist immer die Arduinonummerierung gemeint.

Edit:
also mit Chromebrowser kann ich keine Links in url Tags einfügen, alles kaputt, mit Firefox gehts noch

Hallo,

jetzt bin auch durcheinander bei genauer Betrachtung. Entweder stimmt die Tabelle nicht oder das Pinout.
Aber! Möglicherweise wird das im Arduino Framework verbogen sodass die Tabelle wieder gültig ist. Damit Pin 2 und 3 gleich sind beim Wechsel vom Uno auf Mega. Probiere es einfach aus.

Hallo,

habs getestet, ist wie vermutet. Die Tabelle stimmt. Pin 2 ist auch beim Mega Interruptnummer 0.
Diese Interruptnummer 0 hat nichts mit dem echten INT0 zu tun.
Diese Interruptnummer 0 ist wieder eine Arduinozählweise für die "Kompatibilität" von Uno auf Mega.
Ich weiß ist etwas Kaos. Deswegen verwenden viele die attach Funktion nicht.

Danke Doc!

dann war ich ja gar nicht soo daneben.....

ich habe eben den Code probiert, leider krieg ich immer noch keine Werte raus.

geändert hab ich inwischen:

-const byte pinA = 2;
-attachInterrupt(digitalPinToInterrupt(2),PinA,RISING); // set an interrupt on PinA, looking for a rising
-reading = PINE & 0xC;
-if(reading == B00110000 && aFlag) // Bit 3+4 geändert in 5+6 (an 2560 PE4 + PE5 )
-SEI und CLI flogen raus

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
*/

const byte pinA = 2; // Our first hardware interrupt pin is digital pin 2
const byte pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(digitalPinToInterrupt(2),PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(digitalPinToInterrupt(3),PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  Serial.begin(115200); // start the serial monitor link
  Serial.println("Serial OK");
 
}

void PinA(){
 
  reading = PINE & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00110000 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00010000) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation

}

void PinB(){

  reading = PINE & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00110000 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00100000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation

}

void loop(){
  if(oldEncPos != encoderPos) {
    Serial.println(encoderPos);
    oldEncPos = encoderPos;
  }
}

Hallo,

so ein schlecht wartbare Code hat seine Eier.

Die Binäränderung auf Bit 4 und 5 muss sich laut meiner Meinung auch in der Maske davor wiederspiegeln. 0x0C müßte demzufolge in 0x30 geändert werden.

Besser wäre ein globales const byte mask = 0x30 (eine zentrale Änderung)
und das mask setzte dann überall ein für das aktuell falsche 0x0C und eben B00110000.
Oder schreibst meinetwegen die Binärform dahinter.

Ich würde noch die serielle Ausgabe bremsen falls es klappt. Nimm erstmal eine Baudrate von 9600.

Ansonsten hier mal schauen ...
https://playground.arduino.cc/Main/RotaryEncoders/
oder eben den Link zum Drehgeber lesen und nachbauen. :slight_smile:

Doc_Arduino:
Die Binäränderung auf Bit 4 und 5 muss sich laut meiner Meinung auch in der Maske davor wiederspiegeln. 0x0C müßte demzufolge in 0x30 geändert werden.

Ja, das ist natürlich auch auf jeden Fall notwendig. Auch ich bin der Meinung, dass man diese Masken zentral definieren sollte. So ist das extrem änderungsunfreundlich und fehlerträchtig. Über entsprechende #ifdefs kann man das dann an dieser Stelle auch so schreiben, dass der Code ohne Änderung sowohl auf dem UNO als auch auf dem Mega läuft.

gisa:
-const byte pinA = 2;
-attachInterrupt(digitalPinToInterrupt(2),PinA,RISING); // set an interrupt on PinA, looking for a rising

Dann sollte aber auch in digitalPinToInterrupt(pinA) stehen. Wozu Konstante definieren, wenn man sie nachher nicht benutzt?

Hallo,

falls das immer noch nicht mit den Änderungen klappt, dann nimm meinen. Der hat noch paar Features drin wie Relativzähler und dynamischen Zähler. Dazu wird die PinListLib "CombiePin" benötigt vom gleichnamigen User combie hier aus dem Forum. Beide Libs, CombiePin und docEncoder, entpackst du nach \Dokumente\Arduino\libraries\ danach IDE neu öffnen und ein Encoderbsp. auswählen. Pins und Rasterung ändern und mal ausprobieren. Es gibt Drehencoder die machen 4 Schritte pro mechanischer Rastung und andere 2. Kannst natürlich auch 1 einstellen, dann gibt keine Anpasssung an die Rastung. Ggf. die Pins der Phasen vertauschen falls was spinnt. Das erste Bsp. zeigt eine Demo mit verschiedenen relativen Zählumfängen. Das zweite Bsp. mit dynamischer Zähländerung je nachdem wie schnell man dreht. Ich hoffe du kommst damit klar und es funktioniert auch bei dir. Wurde bisher noch nicht veröffentlicht. Betrachte dich als Testerin.

Deine Downloadlinks führen ins Leere.

Gruß Tommy

Hallo,

bei mir nicht. Kann mit Firefox und Chrome die Dateien anklicken und speichern. Die sind auch normal als Anhang hochgeladen, also nichts besonderes.

Ok, jetzt geht es auch.

Gruß Tommy

ES funktioniert!!!! Ein riesiges Danke an alle Beteiligten!!!

Doc_Arduino:
Die Binäränderung auf Bit 4 und 5 muss sich laut meiner Meinung auch in der Maske davor wiederspiegeln. 0x0C müßte demzufolge in 0x30 geändert werden.

Besser wäre ein globales const byte mask = 0x30 (eine zentrale Änderung)

Nach dieser Änderung hats geklappt!

@Arduini Doc:

danke auch für die Files, werde ich auf jeden Fall auch noch probieren.

Hier noch der funktionierende Code

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
*/
const byte mask = 0x30;
const byte pinA = 2; // Our first hardware interrupt pin is digital pin 2
const byte pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  Serial.begin(9600); // start the serial monitor link
  Serial.println("Serial OK");
 
}

void PinA(){
 
  reading = PINE & mask; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00110000 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00010000) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation

}

void PinB(){

  reading = PINE & mask; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00110000 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00100000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation

}

void loop(){
  if(oldEncPos != encoderPos) {
    Serial.println(encoderPos);
    oldEncPos = encoderPos;
  }
}

Hallo,

schön das es noch geklappt hat. Die vollständige Nutzung von mask wäre wenn du noch die Zeilen mit dem reading Vergleich abänderst. Binärwert durch mask ersetzen. Eine Anpassungsfehlerquelle weniger.

if (reading == B00110000 && bFlag)

ändern in

if (reading == mask && bFlag)