Altes Problem - Taster

Hallo Jungs und Mädels,

ich hab hier wohl ein altes/alltägliches Problem, das wohl schon zigfach gelöst wurde, ich jedoch einfach nicht umgesetzt bekomme.

Es geht um die Mehrfachverwendung eines Tasters.

Mein Taster soll beim ersten Drücken die Funktion menu aufrufen (im Moment ist dort nur eine Option vorhanden) und dort die "Auswahl" dann mit einem zweiten Drücken bestätigen und dadurch die Funktion scan aufrufen.

Ich habe jetzt schon alles mögliche ausprobiert, habe mit 2-3 Variablen gearbeitet, die dann jeweils den Status des Tasters zwischenspeicherten und dann versucht das ganze mit if Abfragen und mehreren Bedingungen zu realisieren. Aber irgendwie haut das ganze nicht so hin, wie ich will. Wenn ich denke, das es eigentlich richtig rein müsste, überspringt er dennoch die zweite Abfrage.

Da die schönste Beschreibung ohne Code ja doch nichts bringt, habe ich die relevanten Codestücke mal unten angefügt:

#include <Bounce.h>
// MenuButton
int RotaryButtonState = 0;
int RotaryButtonStateOld =0;
int RotaryAction =0;
void menu (){            
            //Anzeige SCAN
            siebensegment (scan_einer[0], scan_zehner[0]);
           delay (1000);
            unsigned long startTime = millis();
            while (millis() - startTime < 4000) //Wartezeit
                { // check for input. Break if found
                  RotaryButtonState = bouncer.read();
                 
                    if ((RotaryButtonState == 1)&& (RotaryButtonStateOld == 1)) {
                                         scan()     ;}
                                     } // When we get here, either there was input or the time expired.
                                     
            //ohne Scan Aktivierung zurück zur Anzeige
            siebensegment (kanaele_einer[x], kanaele_zehner[x]);  

  }
void loop() {
    bouncer.update ( );
    RotaryButtonState = bouncer.read();
   if ((RotaryButtonState == 1)&& (RotaryButtonStateOld == 0)&& (RotaryAction==0)) {      // enter menu   
        RotaryButtonStateOld = 1;
        RotaryAction=1;
        RotaryButtonState = bouncer.read();    
              if((RotaryButtonState == 0)&&(RotaryButtonStateOld == 1)) {(RotaryButtonStateOld = 0);}  
        menu();}  
}

Kann mir irgendjemand helfen? Allein steig ich wohl einfach nicht durch. Ich verstehe auch irgendwie nicht warum die die Variable nicht manuell nach dem Ausführen immer wieder zurücksetzen kann.

Also quasi nach Betreten der Funktion menu den Status mit "RotaryButtonState=0" wieder zurücksetzen und dann erneut auslesen und mit ner If Bedingung dann die Scan-Funktion auslösen. Da springt er dann auch immer direkt durch.

Wo ist mein Denkfehler?

Problem:

  1. Tastendruck -> Menu
  2. Tastendruck -> Auswahl aus Menu
    nächster Tastendruck wieder wie 1.

Lösungsvorschlag (ungetestet):

RotaryButtonState = bouncer.read();
if (RotaryButtonState == 1) { // Taste gedrückt
  if (RotaryButtonStateOld == 1) { // 2. Tastendruck
    // Menuanzeige wieder löschen und abfragen, 
    // was aus dem Menu gewählt wurde und entsprechende Routine(n) aufrufen
    .....
    RotaryButtonStateOld = 0;
    .....
  } else { // 1. Tastendruck
    // jetzt Menuauswahl anzeigen
    .....
    RotaryButtonStateOld = 1; // nächster Tastendruck betrifft Menuauswahl
  }
}

So die grundsätzliche Steuerung in der "loop()" - die Feinheiten musst Du selbst noch einbauen :wink:

Gruß, mmi

Also ich habe das bei meiner Menüumsetzung in etwa so:
screen Ich habe eine Variable in der speichere ich welchen Screen ich gerade auf dem Display anzeige
encoderWert Eine in der der Status des Buttons ist.
pos Eine an welcher Position ich im Menü bin
sel Eine ob die Position gerade aktiv ist

Naja und dann geht's los mit den Ifs

wenn screen = Standard ist und enoderwert=gedrückt dann screen= setup
wenn screen = setup ist und encoderwert =gedrückt und pos = bei stunden und sel = nicht ausgewählt dann sel 0 ausgewählt.

Das ganze habe ich mit IFs und switch/case gemacht.
War ne ganz schöne Fummelei bis das lief. Ist bestimmt nicht perfekt (bin selber noch nicht so lange dabei) aber läuft!
Ich kann meine RealTimeClock stellen und 9 Variablen damit ändern und im Eeprom speichern.
Updates hierzu wird es hier geben: http://arduino.cc/forum/index.php/topic,94552.msg741137.html#msg741137

mmi:
So die grundsätzliche Steuerung in der "loop()" - die Feinheiten musst Du selbst noch einbauen :wink:

Hallo mmi,

ich hab dein Beispiel jetzt mal eins zu eins umgesetzt. Nach anfänglichen Schwierigkeiten tut es jetzt auch.

Ich bekomme nur noch nicht ausgeklügelt, wie ich da meine Deadline einbaue, sprich, dass das Menü ohne zweiten Tastendruck nach - sagen wir z.B. 4 Sekunden wieder verschwindet und keine Aktion stattfindet

Ich nehme an ich kann weiter bei einer do .. while-Schlaufe bleiben, oder?Aber die muss ja dann irgendwo oben in der Loop rein. Ich hab gerade mal wieder weitergespielt - aber das Problem ist dann immer, dass der RotaryButtonStateOld ja wieder auf 0 zurückgesetzt werden muss, wenn die Zeit abläuft. Wenn ich das aber direkt nach der Schleife einbaue, wird meine Funktion ja nie ausgelöst.kopfkratz

@Muetos

Auch dir danke für den Ansatz. Darauf werd ich noch zurückgreifen, sobald ich das Menü zu einem richtigen ausbaue.

Mit timeout für Menu könnte etwa so aussehen:

RotaryButtonState = bouncer.read();
if (RotaryButtonState == 1) { // Taste gedrückt
  if (RotaryButtonStateOld == 1) { // 2. Tastendruck
    // Menuanzeige wieder löschen und abfragen, 
    // was aus dem Menu gewählt wurde und entsprechende Routine(n) aufrufen
    .....
    RotaryButtonStateOld = 0;
    .....
  } else { // 1. Tastendruck
    // jetzt Menuauswahl anzeigen
    .....
    RotaryButtonStateOld = 1; // nächster Tastendruck betrifft Menuauswahl
    showMenuTime = millis() + 4000; // Menu max. 4 Sekunden anzeigen
  }
}

if  (RotaryButtonStateOld == 1 && millis() > showMenuTime) {
  // 2. Tastendruck fehlt, deshalb timeout
  // Menu wieder ausblenden
  RotaryButtonStateOld = 0;
 }

Das ist eine einfache und halbwegs effiziente Lösung. Hab' bitte Verständnis, daß ich mich in Deine Anwendung nicht tiefer einarbeiten möchte.

Ein Nachteil dieser Lösung ist, daß "millis()" seit Einschalten des Arduinos hochzählt, nach ca. 50 Tagen Dauerbetrieb wird aber wieder bei 0 begonnen (=overflow) - also alle 50 Tage ist für 4 sek. diese Timeoutroutine unzuverlässig. :wink:

Ob Du eine extra "do ... while" - Schleife brauchst, kann ich nicht beurteilen - zunächst hat ja "loop()" dieselbe Funktion.

Ich danke dir - so funktioniert es jetzt.

Nee, is doch klar. Mein Code, mein Bier.
T'schuldigung wenn du dir jetzt so vorgekommen bist wie in dem Sprichwort mit dem kleinen Finger und der ganzen Hand.
Also, nochmals Danke.

Nichts zu danken - man hilft ja gerne.

Mit "nicht tiefer einarbeiten" habe ich nur vorsorglich argumentiert, da ich z.B. mit der bounce lib sowie Deiner Displayansteuerung keine Erfahrung habe. Da wird's dann immer schwieriger, ungetestet noch einen halbwegs sicheren Tipp zu geben.

Noch etwas eleganter wäre es übrigens, den timeout gleich mit "RotaryButtonStateOld" zu erledigen - das spart einige Bytes und der Code wird eine Idee schneller:

RotaryButtonState = bouncer.read();
if (RotaryButtonState == 1) { // Taste gedrückt
  if (RotaryButtonStateOld > 0) { // 2. Tastendruck innerhalb timeout ist erfolgt
    // Menuanzeige wieder löschen und abfragen, 
    // was aus dem Menu gewählt wurde und entsprechende Routine(n) aufrufen
    .....
    RotaryButtonStateOld = 0; // timeout ausschalten
    .....
  } else { // 1. Tastendruck
    // jetzt Menuauswahl anzeigen
    .....
    RotaryButtonStateOld = millis() + 4000; // nächster Tastendruck betrifft Menuauswahl oder timeout
  }
}

if  (RotaryButtonStateOld > 0 && millis() > RotaryButtonStateOld) {
  // 2. Tastendruck fehlt, deshalb timeout
  // Menu wieder ausblenden
  RotaryButtonStateOld = 0;
 }

Die Variablendefinition von RotaryButtonstateOld ggf. anpassen.

Weitere Optimierungen wären durchaus noch möglich (z.B. bei Knopfdruck direkt einen Interrupt auslösen anstatt zu pollen), aber im Sinne des Arduino-Konzepts wäre es dann wohl nicht mehr unbedingt.

Viel Erfolg!

Ah okay, dann werde ich das nachher nochmal umsetzen.

(z.B. bei Knopfdruck direkt einen Interrupt auslösen anstatt zu pollen)

Okay, jetzt wirds interessant. Ich denke das war mitunter mein Fehler im ersten Versuch - richtig?

Sprich, da beim Pollen der Zustand permanent immer wieder abgefragt wird, ist eine zweite Abfrage/ein Auslesen des Pins sinnlos, da das in der main loop selbst ja sowieso ständig erfolgt?? Richtig??

Ich hätte da dann aber doch noch eine Frage (darf aber gerne auch jemand anders antworten).

Ich würde gern mein scan() Unterprogramm durch mehrere "äußere Einflüsse" abbrechen lassen. Einmal natürlich durch die Variable sqState, sprich wenn an diesem Port plötzlich LOW anliegen sollte, andererseits aber auch durch ein Drehen am Rotary Encoder.
Aus diesem Grund habe ich die scan Funktion ja mit einer do .. while Schlaufe realisiert.

Dummerweise bekomme ich es aber nicht hin, das Ding auch durch eine Drehbewegung des Encoders abbrechen zu lassen. Ich verstehe jetzt bloss nicht, ob es an mir oder der Vorgehensweise liegt.

Meine Rotary-lib liest den Encoder ja mit Interrupt-Betrieb aus (wie ich soeben festgestellt habe - AHA-Erlebnis. Jetzt weiss ich auch warum der Code außerhalb der main Loop steht) und überführt das Ergebnis in die Varriable result, die bei einer Links-Umdrehung den Wert DIR_CW und bei einer Rechtsumdrehung den Wert Dir_CCW zugewiesen bekommt.

Ich dachte eigentlich ich könnte in meiner While Schleife so argumentieren while ((sqState == 1)&&(result!=DIR_CW)&&(result!=DIR_CCW)); Das bewirkt aber überhaupt nichts. Habe ich da irgendetwas falsch aufgefasst?

Ich habe es auch schon einmal mit einer weiteren Hilfvariable in der Schleife versucht, also in der Variante

do { int Test = 0;
     if (result==DIR_CW){Test=1;}
     if (result==DIR_CCW){Test=1;}
while ((sqState == 1)&&(Test==0))

Auch diese Variante tutet nicht. Hat jemand ne Idee?

Chimaera:
Sprich, da beim Pollen der Zustand permanent immer wieder abgefragt wird, ist eine zweite Abfrage/ein Auslesen des Pins sinnlos, da das in der main loop selbst ja sowieso ständig erfolgt?? Richtig??

"Jein"!
Du kannst den Buttonzustand an unterschiedlichen Stellen auch mehrfach abfragen, wenn es Sinn macht - was sollte auch dagegensprechen? Wichtig ist, den Code einerseits übersichtlich zu halten, andererseits aber auch nicht unnötigerweise aufzublähen. So wie die Lösung jetzt ist, ist sie völlig in Ordnung.

Was nun Deine weiteren Fragen betrifft:
Vielleicht machst Du den Fehler, daß Du zuviel codierst anstatt erstmal nur kleine Erweiterungen einzubauen und diese jeweils Schritt für Schritt ausgiebig zu testen. Du kannst dafür ja vorübergehend über serial Ergebnisse anzeigen lassen. Das ist beim Einstieg in die Programmierung mühsam und kostet viel Ausdauer, aber nur so wirst Du es lernen.

Viel Erfolg!

Du kannst dafür ja vorübergehend über serial Ergebnisse anzeigen lassen.

Jupp - step für step hat das dann auch in die richtige Richtung geführt. Ich habe jetzt erkannt, dass ich eine global definierte Variable später nicht nochmal im Code definiert werden sollte - das hat sich dann irgendwie gebissen. Zwar hat es keine Fehlermelduzng gegeben, aber auch zu keinem Ergebnis geführt.

Ich bin mit meiner Lösung jetzt auch fast zufrieden. Allerdings verstehe ich noch nicht warum der RotaryEncoder beim Auslesen in meinem Unterprogramm irgendwie laggt. Und zwar bei Drehung in die linke Richtung stärker als in die Rechte. Ich habe das ganze mit serialPrint auch verifiziert. Man benötigt jede Menge Impulse in die linke Richtung bevor das Programm wenigstens mal einen zählt. In die rechte Richtung ist es besser - da benötigt man vielleicht ne Virtelumdrehung des Encoders bevor mal was passiert - aber dann erscheinen alle einzelnen Impulse auf einen Schlag (ca ne halbe bis eine Sekunde nach Auslösung.

Okay, im Prinzip muss ich also mein Problem umdefinieren. Bei Drehung nach links, sieht es so aus als ob er 95% aller Impulse verschluckt, nach rechts verschluckt er vielleicht nur 30%, dafür laggt er aber extrem.

void scan ()  {
              do
                {
                         sqState = digitalRead(sqDetect);
                         RotaryStop=1;
                         siebensegment (kanaele_einer[x], kanaele_zehner[x]);
                         binausgabe(bincode[x]);    
                         x++;
                         delay (50);
                         if (x==40) {x=0;}
                         if (sqState==0) {RotaryStop=0;}
                         if ((result==DIR_CW)||(result==DIR_CCW)) {
                                                                   Test=1;
                                                                   RotaryStop=0;} //  Check mit {Serial.println(result == DIR_CW ? "L": "R");}                 
                       
                }
                while ((sqState == 1) &&(Test==0));
                Test=0;
  }
void loop() {
    bouncer.update ( );
    RotaryButtonState = bouncer.read();
    if (RotaryButtonState == 1) { // Taste gedrückt
        if (RotaryButtonStateOld == 1) { // 2. Tastendruck
           scan();
           RotaryButtonStateOld = 0;
                                        } 
        else { // 1. Tastendruck
                   
                   menu();
                   RotaryButtonStateOld = 1; // nächster Tastendruck betrifft Menuauswahl
                   showMenuTime = millis() + 4000; // Menu max. 4 Sekunden anzeigen
   }
}          
if  (RotaryButtonStateOld == 1 && millis() > showMenuTime) {
  // 2. Tastendruck fehlt, deshalb timeout
  // Menu wieder ausblenden
  RotaryButtonStateOld = 0;
  siebensegment (kanaele_einer[x], kanaele_zehner[x]);
 }     }               
                               
                              


ISR(PCINT2_vect) {
  result = r.process();
  if (result)   {
              if ((result == DIR_CW)&&(RotaryStop==0)) {
                   x++;};
                         if (x==40) {
                             x=0;};
    
              if ((result == DIR_CCW)&&(RotaryStop==0)) {
                   x--;};
                         if (x==-1) {
                             x=39;};
    
       
       // Anzeigenausgabe   
      siebensegment (kanaele_einer[x], kanaele_zehner[x]);        
      //Binausgabe  
      byte aktuellKanal= bincode[x];
      binausgabe (aktuellKanal);                       
               }
  
}

Ist das irgendein Interrupt-Problem? Ich habe auch schon einmal versucht den Encoder in dem Unterprogramm erneut auszulesen und das Ergebnis erneut in die Variable result zu überführen. Aber das hat auch nicht funktioniert.

Irgendwelche Anregungen? Ich kann zur Not auch so mit dem Problem irgendwie leben. Aber schön ist es nicht.

Lagging und Aussetzer lassen jedenfalls stark vermuten, daß der Controller da noch anderweitig beschäftigt ist. So wird beispielsweise ein neuer Interrupt sang- und klanglos übergangen, wenn der vorherige Interrupt noch nicht fertig abgearbeitet ist. Deshalb Interruptroutinen so kurz und effizient wie möglich halten!

Was mir auffällt:

Du rufst die Funktion "siebensegment ( .... );" sowohl in der ISR als auch während des normalen Programmablaufs auf. Das ist "harakiri"! ]:slight_smile:
"binausgabe" hat in der ISR vermutlich auch nichts verloren.

In einer ISR wirklich nur das Nötigste: keine Funktionsaufrufe, die auch im Hauptprogramm noch erfolgen können und auch keine Ausgaben über serial bzw. andere Funktionen aus libs, die selbst Interrupts verwenden! Lieber notfalls eine Variable in der ISR setzen und im Hauptprogramm dann abfragen und abarbeiten lassen.

Sicherheitshalber global definierte Variablen, die sowohl in der ISR als auch anderweitig verwendet werden, als "volatile" deklarieren, also z.B. "volatile int zaehler".