Variablenwert durch Interrupt übernehmen

Hallo,
ich versuche mich das erste Mal an einem Rotary Encoder vom Typ KY040. Einlesen der Werte und des Switches klappt alles. Ich lasse mir die Werte im seriellen Monitor und auf einem LCD-Display ausgeben. Auch das klappt.

Nun aber habe ein Verständnisproblem mit dem Interrupt. Das niederdrücken der Welle löst einen Interrupt aus, klappt.
Aber, im Void Interrupt möchte ich den aktuellen Wert der Drehvariablen (neuePosition) an eine andere Variable (aktuell) übergeben. Das klappt aber nicht.
Wo liegt mein Verständnisproblem? Könnt ihr mir auf die Sprünge helfen?

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>

const int CLK = 6;      // Definition der Pins. CLK an D6, DT an D5.
const int DT = 5;
const int SW = 2;       // Der Switch wird mit Pin D2 Verbunden. ACHTUNG : Verwenden Sie einen interrupt-Pin!
long altePosition = -999;  // Definition der "alten" Position (Diese fiktive alte Position wird benötigt, damit die aktuelle Position später im seriellen Monitor nur dann angezeigt wird, wenn wir den Rotary Head bewegen)
long neuePosition = 0;
long aktuell = 0;
Encoder meinEncoder(DT, CLK); // An dieser Stelle wird ein neues Encoder Projekt erstellt. Dabei wird die Verbindung über die zuvor definierten Varibalen (DT und CLK) hergestellt.
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


void setup()   // Beginn des Setups
{
  Serial.begin(9600);
  lcd.begin(16, 2);              // initialize the lcd
  lcd.clear();
  lcd.home ();                   // go home

  pinMode(SW, INPUT);   // Hier wird der Interrupt installiert.

  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE); // Sobald sich der Status (CHANGE) des Interrupt Pins (SW = D2) ändern, soll der Interrupt Befehl (onInterrupt)ausgeführt werden.
}

void loop()
{
  long neuePosition = meinEncoder.read();  // Die "neue" Position des Encoders wird definiert. Dabei wird die aktuelle Position des Encoders über die Variable.Befehl() ausgelesen.
  lcd.setCursor ( 0, 0 );        // go to the next line

  if (neuePosition != altePosition)  // Sollte die neue Position ungleich der alten (-999) sein (und nur dann!!)...
  {
    altePosition = neuePosition;
    Serial.println(neuePosition);      // ...soll die aktuelle Position im seriellen Monitor ausgegeben werden.
    lcd.print("Ist: ");
    lcd.print(neuePosition);
    lcd.print ("  ");
    lcd.print ("soll: ");
    lcd.print (aktuell);
    lcd.print ("  ");
  }
}

void Interrupt() // Beginn des Interrupts. Wenn der Rotary Knopf betätigt wird, springt das Programm automatisch an diese Stelle. Nachdem...
{
  Serial.println("Switch betaetigt"); //... das Signal ausgegeben wurde, wird das Programm fortgeführt.
  aktuell = neuePosition;
}

Könnt ihr mir auf die Sprünge helfen?

Klar!
Die Zauberworte sind:

  1. volatile
  2. atomic

Die neuePosition gibt es bei Dir zweimal! Die globale Version wird in Interrupt() verwendet, die andere ist lokal in loop(). Beide sind völlig unabhängig voneinander, die globale Version bleibt also auf 0
stehen.

  long //Das macht diese Variable lokal. Streichen!
 neuePosition = meinEncoder.read();  // Die "neue" Position des Encoders wird definiert.

Serielle Ausgaben haben in Interrupts übrigens nichts verloren

DrDiettrich:
Die neuePosition gibt es bei Dir zweimal! Die globale Version wird in Interrupt() verwendet, die andere ist lokal in loop(). Beide sind völlig unabhängig voneinander, die globale Version bleibt also auf 0
stehen.

  long //Das macht diese Variable lokal. Streichen!

neuePosition = meinEncoder.read();  // Die "neue" Position des Encoders wird definiert.

Da steige ich noch nicht durch. Vor dem Setup habe ich "long neuePosition" deklariert und mit dem Wert Null vorbesetzt. Lass ich da das long weg, bekomme ich eine Fehlermeldung:

'neuePosition' does not name a type; did you mean 'altePosition'?

In Loop weise ich ihr einen Wert vom Rotary zu.
Und in Interrupt lese ich sie nur aus und weise sie der Variablen "aktuell" zu. Das funktioniert übrigens. Ich hatte nur die LCD-Ausgabe innerhalb der if-Anweisung, wurde also erst bei erneutem drehen angezeigt. Mit dem geänderten Sketch wird "aktuell" richtig beim drücken angezeigt.
Mit volatile und atomic kann ich nichts anfangen, muss ich erstmal gugeln.

Serielle Ausgaben haben in Interrupts übrigens nichts verloren

Das erzähl mal Funduino. Da habe ich das her.

urdaino:
In Loop weise ich ihr einen Wert vom Rotary zu.

Nein, das tust Du nicht. Im loop legst Du eine neue (lokale) Variable 'neuePosition' an, der Du den Wert zuweist. Du solltest das 'long' in der Zuweisung im loop weglassen, nicht bei der globale Definition.

Edit: und was Funduino angeht: leider ist auch nicht alles, was in den Tutorials steht vorbildlich und korrekt. Ist natürlich blöd, weil genau die, für die die Tutorials geschrieben sind, das nicht erkennen können. Aber so ist das Internet nunmal ....

Hallo,

die Variablen Initialiserung vor setup erzeugt dir globale Variablen die überall im Code sichtbar sind. Also du hast eine globale Variable namens neuePosition mit Datentyp long erzeugt.

Jetzt gibt es aber noch lokale Variablen. Diese sind nur innerhalb von Funktionen sichtbar. Innerhalb von Funktionen haben lokale Variablen Vorrang. Selbst wenn eine gleichnamige globale Variable existiert.

Jetzt kommts. loop und setup sind auch nur Funktionen!
Du erzeugst innerhalb der loop eine neue lokale Variable neuePosition mittels
long neuePosition = meinEncoder.read();

Lass den Datentyp long weg und es wird die global existierende benutzt.

Mit Datentyp und Variablenname wird immer eine neue Variable deklariert. Immer!

Da ich die Encoder Lib nicht habe, kann ich keine Kompilierung testen. Habs mal abgeändert und ein struct angelegt. Hoffe keinen Fehler eingebaut zu haben.

Aber! Frage bitte keinen simplen Taster mit einem Interrupt ab. Damit erspart man sich auch das volatile/atomic Gerassel. Welches ansonsten unbedingt notwendig ist.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>
#include <util/atomic.h>

const int CLK = 6;      // Definition der Pins. CLK an D6, DT an D5.
const int DT = 5;
const int SW = 2;       // Der Switch wird mit Pin D2 Verbunden. ACHTUNG : Verwenden Sie einen interrupt-Pin!

struct EncoderDaten
{
  long alt = -999;  // Definition der "alten" Position (Diese fiktive alte Position wird benötigt, damit die aktuelle Position später im seriellen Monitor nur dann angezeigt wird, wenn wir den Rotary Head bewegen)
  long neu = 0;
  volatile long aktuell = 0;
};
EncoderDaten position;

Encoder meinEncoder(DT, CLK); // An dieser Stelle wird ein neues Encoder Projekt erstellt. Dabei wird die Verbindung über die zuvor definierten Varibalen (DT und CLK) hergestellt.
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


void setup()   // Beginn des Setups
{
  Serial.begin(9600);
  lcd.begin(16, 2);              // initialize the lcd
  lcd.clear();
  lcd.home ();                   // go home

  pinMode(SW, INPUT);   // Hier wird der Interrupt installiert.

  attachInterrupt(digitalPinToInterrupt(SW), Interrupt, CHANGE); // Sobald sich der Status (CHANGE) des Interrupt Pins (SW = D2) ändern, soll der Interrupt Befehl (onInterrupt)ausgeführt werden.
}

void loop()
{
  position.neu = meinEncoder.read();  // Die "neue" Position des Encoders wird definiert. Dabei wird die aktuelle Position des Encoders über die Variable.Befehl() ausgelesen.
  lcd.setCursor ( 0, 0 );        // go to the next line

  if (position.neu != position.alt)  // Sollte die neue Position ungleich der alten (-999) sein (und nur dann!!)...
  {
    position.alt = position.neu;
    Serial.println(position.neu);      // ...soll die aktuelle Position im seriellen Monitor ausgegeben werden.
    lcd.print("Ist: ");
    lcd.print(position.neu);
    lcd.print ("  ");
    lcd.print ("soll: ");
    
    long aktuell {0};
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
      aktuell = position.aktuell;
    }
    
    lcd.print (aktuell);
    lcd.print ("  ");
  }
}

void Interrupt() // Beginn des Interrupts. Wenn der Rotary Knopf betätigt wird, springt das Programm automatisch an diese Stelle. Nachdem...
{
  position.aktuell = position.neu;
}

Doc_Arduino:
Hallo,

die Variablen Initialiserung vor setup erzeugt dir globale Variablen die überall im Code sichtbar sind. Also du hast eine globale Variable namens neuePosition mit Datentyp long erzeugt.

Jetzt gibt es aber noch lokale Variablen. Diese sind nur innerhalb von Funktionen sichtbar. Innerhalb von Funktionen haben lokale Variablen Vorrang. Selbst wenn eine gleichnamige globale Variable existiert.

Jetzt kommts. loop und setup sind auch nur Funktionen!
Du erzeugst innerhalb der loop eine neue lokale Variable neuePosition mittels
long neuePosition = meinEncoder.read();

Lass den Datentyp long weg und es wird die global existierende benutzt.

Mit Datentyp und Variablenname wird immer eine neue Variable deklariert. Immer!

Das war sehr gut erklärt. So ähnlich hatte ich es jetzt, ohne es genau zu wissen, zum Schluss auch gemacht.
Recht herzlichen Dank dafür

urdaino:
Das erzähl mal Funduino. Da habe ich das her.

Das kann gehen. Das kann aber aber auch Probleme bereiten. So ist das generell mit Interrupts.

Das gleiche mit atomic. Wenn man Glück hat geht es. Aber man hat zufällig Werte bei denen das egal ist. Sauber ist aber so:
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

Kurz: Multi-Byte Variablen die in Interrupts verändert werden müssen außerhalb bei abgeschalteten Interrupts ausgelesen werden. Weil sonst zwischen den Bytes ein neuer Interrupts dran kommen kann. Und das ist wahrscheinlicher als man denkt.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.