Tastenabfrage mehrerer Tasten inkl. Entprellung und Sonderfunktionalitäten

Hallo,

ich bin in der Vergangenheit immer wieder auf das Problem gestoßen, dass ich Tasteneingaben viel zu umständlich an den restlich Sketch übergeben habe.

Da ich vor kurzem mal ausnahmsweise kein Projekt am laufen hatte, nahm ich mich der Thematik nochmals an und habe bewusst versucht das Ganze diesmal knapp und trotzdem gut lesbar umzusetzen.

Das Ergebnis möchte ich Euch an dieser Stelle präsentieren.

Folgende Funktionalitäten bietet der Codeblock:

  1. Entprellung mittels der Bounce2-Library
  2. Erfassung einzelner oder mehrerer Tastendrucke
  3. Erfassung des gedrückt haltens einer oder mehrerer Tasten
  4. Erfassung des gedrückt haltens einer oder mehrerer Tasten, ohne dass andere Tasten gedrückt gehalten werden
  5. Erfassung des Loslassens von Tasten
  6. Erfassung des aktuellen Zustands von Tasten

Ich habe extra viel kommentiert, damit Anfänger was davon haben und der Codeblock jederzeit sofort einsatzbereit Verwendung finden kann. :wink:

Gruß Chris

/*
 *
 * Tastenauswertungsalgorithmus
 *
 * Tastenzustände können folgendermaßen abgefragt werden:
 *
 * Beispiele:
 *
 * if (bState[7] == HOLD)                                         // Wird die Taste 7 momentan gedrückt gehalten?
 * if (bCmd[3] == PUSH)                                           // Wurde die Taste 3 gedrückt?
 * if (bCmd[1] == S_HOLD)                                         // Wurde die Taste 1 pushDur ms lang gedrückt gehalten?
 * if (bCmd[2] == M_HOLD && bCmd[4] == M_HOLD)                    // Wurden die Tasten 2 und 4 pushDur ms lang gedrückt gehalten, unabhängig davon, ob weiteren Tasten gedrückt gehalten werden?
 * if (bCmd[5] == M_HOLD && bCmd[6] == M_HOLD && bCount == 2)     // Wurden die Tasten 5 und 6 pushDur ms lang gedrückt gehalten, während keine weiteren Tasten gedrückt gehalten werden?
 *                                                                // bCount sollte der Summe der abzufragenden Tasten entsprechen.
 *
 * Werden mehr als 255 Tasten benötigt, muss der Wertebereich von butPin und bCount jeweils in Integer geändert werden.
 * 
*/

#include <Bounce2.h>

unsigned long now;                  // Aktueller Zeitpunkt

// _Entprellung

#define INPUTMODE INPUT_PULLUP      // INPUT oder INPUT_PULLUP
const byte butPin[] = {             // Pin-Nummern der angeschlossenen Tasten
  2, 3, 4, 5, 6, 7
};
#define NUM_BUTTONS sizeof(butPin)  // Die Anzahl der Tasten durch die Anzahl der Bytes des Arrays butPin ermitteln (wird automatisch definiert)
Bounce debouncer[NUM_BUTTONS];      // Mehrere Bounce-Instanzen erstellen
bool bState[NUM_BUTTONS];           // Speichert den Zustand des Pins
enum {
  UNHOLD, HOLD
};
byte bCmd[NUM_BUTTONS];             // Speichert Pinbefehle, welche dem Sketch übergeben werden
enum {
  WAIT, PUSH, RELEASE, S_HOLD, M_HOLD
};
byte bCount;                        // Anzahl der gleichzeitig gedrückt gehaltenen Tasten

void setup()
{
  for (int i = 0; i < NUM_BUTTONS; i++)
  {
    pinMode(butPin[i], INPUTMODE);
    debouncer[i].attach(butPin[i]);
    debouncer[i].interval(10);
  }
}



void loop()
{
  now = millis(); // Aktueller Zeitpunkt
  debounce();
  // <- Bitte an dieser Stelle die restlichen Codebestandteile einbauen
}

void debounce()
{
  static unsigned long pushTime;    // Definiert, wann zuletzt eine Taste gedrückt gehalten wurde
  const  int pushDur = 700;         // Definiert die Haltedauer, im Anschluss derer eine Aktion ausgelöst werden soll
  static bool action = false;       // Definiert, ob eine Aktion bereits ausgeführt wurde
  now = millis();
  for (int i = 0; i < NUM_BUTTONS; i++)
  {
    switch (bCmd[i])
    {
      case WAIT:
        if (now - pushTime >= pushDur && action == false &&  bState[i] == HOLD)
        {
          if (bCount == 1) bCmd[i] = S_HOLD;   // Aktion fürs gedrückt halten einer Taste
          if (bCount >= 2) bCmd[i] = M_HOLD;   // Aktion fürs gedrückt halten mehrerer Tasten
        }
        break;
      case PUSH:
        if (!bCount)       // Wurde bereits noch keine Tasten weitere Taste gedrückt?
        {
          pushTime = now;  // Druck der ersten Taste
          action = false;  // Actionstatus zurücksetzen
        }
        bCount++;          // Anzahl der gleichzeitig gedrückt gehaltenen Tasten inkrementieren
        bCmd[i] = WAIT;
        break;
      case RELEASE:
        if (bCount) bCount--;         // Anzahl der gleichzeitig gedrückt gehaltenen Tasten inkrementieren, falls noch welche gedrückt gehalten werden
        if (!bCount) action = false;  // Gibt das Auslösen von Aktionen wieder frei, sobald keine Tasten mehr gedrückt gehalten werden
        bCmd[i] = M_HOLD; // An dieser Stelle wurde bewusst auf break; verzichtet
      case S_HOLD:
        bCmd[i] = M_HOLD; // An dieser Stelle wurde bewusst auf break; verzichtet
      case M_HOLD:
        action = true;  // Verhindert das mehrmalige Auslösen bereits ausgelöster Aktionen
        bCmd[i] = WAIT;
        break;
    }
    debouncer[i].update();   // Status der Tasten prüfen
    if (debouncer[i].fell())
    {
      bCmd[i] = PUSH;
      bState[i] = HOLD;
    }
    else if (debouncer[i].rose())
    {
      bCmd[i] = RELEASE;
      bState[i] = UNHOLD;
    }
  }
}

Nicht übel, würde es gerne in ein Projekt übernehmen, wo ich das Hold noch nicht benutze aber gut gebrauchen könnte.

Allerdings hängen da alle Taster an einem MCP23017, mit der entsprechenden Adafruit Lib. Wie kann ich dies an die debounce2 Lib übergeben?

Hi,

ich hab selber noch nie ein MCP23017 verbaut und weiss leider daher auch nicht wie dieser mit dem Arduino kommuniziert.

Theoretisch müsste es reichen, wenn Du die folgenden Zeilen meines Codes an Deine Bedürfnisse anpasst, da der komplette Code sich auf diese Auswertung stützt:

debouncer[i].update();   // Status der Tasten prüfen
    if (debouncer[i].fell())
    {
      bCmd[i] = PUSH;
      bState[i] = HOLD;
    }
    else if (debouncer[i].rose())
    {
      bCmd[i] = RELEASE;
      bState[i] = UNHOLD;
    }

Du müsstest dann i praktisch auf die Pins deines MCP23017 "umbiegen".

Gruß Chris

Chris72622:
Du müsstest dann i praktisch auf die Pins deines MCP23017 "umbiegen".

Genau das ist ja die Gretchenfrage. i ist die Pinnummer am Arduino. Beim mcp hab ich dan sowas:

mcp.digitalRead(1)

wie verklicker ich das der debouncerlib ?

ElEspanol:
wie verklicker ich das der debouncerlib ?

In Bounce2.cpp steht dreimal digitalRead(pin). Daraus müßte mcp.digitalRead(pin) werden.

Ungetestete Ideen:

  • #define digitalRead(pin) mcp.digitalRead(pin) sicherlich mit netten Fußangeln :slight_smile:
  • Bounce_mcp.cpp erstellen, also eigene Bibliothek.
  • Auf bounce2 verzichten und "Hold" selbst implementieren. M. E. braucht man da keine Bibliothek.

Bevor ich die Bibliothek ändern würde, würde ich es ehr dann auch ohne die Entprellung über Bounce2 lösen. Wie entprellst Du denn bisher, ElEspanol?

Gruß Chris

Bisher frag ich die Tasten nur alle 50 ms ab. Ohne delay natürlich. Und der loop braucht sowieso zwischen 20 und 150 ms.

ElEspanol:
..der loop braucht sowieso zwischen 20 und 150 ms.

Ui.

Hallo zusammen,
ein wenig spät meine Antwort, aber vielleicht nutzbringend: Vor einiger Zeit hatte ich so ein ähnliches Problem mit umständlichen Tasten-Abfragen - und habe mir dazu eine eigene "Class" geschrieben. Letzendlich beinhaltet ein entsprechendes Programm von mir dann z.B. nur noch solche Anweisungen:

Buttons Button4(4); // Objekt - Taste an Pin 4 (LOW-Aktiv)
Buttons Button5(5); // Objekt - an Pin 5 (LOW-Aktiv)
Buttons Button6(6); // Objekt - Pin 6 (LOW-Aktiv)
Buttons Button7(7); // Objekt - 7 (LOW-Aktiv)

void setup()
{
  pinMode(  9, OUTPUT ); // Status-LED an Pin 9
  pinMode( 10, OUTPUT ); // an Pin 10
  pinMode( 11, OUTPUT ); // Pin 11
  pinMode( 12, OUTPUT ); // 12  
  pinMode( 13, OUTPUT ); // "HOLD"-LED für Taste 4 
}

void loop()
{  
  // Toggled bei "Taste loslassen"
  if( Button4.onKeyUp() )
    digitalWrite( 9, !digitalRead( 9) ); 
  if( Button5.onKeyUp() ) 
    digitalWrite( 10, !digitalRead(10) ); 
  
  // Toggled bei "Taste drücken"
  if( Button6.onKeyDown() ) 
    digitalWrite( 11, !digitalRead(11) ); 
  if( Button7.onKeyDown() ) 
    digitalWrite( 12, !digitalRead(12) ); 
    
  // "HOLD"-Status für Taste 4  
  digitalWrite( 13, Button4.onKeyPress() );                     
}

"Button4...Button7" sind hierbei "per default" mit 10mS Entprellzeit eingestellt. Ich kann aber (wenn es notwendig sein sollte) einen 2. Parameter in Millisekunden an das Objekt übergeben. Der folgende Code ist die eigentliche Class, die in diesem Fall jedoch VOR das "void setup()" geschrieben werden muss. Ich überlege, ob ich daraus vielleicht noch eine eigene LIB machen soll. Kommt Zeit, kommt Idee.

class Buttons
{
  public:
    Buttons( byte pin );
    Buttons( byte pin, word dbTime );
    boolean onKeyDown();
    boolean onKeyPress();
    boolean onKeyUp();
    
  protected:
    byte    _pin;
    boolean _down;
    boolean _up;
    boolean _press;
    boolean _newState;
    boolean _oldState;
    unsigned long _dbTime;
    void init( byte, word );
};

Buttons::Buttons( byte pin )
{ init( pin, 10 ); }

Buttons::Buttons( byte pin, word dbTime )
{ init( pin, dbTime ); }

void Buttons::init( byte pin, word dbTime )
{
  _down     = false;
  _press    = false;
  _up       = false;
  _newState = false;
  _oldState = false;
  _pin      = pin;
  _dbTime   = dbTime *1000;
  pinMode( _pin, INPUT_PULLUP );
}

boolean Buttons::onKeyDown()
{
  boolean RESULT = false;  
  if( !digitalRead(_pin) )
    {
      delayMicroseconds( _dbTime );
      _newState = !digitalRead( _pin );
      if( _newState != _down ) 
        {
          _down = _newState;
          if( _newState ) 
            RESULT = true;
        }
    }
  return RESULT;
}

boolean Buttons::onKeyPress()
{ return _down | _up; }

boolean Buttons::onKeyUp()
{
  boolean RESULT = false;  
  if( !digitalRead(_pin) )
    {
      delayMicroseconds( _dbTime );
      _newState = !digitalRead( _pin );
      if( _newState != _up ) 
        {
          _up = _newState;
          if( !_newState ) 
            RESULT = true;
        }
    }
  return RESULT;
}

Im Grunde genommen kann man damit jeden Button direkt mit Namen ansprechen ohne sich um irgendwelches Drumherum kümmern zu müssen: "onKeyDown()" für "gedrückt", "onKeyUp()" für "losgelassen" und "onKeyPress()" für "gedrückt haltend". Ich denke das müsste eigentlich reichen.

Vielleicht kannst du obiges gebrauchen, oder mir zumindest sagen, was da noch drin fehlt oder vielleicht sogar falsch ist.
Rudi

Hi Rudi,

was Deinem Code, wenn ich ihn richtig gelesen haben sollte, noch fehlt ist folgende Funktionalität:

"Mach was, wenn ausschließlich die Tasten A und B für X ms gedrückt gehalten wurden."

Gruß Chris

Hi Chris,
interessante Fragestellung. Ich gehe mal davon aus, dass du folgendes meinst:
"Wenn A und B gemeinsam mehr als x mS gedrückt werden, dann mache y" ... ??
Müsste ich mal schauen, wie ich das dann noch einbaue.
Rudi

Genau.

In meinem Code werden sowohl "EinzeltastengedrückthalteAktionen", als auch "MultitastengedrückthalteAktionen" berücksichtigt.

Gruß Chris

ahaaaa, "ganz einfach" könntest du das ja machen, indem du zwei Buttons mit AND verknüpfst:

if( Button3.onKeyPressed() && Button4.onKeyPressed() ) 
  { ... }

Oder brauchst du die tatsächlichen mS-Werte davon?

Nein. Spannend wirds aber, sobald abgefragt werden muss, ob noch andere Tasten (evtl. versehentlich gedrückt gehalten werden.

Ich löse dies durch "mitzählen" der gleichzeitig gedrückt gehaltenen Tasten.

if (bCmd[5] == M_HOLD && bCmd[6] == M_HOLD && bCount == 2)

Kritisch (und sehr spannend) wird es zudem, wenn das gedrückt halten von zwei bestimmten Tasten abgefragt werden soll, aber für eine kurze Zeitdauer drei Tasten gedrückt gehalten wurden.

Das Konstruckt, dass dabei im Hintergrund zu erstellen ist, um auch solche "Sondersituationen" zu behandeln, kann einen wirklich fast wahnsinnig machen.

Diese Thematik hat mich vor Monaten bereits mal für Wochen aufgehalten. Damals hatte ich es vertagt.

Gruß Chris

Oh Weia :smiley:

Spannend wirds aber, sobald abgefragt werden muss, ob noch andere Tasten (evtl. versehentlich gedrückt gehalten werden.

Vielleicht EINE Lösungsmöglichkeit:

if( b1.onKeyPressed() && b2.onKeyPressed() && !b3.onKeyPressed() && !b4.onKeyPressed() ) {...}

Wäre für den Fall, dass nur b1 && b2 gedrückt sind (bzw. werden dürfen), b3 aber nicht und b4 ebenso nicht...

*Nachsatz...
Habe ich gerade noch entdeckt: In meiner Class sind noch zwei Überbleibsel aus vorherigen Versuchen... _press und _oldStatus sollten natürlich überall raus ... :wink:

..und schon sind wir wieder an einem Punkt, wo wir für etwas eigentlich ganz Einfaches aus meiner Sicht viel zu viel Text schreiben müssen.

Viel Spass noch beim Forschen! :slight_smile:

Gruß Chris

Ebenso viel Spaß noch beim Basteln :smiley:

(Würde mich aber dennoch interessieren, welche Anwendung auf so abentuerliche Eingaben reagieren muß. Was ich kenne ist, dass die meisten eh nur im Geier-System eingeben: Suche, kreisen, zuschlagen... :wink:
Und mit onKeyDown() bzw. onKeyUp() fängst du ja solche Fehleingaben direkt ab und reagierst gar nicht mehr drauf, als wenn du dir mit onKeyPress() die Finger brichst. )

Bei Mehrfachtastenbelegungen brauche ich das.

Gruß Chris

@RudiDL5: Die Idee mit der Class gefällt mir sehr gut! Damit sieht das übrige Programm sehr aufgeräumt aus und für mich reichen die Möglichkeiten bei weitem aus. Vielen Dank dafür! [Karma add] :slight_smile:

@slartibartfast
Hey WOW, recht herzlichen Dank, war doch nicht nötig :smiley:

Ich habe mit meiner Idee gestern noch ein wenig experimentiert: Ebenso übersichtlich bleibt es wenn mehrere Tasten abgefragt werden sollen. Dazu könnte man aus einer Button-Definition direkt ein ganzes Object-Array machen und dieses dann mit einer FOR-Schleife abarbeiten.

Im folgenden Beispiel habe ich 4 Tasten an Pin4...Pin7 angeschlossen und 4 LEDs an Pin9...Pin12. Der Code in Verbindung mit meiner Class bleibt m.E. genau so übersichtlich.

Buttons allButtons[] = { Buttons(4), Buttons(5), Buttons(6), Buttons(7) };

void setup()
{
  for( byte a = 9; a < 13; a++ )
     pinMode( a, OUTPUT ); 
}

void loop()
{   
  for( byte i = 0; i < 4; i++ )
     if( allButtons[i].onKeyDown() )
       digitalWrite( i +9, !digitalRead( i +9 ) );           
}

Jede der 4 LEDs lässt sich beliebig ein- und ausschalten, so wie es sich viele eigentlich immer wieder wünschen. Theoretisch könnte man später auch noch für die Tasten-Nummern #define-Namen vergeben, wodurch man die einzelnen Elemente in so einem Object-Array gezielt ansprechen kann. So im Sinne von:

#define STARTKEY 0
#define STOPPKEY 3
.
.
.
if( allButtons[STARTKEY].onKeyDown() ) {...}
if( allButtons[STOPPKEY].onKeyDown() ) {...}

Gruß, Rudi