Konkrete Fragen zur einfacheren Tastenabfrage

Hallo,

mein unten angehängter Sketch nutzt die Bounce2-Bibliothek (Download).

Für den Fall das jmd. diesen Versuch nachbauen möchte, habe ich ein Foto angehängt.

Dieser Versuch soll es ermöglichen von jeder erdenklichen Stelle im Sketch aus so einfach wie möglich das drücken einer Taste erfassen zu können. Desweiteren soll das Auslösen unterbunden werden, wenn bereits eine Taste gedrückt gehalten wird. Später sollen noch Abfragen wie z.B. das gedrückt halten von mehreren Tasten über eine gewisse Zeitspanne dazukommen.

In diesem Fall wird jedoch lediglich von der Funktion void einbindung() auf die Zustände der beiden Tasten zugegriffen.

Ich habe nun zwei Fragen:

  1. Wie kann ich umgehen, dass t1 bis t5 globale Variablen sein müssen?
  2. Wie kann ich umgehen, dass ich mit fortschreitender Tastenanzahl verrückt werde?

Gruß Chris

#include <Bounce2.h>

#define BUTTON_1 2
#define BUTTON_2 3

Bounce debouncer_1 = Bounce(); 
Bounce debouncer_2 = Bounce();

boolean t1; // Taste 1 wurde gedrückt
boolean t2; // Taste 2 wurde gedrückt
boolean t3; // Taste 3 wurde gedrückt
boolean t4; // Taste 4 wurde gedrückt
boolean t5; // Taste 5 wurde gedrückt

void setup()
{
  pinMode(BUTTON_1,INPUT_PULLUP);
  pinMode(BUTTON_2,INPUT_PULLUP);
  debouncer_1.attach(BUTTON_1);
  debouncer_2.attach(BUTTON_2);
  debouncer_1.interval(10);
  debouncer_2.interval(10);
  Serial.begin(9600); 
}

void loop()
{
  buttonCheck();
  einbindung();
}

void buttonCheck()
{
  t1 = 0;  // Taste 1 wurde gedrückt
  t2 = 0;  // Taste 2 wurde gedrückt
  t3 = 0;  // Taste 3 wurde gedrückt
  t4 = 0;  // Taste 4 wurde gedrückt
  t5 = 0;  // Taste 5 wurde gedrückt

  static unsigned long tTime1;  // Hält den Zeitpunkt fest, wann zum ersten Mal ein einzelner Button gedrückt gehalten wird
  static unsigned long tTime2;  // Hält den Zeitpunkt fest, wann zum ersten Mal zwei Buttons gedrückt gehalten werden
  static unsigned long tTime3;
  static unsigned long tTime4;
  static unsigned long tTime5;

  debouncer_1.update();
  debouncer_2.update();

  if(!debouncer_1.read() + !debouncer_2.read() == 1)  // Wenn nur eine Taste gedrückt gehalten wird
  {
    if(debouncer_1.fell())
    {
      Serial.println("Taste 1 wurde einmalig gedrueckt");
      Serial.println("tTime1 = millis()");
      tTime1 = millis();
      t1 = 1;
    }
    if(debouncer_2.fell())
    {
      Serial.println("Taste 2 wurde einmalig gedrueckt");
      Serial.println("tTime1 = millis()");
      tTime1 = millis();
      t2 = 1;
    }
  }
  else if(!debouncer_1.read() + !debouncer_2.read() == 2)  // Wenn zwei Tasten gedrückt gehalten werden
  {
    if(debouncer_1.fell())
    {
      Serial.println("Taste 1 wurde einmalig gedrueckt, waehrend Taste 2 gedrueckt gehalten wird");
      Serial.println("tTime2 = millis()");
      tTime2 = millis();
    }
    if(debouncer_2.fell())
    {
      Serial.println("Taste 2 wurde einmalig gedrueckt, waehrend Taste 1 gedrueckt gehalten wird");
      Serial.println("tTime2 = millis()");
      tTime2 = millis();
    }
  }
}

void einbindung()
{
  if(t1) Serial.println("Taste 1 hat etwas ausgeloest");
  if(t2) Serial.println("Taste 1 hat etwas ausgeloest");
}

IMG_4389.JPG

Chris72622:

  1. Wie kann ich umgehen, dass t1 bis t5 globale Variablen sein müssen?

Warum?

Wenn Du "von jeder erdenklichen Stelle im Sketch aus so einfach wie möglich das drücken einer Taste erfassen" möchtest, wirst Du kaum eine andere Möglichkeit haben, als die Info in einer globalen Variablen zur Verfügung zu stellen. Ob das jetzt allerdings 5 einzelne Variablen sein müssen oder nicht besser ein Array mit 5 Elementen verwendet wird, steht natürlich auf einem anderen Blatt.

Chris72622:
2. Wie kann ich umgehen, dass ich mit fortschreitender Tastenanzahl verrückt werde?

Auf so einen Mist wie Deine Debouncer-Library verzichten.
Anfangen mit strukturierter Programmierung.
Dem Programm eine einfache Programm-Logik verpassen.
Und ggf. sinnvolle Datenstrukturen deklarieren und verwenden.

Hast Du hier im Forum in einem meiner Beiträge noch nie etwas über das von mir bevorzugte EVA-Prinzip (Eingabe-Verarbeitung-Ausgabe) bei der Programmierung gelesen?

Und auch noch nie ein Posting mit einem Beispiel zum einfachen Abfragen vieler Taster mit Beispielprogramm zum Verarbeiten von Tastendrücken?

Darf ich Dir sonst mal das posten:

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 5              // maximale Prellzeit in Millisekunden
byte buttonPins[]={2, 3, 4, 5, 6};// Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins) // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];  // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS]; // Speichert Flankenwechsel an den Pins
enum{UNCHANGED,BUTTONUP,BUTTONDOWN};

void eingabe(){
// Tasterstatus und Flankenwechsel an den Tastern auswerten  
  static unsigned long lastButtonTime; // Zeitstempel, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange)); // Alle alten Flankenwechsel verwerfen
  if (millis()-lastButtonTime<BOUNCETIME) return;  // innerhalb der Prellzeit die Funktion verlassen
  lastButtonTime=millis(); // Zeitstempel der letzten Buttonabfrage aktualisieren
  for (int i=0;i<NUMBUTTONS;i++) 
  {
    byte curState=digitalRead(buttonPins[i]);        // Aktueller Buttonstatus
    if (INPUTMODE==INPUT_PULLUP) curState=!curState; // Vertauschte Logik bei INPPUT_PULLUP
    if (curState!=buttonState[i])                    // Flankenwechsel erkannt
    {
      if (curState==HIGH) buttonChange[i]=BUTTONDOWN;
      else buttonChange[i]=BUTTONUP;
    }
    buttonState[i]=curState;  // den jetzigen Buttonzustand speichern
  }
}


void ausgabe(){
// Bei Tastendruck Ausgabe des Pins auf Serial
// Button gedrückt: Minus-Pinnummer
// Button losgelassen: Pinnummer
  byte action;
  for (int i=0;i<NUMBUTTONS;i++)
  {
    switch (buttonChange[i])  
    {
      case BUTTONUP: Serial.println(buttonPins[i]);break;
      case BUTTONDOWN: Serial.println(-buttonPins[i]);break;
    }
  }
}


void setup() {
  Serial.begin(9600);
  for (int i=0;i<NUMBUTTONS;i++) pinMode(buttonPins[i],INPUTMODE);
}

void loop() {
  eingabe();
//  verarbeitung(); // Schreibe eine Funktion, wenn Du es brauchst!
  ausgabe();
}

Ist ein Demo-Sketch nur mit Eingabe (über 5 Buttons) und Ausgabe (über Serial).
Keine Verarbeitung dabei. Könnte man noch dranstricken.

Was meinst Du dazu?

Die loop-Funktion besteht einfach aus drei selbstgeschriebenen Funktionen für Eingabe, Verarbeitung und Ausgabe?

Da brauchst Du auch nicht "mit fortschreitender Tastenanzahl verrückt werden", sondern mit zunehmender Tastenanzahl ändert sich

  • überhaupt nichts an der loop-Funktion
  • und auch überhaupt nichts an der eingabe() Funktion
    In der Demo braucht eigentlich nur das Array "buttonPins[]" erweitert werden, wenn Du mehr Buttons haben möchtest, die abgefragt werden sollen.

Wow. Da bin ich (mal wieder) einfach platt. :roll_eyes:

Du hattest mir bereits bei so einigen seriellen Problemstellungen geholfen, aber das Tasterchaosproblem hatte ich bislang immer im Hintergrund.

Nun hab ich da aber keine Lust mehr drauf und werde versuchen mir Deinen geposteten Code genauestens zu Gemüte zu führen..

Als wie kompliziert erachtest Du im Zusammenhang mit dem von Dir geposteten Code die Implementierung von so Geschichten wie "wenn Taste 1, 4 und 13 über x ms gedrückt gehalten werden.."?

Dank Dir vielmals.

Gruß Chris

Chris72622:
Als wie kompliziert erachtest Du im Zusammenhang mit dem von Dir geposteten Code die Implementierung von so Geschichten wie "wenn Taste 1, 4 und 13 über x ms gedrückt gehalten werden..".

Wenn Du Dir bei jedem BUTTONDOWN Tastendruck merkst, bei welchem Stand des millis() Zählers die Taste gedrückt wird, kannst Du auch jederzeit ermitteln, wie lange eine Taste gedrückt ist.

Sagen wir mal Du verwaltest die BUTTONDOWN-Zeiten in einem Array (muss natürlich Code für geschrieben werden):

unsigned long buttonDownTime[NUMBUTTONS]; // Speichert die Zeit, wenn ein Button gedrückt wird

Dann kannst Du jederzeit bei der Verarbeitung die gesuchte Information abfragen. Ich gehe mal aus, Du meinst den Button-Index in einem Array mit mindestens 14 Buttons und nicht direkt die Button-Pin-Nummer:

long now=millis();
if (buttonState[1] && buttonState[4] && buttonState[13]) // alle drei Tasten sind unten
{
  // prüfen ob sie alle mindestens 100ms unten sind
  if (now-buttonDownTime[1]>=100 && now-buttonDownTime[4]>=100 && now-buttonDownTime[13]>=100)
  {
    // tu was
  }
}

Klingt für mich einfach.

Ja, ich denke den Rest bekomme ich so tatsächlich hin.

Merci.

Gruß Chris

Schwierig wird es halt dadurch, dass beim gleichzeitig gedrückt halten von mehr als zwei Tasten die einzelnen gedrückt gehaltenen Tasten dann nicht jeweils ihre "Einzelaktion" auslösen, während man dabei ist noch mehr Tasten gedrückt zu halten.

In meinem Codefetzen vom Anfang kann man dies herauslesen (was bei zweit Tasten noch interessant und spaßig ist wird bei drei oder mehr Tasten zu einem massiven Problem).

Dieses Problem besteht in Deinem zuletzt geposteten Codefetzen ja ebenfalls und genau das ist es ja, was es aus meiner Sicht so wahnsinnig kompliziert werden lässt.

Gruß Chris

Edit: Momentaner Stand (nochmals geändert um 14:40 Uhr):

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 10             // maximale Prellzeit in Millisekunden
byte buttonPins[] = {
  11, 12, 2, 3, 4, 5, 6};// Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins) // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];  // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS]; // Speichert Flankenwechsel an den Pins
enum{
  UNCHANGED,BUTTONUP,BUTTONDOWN};
unsigned long buttonDownTime[NUMBUTTONS]; // Speichert die Zeit, wenn ein Button gedrückt wird

void eingabe()  // Tasterstatus und Flankenwechsel an den Tastern auswerten  
{
  static unsigned long lastButtonTime;                 // Zeitstempel der erfasst hat, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange));         // Alle alten Flankenwechsel verwerfen (buttonChange wird komplett mit Nullen überschrieben)
  if (millis() - lastButtonTime < BOUNCETIME) return;  // Innerhalb der Prellzeit die Funktion ggfs. verlassen

    lastButtonTime = millis();                           // Zeitstempel der letzten Buttonabfrage aktualisieren
  for (int i = 0; i < NUMBUTTONS; i++) 
  {
    byte currentState = digitalRead(buttonPins[i]);          // Aktueller Buttonstatus
    if (INPUTMODE == INPUT_PULLUP) currentState = !currentState;   // Vertauschte Logik bei INPUT_PULLUP
    if (currentState != buttonState[i])                      // Flankenwechsel erkannt
    {
      if (currentState == HIGH)
      {
        buttonChange[i]=BUTTONDOWN;
        buttonDownTime[i] = millis();
      }
      else buttonChange[i] = BUTTONUP;
    }
    buttonState[i] = currentState;                           // den jetzigen Buttonzustand speichern
  }
}

void verarbeitung()
{
  if(buttonState[2] && buttonState[3]) // alle zwei Tasten sind unten
  {
    if(millis() - buttonDownTime[2] >= 1000 && millis() - buttonDownTime[3] >= 1000)  // prüfen, ob sie beide mindestens 1000ms unten sind
    {
      Serial.println("Zupp!");
    }
  }
}


void ausgabe(){
  // Bei Tastendruck Ausgabe des Pins auf Serial
  // Button gedrückt: Minus-Pinnummer
  // Button losgelassen: Pinnummer
  for(int i = 0; i < NUMBUTTONS; i++)
  {
    switch (buttonChange[i])  
    {
    case BUTTONDOWN: 
      Serial.println(-buttonPins[i]);
      Serial.print("buttonDownTime");
      Serial.println(buttonPins[i]);
      Serial.print(" = ");
      Serial.println(buttonDownTime[i]);
      break;  
    case BUTTONUP: 
      Serial.println(buttonPins[i]);
      break;
    }
  }
}


void setup() {
  Serial.begin(9600);
  for (int i = 0; i < NUMBUTTONS; i++) pinMode(buttonPins[i], INPUTMODE);
}

void loop() {
  eingabe();
  verarbeitung();
  ausgabe();
}

Chris72622:
Schwierig wird es halt dadurch, dass beim gleichzeitig gedrückt halten von mehr als zwei Tasten die einzelnen gedrückt gehaltenen Tasten dann nicht jeweils ihre "Einzelaktion" auslösen, während man dabei ist noch mehr Tasten gedrückt zu halten.

Wieso denn das nicht?

Natürlich kannst Du Aktionen beim Drücken einzelner Tasten auslösen wie auch Aktionen beim gleichzeitigen Drücken mehrer Tasten für eine bestimmte Zeit.

Also:
Taste-1 ==> Einzelaktion-1
Taste-4 ==> Einzelaktion-4
Taste-13 ==> Einzelaktion-13
Alle Tasten länger als 100ms gedrückt ==> Mehrfach-Tastenaktion ausführen

Aber Hellsehen kann ein Programm nie.
Also wenn es eine "Einzelaktion-1 für Taste-1" gibt, dann kannst Du kaum festlegen, dass diese Einzelaktion-1 nicht ausgeführt wird, wenn später danach noch Taste-4 und Taste-13 gedrückt werden und ja noch eine Mehrfachaktion draus werden könnte. Du würdest dann immer die Einzelaktionen bekommen und zusätzlich die Mehrfachaktion.

Das muss schon logisch und stimmig sein. Wenn es Einzelaktionen zu einzelnen Tasten gibt, dann werden diese auch gefeuert, wenn später noch ein Mehrfachtastendruck draus werden könnte.

Was aber möglich wäre:

  • Für jede Aktion sind drei Tastendrücke notwendig

Ebenfalls möglich wäre es, Tasten mit mehreren Funktionen zu belegen, z.B.

  • kurzer Tastendruck unter 1s gedrückt ==> Die eine Funktion
  • langer Tastendruck über 1s gedrückt ==> Die andere Funktion
    In dem Fall würde die Funktion aber erst beim Loslassen des Tasters (oder nach 1s) feuern können, da erst dann feststeht, wie lange gedrückt wurde.

Das Grundgerüst aus Eingabe-Verarbeitung-Ausgabe ist so flexibel, dass damit jedes in sich logische Bedienkonzept realisiert werden kann. Nur "Hellsehen" bei Mehrdeutigkeiten im Konzept sind grundsätzlich ausgeschlossen.

Ich fasse vielleicht noch einmal kurz zusammen, was erreicht werden soll:

  1. Jede Taste soll bei Tastendruck eine bestimmte Aktion auslösen.
  2. Jede Taste soll durch gedrückt halten eine alternative Aktion auslösen.
  3. Durch gedrückt halten mehrerer Tasten sollen ebenfalls Aktionen ausgelöst werden.

Wenn ich z.B. eine Aktion erzeugen möchte, zu der zwei Tasten gedrückt gehalten werden müssen, möchte ich nicht, das die Aktion der als zweites gedrückten Taste ausgeführt wird. Da es ja kein "gleichzeitig" gibt, wird immer eine Taste vor der Andeen gedrückt.

Im Detail:

Ich drücke Taste 1 -> Ergebnis: Aktion Taste 1 wird ausgeführt
Ich drücke kurz danach (50ms) die Taste 2 -> Ergebnis: nix passiert
Ich warte 2 Sekunden -> Ergebnis: "Kombiaktion für Taste 1 und 2" wird ausgeführt
Ich lasse Taste 2 los -> Ergebnis: nix passiert
Ich drücke erneut Taste 2 -> Ergebnis: nix passiert
Ich lasse beide Tasten los -> Ergebnis: nix passiert
Ich drücke Taste 2 -> Ergebnis: Aktion Taste 2 wird ausgeführt
Ich drücke kurz danach (50ms) die Taste 1 -> Ergebnis: nix passiert
Ich warte 2 Sekunden -> Ergebnis: "Kombiaktion für Taste 1 und 2" wird ausgeführt

So ist es gewünscht.

Ich habe in der Vergangenheit schon so viel rumprobiert und kann sagen, dass es mal wieder die Sonderfälle sind, die es kompliziert machen.

Wurden z.B. drei Tasten über längere Zeit gedrückt gehalten und ihre Aktion ausgelöst, soll beim loslassen einer dieser drei Taste nach einem kurzen Moment nicht die Aktion ablaufen, die für das gedrückt halten der zwei übrig gebliebenen Tasten vorgesehen ist (ähnlich wie im Beispiel weiter oben).
Gruß Chris

Chris72622:
Ich habe in der Vergangenheit schon so viel rumprobiert und kann sagen, dass es mal wieder die Sonderfälle sind, die es kompliziert machen.

Je komplizierter das Bedienkonzept, desto komplizierter auch die programmtechnische Umsetzung.

Aber so lange das Bedienkonzept in sich logisch ist, kann es auch programmiert werden.

Chris72622:
Wurden z.B. drei Tasten über längere Zeit gedrückt gehalten und ihre Aktion ausgelöst, soll beim loslassen einer dieser drei Taste nach einem kurzen Moment nicht die Aktion ablaufen, die für das gedrückt halten der zwei übrig gebliebenen Tasten vorgesehen ist (ähnlich wie im Beispiel weiter oben).

Wie gesagt, es muss logisch umsetzbar sein, ohne dass das Programm hellsehen können muss.

Logisch handhabbar wäre z.B. das: Sobald innerhalb der Programmlogik eine Mehrfachaktion ausgeführt wurde, egal ob mit 2 oder 3 Tasten nach Zeitablauf, sind alle Tastenaktionen so lange gesperrt, bis sämtliche Tasten einmal gleichzeitig losgelassen worden sind.

Keine Ahnung, ob das von der Logik her passen würde. Für Dein letztgenanntes Beispiel würde es jedenfalls passen: Neue Aktionen nach einer Mehrfachaktion erst dann wieder, nachdem vorher alle Tasten gleichzeitig losgelassen worden sind.

So- bin dank Dir wieder ein kleines Stückchen weiter.

Hab den Sketch nun so umgebaut, dass es in die richtige Richtung geht.

Wenn ich es nun nur noch hinbekommen würde, dass buttonPressCount auf Null gesetzt wird, sobald keine Knöpfe mehr gedrückt sind, dann wäre ich glücklich.

Gruß Chris

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 10             // maximale Prellzeit in Millisekunden
byte buttonPins[] = {
  7, 8, 2, 3, 4, 5, 6};                 // Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins) // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];     // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS];    // Speichert Flankenwechsel an den Pins
enum{
  UNCHANGED, BUTTONUP, BUTTONDOWN};
byte buttonPressCount;            // Speichert, wie oft Tasten gedrückt wurden
unsigned long buttonDownTime[NUMBUTTONS]; // Speichert den Zeitpunkt an dem ein Button gedrückt wird

void eingabe()  // Tasterstatus und Flankenwechsel an den Tastern auswerten  
{
  static unsigned long lastButtonTime;                 // Zeitstempel der erfasst hat, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange));         // Alle alten Flankenwechsel verwerfen (buttonChange wird komplett mit Nullen überschrieben)
  if(millis() - lastButtonTime < BOUNCETIME) return;  // Innerhalb der Prellzeit die Funktion ggfs. verlassen

  lastButtonTime = millis();                           // Zeitstempel der letzten Buttonabfrage aktualisieren
  
  for (int i = 0; i < NUMBUTTONS; i++)                 // Für alle Buttons der Reihe nach
  {
    byte currentState = digitalRead(buttonPins[i]);               // Aktuellen Buttonstatus am Eingang erfragen
    if(INPUTMODE == INPUT_PULLUP) currentState = !currentState;  // Vertauschte Logik bei INPUT_PULLUP

    if(currentState != buttonState[i])                           // Bei erkanntem Flankenwechsel
    {
      if(currentState == HIGH)
      {
        buttonChange[i] = BUTTONDOWN;     // Wenn der Button gedrückt wurde
        buttonDownTime[i] = millis();     // den Zeitpunkt für diesen Button festhalten
        buttonPressCount++;               // ButtonPresses inklementieren
      }
      else buttonChange[i] = BUTTONUP;                           // Wenn der Button losgelassen wurde
    }
    buttonState[i] = currentState;                           // Den aktuellen Buttonzustand speichern
  }

  if(buttonState[2] && buttonState[3]) // alle zwei Tasten sind unten
  {
    if(millis() - buttonDownTime[2] >= 1200 && millis() - buttonDownTime[3] >= 1200)  // prüfen, ob sie beide mindestens 1000ms unten sind
    {
      if(buttonPressCount == 2) Serial.println("Zupp!");
    }
  }
}

void verarbeitung()
{
}

void ausgabe(){
  // Bei Tastendruck Ausgabe des Pins auf Serial
  // Button gedrückt: Minus-Pinnummer
  // Button losgelassen: Pinnummer
  for (int i = 0; i < NUMBUTTONS; i++)
  {
    switch (buttonChange[i])  
    {
    case BUTTONDOWN: 
      Serial.println(-buttonPins[i]);
      Serial.print("buttonPressCount: ");
      Serial.println(buttonPressCount);
      break;
    case BUTTONUP: 
      Serial.println(buttonPins[i]);
      break;

    }
  }
}


void setup() {
  Serial.begin(9600);
  for (int i = 0; i < NUMBUTTONS; i++) pinMode(buttonPins[i], INPUTMODE);
}

void loop() {
  eingabe();
  verarbeitung();
  ausgabe();
}

Kurz / lang kannst du erst unterschieden beim loslassen oder nach Ablauf der Zeit.

Wenn das zwei unterschiedliche Funktionen sind darf "kurz" erst beim Loslassen angestossen werden.

Das ist eigentlich logisch und kann auch so programmiert werden.

Besser ist es natürlich, wenn die Funktionen etwas gemeinsames haben, das schon am Anfang gemacht werden kann (Anzeige ändern z.B)

Bitte mein Posting mit der #7 und vor allem das darin enthaltene Beispiel noch einmal ganz genau lesen.

Gruß Chris

Chris72622:
Wenn ich es nun nur noch hinbekommen würde, dass buttonPressCount auf Null gesetzt wird, sobald keine Knöpfe mehr gedrückt sind, dann wäre ich glücklich.

Die Anzahl der gedrückten Buttons kannst Du doch einfach durchzählen.

void eingabe()  // Tasterstatus und Flankenwechsel an den Tastern auswerten  
{
  static unsigned long lastButtonTime;                 // Zeitstempel der erfasst hat, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange));         // Alle alten Flankenwechsel verwerfen (buttonChange wird komplett mit Nullen überschrieben)
  if(millis() - lastButtonTime < BOUNCETIME) return;  // Innerhalb der Prellzeit die Funktion ggfs. verlassen

  lastButtonTime = millis();                           // Zeitstempel der letzten Buttonabfrage aktualisieren
  buttonPressCount=0; // Reset auf 0
  for (int i = 0; i < NUMBUTTONS; i++)                 // Für alle Buttons der Reihe nach
  {
    byte currentState = digitalRead(buttonPins[i]);               // Aktuellen Buttonstatus am Eingang erfragen
    if(INPUTMODE == INPUT_PULLUP) currentState = !currentState;  // Vertauschte Logik bei INPUT_PULLUP

    if(currentState != buttonState[i])                           // Bei erkanntem Flankenwechsel
    {
      if(currentState == HIGH)
      {
        buttonChange[i] = BUTTONDOWN;     // Wenn der Button gedrückt wurde
        buttonDownTime[i] = millis();     // den Zeitpunkt für diesen Button festhalten
        buttonPressCount++;               // ButtonPresses inklementieren
      }
      else buttonChange[i] = BUTTONUP;                           // Wenn der Button losgelassen wurde
    }
    buttonState[i] = currentState;                           // Den aktuellen Buttonzustand speichern
    buttonPressCount+=currentState;  // gedrückten Button addieren
  }
...

Dadurch, dass buttonPressCount mit jedem Durchlauf erneut berechnet wird, kann ich folgendes, weiter oben angerissene Problem jedoch noch immer nicht umgehen:

"Wurden z.B. drei Tasten über längere Zeit gedrückt gehalten und ihre Aktion ausgelöst, soll beim loslassen einer dieser drei Taste nach einem kurzen Moment nicht die Aktion ablaufen, die für das gedrückt halten der zwei übrig gebliebenen Tasten vorgesehen ist."

Das Problem im Detail:

Ich drücke Taste 1 -> Ergebnis: Aktion Taste 1 wird ausgeführt
Ich drücke kurz danach (50ms) die Taste 2 -> Ergebnis: nix passiert
Ich drücke kurz danach (50ms) die Taste 3 -> Ergebnis: nix passiert
Ich warte 2 Sekunden -> Ergebnis: "Kombiaktion für Taste 1, 2 und 3" wird ausgeführt
Ich lasse eine der drei Tasten los -> Ergebnis: nix passiert
Ich warte 2 Sekunden -> Ergebnis: "Kombiaktion für Taste 1 und 2" wird ausgeführt.. genau das soll nicht passieren.

Sobald eine Kombiaktion ausgeführt wurde, sollen zunächst einmal sämtliche Tasten losgelassen werden müssen um erneut eine Aktion auslösen zu können.

Bis auf dieses Problem läuft der folgende Code jedoch.

Gruß Chris

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 10             // maximale Prellzeit in Millisekunden
byte buttonPins[] = {
  7, 8, 2, 3, 4, 5, 6};                 // Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins)   // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];           // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS];          // Speichert Flankenwechsel an den Pins
enum{
  UNCHANGED, BUTTONUP, BUTTONDOWN};
byte buttonPressCount;                    // Speichert wie oft Tasten, nachdem zuvor keine mehr gedrückt wurde, gedrückt wurden
unsigned long buttonDownTime[NUMBUTTONS]; // Speichert den Zeitpunkt an dem ein Button gedrückt wird

void eingabe()  // Tasterstatus und Flankenwechsel an den Tastern auswerten  
{
  static unsigned long lastButtonTime;                 // Zeitstempel der erfasst hat, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange));         // Alle alten Flankenwechsel verwerfen (buttonChange wird komplett mit Nullen überschrieben)
  if(millis() - lastButtonTime < BOUNCETIME) return;   // Innerhalb der Prellzeit die Funktion ggfs. verlassen

  lastButtonTime = millis();                           // Zeitstempel der letzten Buttonabfrage aktualisieren
  buttonPressCount = 0;                                // Buttonzähler auf 0 zurücksetzen
  for (int i = 0; i < NUMBUTTONS; i++)                 // Für alle Buttons der Reihe nach
  {
    byte currentState = digitalRead(buttonPins[i]);               // Aktuellen Buttonstatus am Eingang erfragen
    if(INPUTMODE == INPUT_PULLUP) currentState = !currentState;   // Vertauschte Logik bei INPUT_PULLUP

    if(currentState != buttonState[i])                            // Bei erkanntem Flankenwechsel
    {
      if(currentState == HIGH)
      {
        buttonChange[i] = BUTTONDOWN;     // Wenn der Button gedrückt wurde
        buttonDownTime[i] = millis();     // den Zeitpunkt für diesen Button festhalten
        // buttonPressCount++;            // ButtonPresses inklementieren
      }
      else buttonChange[i] = BUTTONUP;    // Wenn der Button losgelassen wurde
    }
    buttonState[i] = currentState;        // Den aktuellen Buttonzustand speichern
    buttonPressCount += currentState;     // gedrückten Button addieren
  }
  
  if(buttonState[2] && buttonState[3])    // Die Tasten 2 und 3 werden gedrückt gehalten
  {
    if(millis() - buttonDownTime[2] >= 1200 && millis() - buttonDownTime[3] >= 1200)  // prüfen, ob sie beide mindestens 1000ms unten sind
    {
      if(buttonPressCount == 2) Serial.println("Zupp!");
    }
  }
}

void verarbeitung()
{
}

void ausgabe(){
  // Bei Tastendruck Ausgabe des Pins auf Serial
  // Button gedrückt: Minus-Pinnummer
  // Button losgelassen: Pinnummer
  for (int i = 0; i < NUMBUTTONS; i++)
  {
    switch (buttonChange[i])  
    {
    case BUTTONDOWN: 
      Serial.println(-buttonPins[i]);
      Serial.print("buttonPressCount: ");
      Serial.println(buttonPressCount);
      break;
    case BUTTONUP: 
      Serial.println(buttonPins[i]);
      break;
    }
  }
}


void setup() {
  Serial.begin(9600);
  for (int i = 0; i < NUMBUTTONS; i++) pinMode(buttonPins[i], INPUTMODE);
}

void loop() {
  eingabe();
  verarbeitung();
  ausgabe();
}

Na dann programmier das doch so und definiere eine StatusVariable "KombiAktion_Ausgeführt", die erst wieder gelöscht wird, wenn keine Taste mehr gedrückt ist.

Generell habe ich die Frage:
Das ist eher ein Puzzle als ein Bedienkonzept. Bist du sicher dass du das willst?

Chris72622:
Dadurch, dass buttonPressCount mit jedem Durchlauf erneut berechnet wird, kann ich folgendes, weiter oben angerissene Problem jedoch noch immer nicht umgehen:

"Wurden z.B. drei Tasten über längere Zeit gedrückt gehalten und ihre Aktion ausgelöst, soll beim loslassen einer dieser drei Taste nach einem kurzen Moment nicht die Aktion ablaufen, die für das gedrückt halten der zwei übrig gebliebenen Tasten vorgesehen ist."

Du hast bisher das EVA-Prinzip noch überhaupt nicht verinnerlicht.

Die "eingabe()" von außen muss vollkommen von der logischen "verarbeitung()" getrennt werden. Und diese wiederum wird von der physikalischen "ausgabe()" komplett entkoppelt.

Die Eingabe ist beendet, sobald

  • der gedrückt/losgelassen Status der Buttons feststeht
  • die Anzahl der gedrückten Tasten durchgezählt wurde
  • Statusänderungen ermittelt und BUTTONUP/BUTTONDOWN gesetzt wurde

Die Verarbeitung von Tastenbetätigungen findet nicht in der "eingabe()" statt, sondern erst im Schritt "verarbeitung()". Und dort mußt Du Dir dann anhand von logischen Bedingungen merken, ob eine Blockierung vorliegt, die erst Du eine andere eingabe() aufgehoben werden kann. Dazu brauchst Du dann Hilfsvariablen, die für die Verarbeitung benötigt werden.

Zum Beispiel:

  • Wenn ein Mehrfachcode zu einer Aktion führt, wird "blocked" auf true gesetzt
  • Falls "blocked" gesetzt ist und die Zahl gedrückter Tasten >0 ==> keine weitere Verarbeitung
  • Falls irgendwann die Zahl gedrückter Tasten 0 ist ==> "blocked" löschen und false setzen
  • und die logischen Bedingungen weiter verarbeiten
void verarbeitung() 
{
  static boolean blocked;
  if (blocked && buttonPressCount>0) return;
  blocked=false; // Blockierung aufheben, wenn keine Taste mehr gedrückt
  if(buttonState[2] && buttonState[3])    // Die Tasten 2 und 3 werden gedrückt gehalten
  {
    if(millis() - buttonDownTime[2] >= 1200 && millis() - buttonDownTime[3] >= 1200)  // prüfen, ob sie beide mindestens 1000ms unten sind
    {
      if(buttonPressCount == 2)
       {
         Serial.println("Zupp!"); // ==> gehört nicht hierher, sondern zur "ausgabe()"
         // Hier sollten nur logische Zustände verarbeitet werden
         blocked=true;
       }
    }
  }
}

Falls das "Serial.println("Zupp!");" eine Ausgabe zum Debuggen sein soll, kann man das natürlich mal ausnahmsweise machen.

Aber alle Ausgaben gehören erst in die Funktion "ausgabe()" hinein. In der Verarbeitung würdest Du normalerweise nur eine Statusvariable setzen, was keine Zahl sein muss, sondern auch eine Enumerations-Konstante sein kann, wenn Du "sprechenden Code" programmieren möchtest.

Hallo jurs,

danke für Deine Geduld.

Ich habe das mit dem EVA-Prinzip offensichtlich zu global betrachtet. Ich bin nämlich bis hierher von folgender Herangehensweise ausgegangen:

Eingabe:
Knopfentprellung, Tastenstatuserfassung und Definition, welche Tasten und welche Tastenkombination etwas auslösen sollen

Verarbeitung:
Je nach Status des Prozessors festlegen, welche Tasten und Tastenkombination überhaupt etwas auslösen können sollen

Ausgabe:
Ausführen der Aktionen

Somit habe ich also das "Endprodukt" (sprich den Prozessor inkl. Ein- und Ausgabe) als Ganzes betrachtet. Wenn ich Dich nun richtig verstanden habe, sollte ich das EVA-Prinzip jedoch differenzierter anwenden:

Eingabe:
Knopfentprellung und Tastenstatuserfassung

Verarbeitung:
Definition, welche Tasten und welche Tastenkombination überhaupt etwas auslösen können sollen

Ausgabe:
Je nach Status des Prozessors festlegen, welche Tasten und Tastenkombination zu welchen Aktionen führen sollen

Das EVA-Prinzip würde sich somit auf den Eingangsprozess beziehen; sprich auf den Verlauf vom Tastendruck bis zur Übergabe dessen "Ergebnis" an den Prozessor.

Somit habe ich doch dann die folgenden Codezeilen eigentlich schon falsch platziert, da sie Deiner Meinung nach dann in Verarbeitung gehören, oder?

if(buttonState[2] && buttonState[3])    // Die Tasten 2 und 3 werden gedrückt gehalten
  {
    if(millis() - buttonDownTime[2] >= 1200 && millis() - buttonDownTime[3] >= 1200)  // prüfen, ob sie beide mindestens 1000ms unten sind
    {
      if(buttonPressCount == 2) Serial.println("Zupp!");
    }
  }

Ja, "Zupp!" war natürlich lediglich zum Debuggen.

michael_x:
Das ist eher ein Puzzle als ein Bedienkonzept. Bist du sicher dass du das willst?

Ansichtssache.

Ja.

Gruß Chris

Edit_1: Ah ok, jurs- der letzte von Dir gepostete Codefetzen bestätigt meine Theorie. :slight_smile:
Edit_2: Der folgende, von mir erweiterte Code verhält sich nun fast genau so wie es sein soll. Dafür sieht er mittlerweile leider etwas konfus aus und ich bin mir nicht sicher, ob es nicht auch einfacher gehen würde. Leider kommt es aufgrund der Art wie die Tasten entprellt werden noch zu folgendem Fehler:

Wenn zwei Tasten gleichzeitig gedrückt und gehalten werden sollen um die den beiden Tasten zugeordnete Halteaktion auszulösen, kommt es bei sehr gleichzeitigem Drücken zu einer Unterdückung von Tasteneinzelbefehlen.

Mit Tasteneinzelbefehlen meine ich die Befehle, die ausgelöst werden, sobald eine einzelne Taste gedrückt wird, während keine anderen Tasten gedrückt sind.

Ich bin aufgrund dieses Fehlverhaltens am überlegen, ob es deshalb nicht doch sinnvoll ist die Bounce2_Library diesem Code vorzuschalten, um somit die Vorzüge von jurs' Codelogik und die nicht sperrende Entprellung der Bibliothek miteinander zu kombinieren.

#define INPUTMODE INPUT_PULLUP    // INPUT oder INPUT_PULLUP
#define BOUNCETIME 10             // maximale Prellzeit in Millisekunden
byte buttonPins[] = {
  7, 8, 2, 3, 4, 5, 6};                 // Pin-Nummern der angeschlossenen Buttons
#define NUMBUTTONS sizeof(buttonPins)   // Anzahl der Buttons (automatisch definiert)
byte buttonState[NUMBUTTONS];           // Speichert den aktuellen HIGH/LOW Status der Pins
byte buttonChange[NUMBUTTONS];          // Speichert Flankenwechsel an den Pins
enum{
  UNCHANGED, BUTTONUP, BUTTONDOWN};
byte buttonPressCount;                    // Speichert wie viele Tasten gedrückt gehalten werden
byte maxButtonPressCount;                 // Speichert wie viele Tasten seit dem letzten Loslassen maximal gedrückt gehalten wurden
unsigned long buttonDownTime[NUMBUTTONS]; // Speichert den Zeitpunkt an dem ein Button gedrückt wird

void eingabe()  // Tasterstatus und Flankenwechsel an den Tastern auswerten  
{
  static unsigned long lastButtonTime;                 // Zeitstempel der erfasst hat, wann die Routine zuletzt durchlief
  memset(buttonChange,0,sizeof(buttonChange));         // Alle alten Flankenwechsel verwerfen (buttonChange wird komplett mit Nullen überschrieben)
  if(millis() - lastButtonTime < BOUNCETIME) return;   // Innerhalb der Prellzeit die Funktion ggfs. verlassen

    lastButtonTime = millis();                           // Zeitstempel der letzten Buttonabfrage aktualisieren
  buttonPressCount = 0;                                // Buttonzähler auf 0 zurücksetzen
  for(int i = 0; i < NUMBUTTONS; i++)                 // Für alle Buttons der Reihe nach
  {
    byte currentState = digitalRead(buttonPins[i]);               // Aktuellen Buttonstatus am Eingang erfragen
    if(INPUTMODE == INPUT_PULLUP) currentState = !currentState;   // Vertauschte Logik bei INPUT_PULLUP

    if(currentState != buttonState[i])                            // Bei erkanntem Flankenwechsel
    {
      if(currentState == HIGH)
      {
        buttonChange[i] = BUTTONDOWN;     // Wenn der Button gedrückt wurde
        buttonDownTime[i] = millis();     // den Zeitpunkt für diesen Button festhalten
      }
      else buttonChange[i] = BUTTONUP;    // Wenn der Button losgelassen wurde
    }
    buttonState[i] = currentState;        // Den aktuellen Buttonzustand speichern
    buttonPressCount += currentState;     // gedrückten Button addieren
  }
}



void verarbeitung() 
{
  static boolean blocked;                      // Definiert, ob das ausführen einer Aktion unterbunden wird, oder nicht
  if(buttonPressCount > 0 && blocked) return;  // Wurde bereits eine Aktion ausgelöst, während mind. ein Knopf gedrückt gehalten wird

  blocked = false;                             // Blockierung aufheben, sobald keine Tasten mehr gedrückt gehalten werden
  if(buttonPressCount == 0) maxButtonPressCount = 0;
  for(int i = 0; i < NUMBUTTONS; i++)
  {
    if(buttonPressCount == i+1 && maxButtonPressCount < i+1) maxButtonPressCount = i+1;  // Ermittelt die maximale Anzahl der gleichzeitig gedrückt gehaltenen Tasten
    if(buttonPressCount == i+1 && maxButtonPressCount > i+1) blocked = true;             // Wurden bereits mehr Tasten gleichzeitig gedrückt
                                                                                         // wie die momentan gedrückt gehaltenen Tasten -> Aktionen blockieren
    if(buttonState[i])                          // Die Taste i wird gedrückt gehalten
    {
      if(millis() - buttonDownTime[i] >= 1200)  // Prüfen, ob die Taste mindestens 1200ms gerückt gehalten wird
      {
        if(buttonPressCount == 1 && maxButtonPressCount == 1)               // Wenn nur eine Taste gedrückt gehalten wird
        {
          Serial.print("Halteaktion Taste "); // ==> gehört nicht hierher, sondern zur "ausgabe()"
          Serial.println(i);
          // Hier sollten nur logische Zustände verarbeitet werden
          blocked = true;
          maxButtonPressCount = 0;
        }
      }
    }
  }
  if(buttonState[2] && buttonState[3])         // Die Tasten 2 und 3 werden gedrückt gehalten
  {
    if(millis() - buttonDownTime[2] >= 1200 && millis() - buttonDownTime[3] >= 1200)  // Prüfen, ob beide Tasten mindestens 1200ms gerückt gehalten werden
    {
      if(buttonPressCount == 2 && maxButtonPressCount == 2)                // Wenn nur genau zwei Tasten gedrückt gehalten werden
      {
        Serial.println("Halteaktion Tasten 2+3"); // ==> gehört nicht hierher, sondern zur "ausgabe()"
        // Hier sollten nur logische Zustände verarbeitet werden
        blocked = true;
        maxButtonPressCount = 0;
      }
    }
  }
}



void ausgabe(){
  // Bei Tastendruck Ausgabe des Pins auf Serial
  // Button gedrückt: Minus-Pinnummer
  // Button losgelassen: Pinnummer
  for (int i = 0; i < NUMBUTTONS; i++)
  {
    switch (buttonChange[i])  
    {
    case BUTTONDOWN: 
      Serial.println(-buttonPins[i]);
      Serial.print("Gleichzeitig gedrueckt gehaltene Tasten: ");
      Serial.println(buttonPressCount);
      break;
    case BUTTONUP: 
      Serial.println(buttonPins[i]);
      break;
    }
  }
}


void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < NUMBUTTONS; i++) pinMode(buttonPins[i], INPUTMODE);
}

void loop()
{
  eingabe();
  verarbeitung();
  ausgabe();
}

Chris72622:
Edit_2: Der folgende, von mir erweiterte Code verhält sich nun fast genau so wie es sein soll. Dafür sieht er mittlerweile leider etwas konfus aus und ich bin mir nicht sicher, ob es nicht auch einfacher gehen würde. Leider kommt es aufgrund der Art wie die Tasten entprellt werden noch zu folgendem Fehler:

Wenn zwei Tasten gleichzeitig gedrückt und gehalten werden sollen um die den beiden Tasten zugeordnete Halteaktion auszulösen, kommt es bei sehr gleichzeitigem Drücken zu einer Unterdückung von Tasteneinzelbefehlen.

Ganz einfach: Dann ist die von Dir programmierte verarbeitung()-Funktion falsch programmiert.

Wenn Du Die Verarbeitung richtig machst, würde ein "gleichzeitiges" Drücken von Buttons (also Tastendrücke, die innerhalb der BOUNCETIME zusammenfallen) allenfalls dazu führen, dass die Aktionen bei der verarbeitung() dann auch gleichzeitig im selben Schleifendurchlauf auf aktiv gesetzt und unmittelbar nacheinander ausgeführt werden.

Chris72622:
Ich bin aufgrund dieses Fehlverhaltens am überlegen, ob es deshalb nicht doch sinnvoll ist die Bounce2_Library diesem Code vorzuschalten, um somit die Vorzüge von jurs' Codelogik und die nicht sperrende Entprellung der Bibliothek miteinander zu kombinieren.

Was willst Du da "kombinieren"? Beim EVA-Prinzip ist es so, dass Du jede der Funktionen für eingabe(), verarbeitung() und ausgabe() unabhängig voneinander komplett austauschen kannst. Oder für sich anpassen kannst.

Du kannst also meine Buttonabfrage komplett durch irgendwas anderes ersetzen, z.B. Buttons mit der Bounce-Library abfragen.

Du kannst auch die eingabe(); komplett auf irgendwas anderes umstellen, z.B. so, dass Du nicht Buttons drückst, sondern als Eingabe IR-Codes von einer Infrarotfernbedienung kommen.

Du kannst die eingabe(); auch ändern, wenn Du bei der Verarbeitung nicht damit klarkommst, dass zwei Buttons gleichzeitig gedrückt oder losgelassen werden können. Dann könntest Du die eingabe(); beispielsweise so umprogrammieren, dass nicht bei jedem Aufruf alle Buttons abgefragt werden, sondern bei jedem Aufruf nur ein einzelner Button, und ein Leseindex wird jedesmal weitergesetzt. Das Ergebnis wäre dann, dass sich bei jedem Aufruf von eingabe(); nur ein einziger Buttonstatus geändert haben kann. Wenn sich das von Dir für die Programmlogik leichter verarbeiten läßt.

Ja, ich muss von vorne beginnen und das werde ich auch. Deine for-Schleifen gestützten Codeideen waren und sind mir nach wie vor eine große Hilfe.

Sobald ich die nötige Ruhe habe, werde ich mich erneut dransetzen. Sollte ich die Lösung gefunden haben, werde ich sie hier posten.

Gruß Chris

Chris72622:
Ja, ich muss von vorne beginnen und das werde ich auch. Deine for-Schleifen gestützten Codeideen waren und sind mir nach wie vor eine große Hilfe.

Nochmal zum Programmieren komplexer Programme, die fehlerfrei arbeiten:

Der Aufbau nach dem EVA-Prinzip ist das eine Thema.

Das andere große Thema ist "Datenstrukturen und Algorithmen".

Meine eingabe(); Funktion für die Buttons arbeitet unter anderem deshalb so einwandfrei und zuverlässig, weil diese auf Datenstrukturen beruht, die mit Hilfe eines Algorithmus abgearbeitet wird. Deshalb ist die Anzahl der zu verabeitenden if-Bedingungen auch immer gleich und ändert sich nicht, wenn andere Button-Pins oder eine andere Anzahl von Buttons zu verarbeiten sind. Die eingabe(); Funktion ändert sich daher überhaupt nicht, egal ob 2, 10 oder 50 Buttons zu verarbeiten sind. Ich lege fest, wie ein Button abgefragt wird, und damit werden alle anderen Buttons genau so abgefragt, denn die Auswertung der Tastendrücke wird in einer Schleife abgearbeitet.

Natürlich ist auch die eingabe(); Funktion nicht in Stein gemeißelt. Wie beschrieben, könnte man die Eingabelogik auch leicht abändern, falls es zum Beispiel unerwünscht ist, dass sich bei einem Durchlauf der Funktion mehr als ein Buttonstatus ändert. Das zu ändern, dass jeder Aufruf von eingabe(); nur zu keiner oder einer Buttonstatusänderung führt, aber niemals mehrere Statusänderungen gleichzeitig stattfinden, wäre recht einfach umsetzbar.

Und dasselbe, was für die eingabe(); Funktion möglich ist, kann man natürlich auch für die verarbeitung(); realisieren: Man definiert sich eine Datenstruktur mit Variablen für die Verarbeitung, deklariert ein "array of struct", und in einer for-Schleife wird das Variablen-Array verarbeitet.

Beispiel: Du zählst alle möglichen Aktionen in einer Enumeration durch und vergibst symbolische Namen, dann definierst Du, welche Verarbeitungs-Variablen für jede Aktion notwendig sind. Dann legst Du ein Array solcher Variablen an. Z.B. für eine Aktion mit bis zu drei Buttons sowas:

enum {EINZEL0, EINZEL1, EINZEL2, EINZEL3, EINZEL4, DOPPEL01, DOPPEL12, DOPPEL23, DREIFACH012, DREIFACH123};
struct action_t {byte aktion; byte btn1; byte btn2; byte btn3; boolean isActive; unsigned long activeSince, boolean funcRequested; boolean funcExecuted;};
action_t actions[]={
  {EINZEL0, 0, -1, -1}, Aktion, 3x Buttonindex, rest wird ausgenullt
  {EINZEL1, 1, -1, -1},
  {EINZEL2, 2, -1, -1},
  {EINZEL3, 3, -1, -1},
  {EINZEL4, 4, -1, -1},
  {DOPPEL01,0,  1, -1},
  {DOPPEL01,0,  1, -1},
  {DOPPEL12,1,  2, -1},
  {DREIFACH012,0, 1, 2},
  {DREIFACH123,1, 2, 3},
};

Also jetzt nicht direkt für bare Münze nehmen, das ist nur zur Demonstration, wie man eine eigene Datenstruktur mit 'struct' definiert und ein "array of struct" mit selbstdefinierten Datenstrukturen anleggt.

Und in Deiner "verarbeitung()" Funktion würdest Du nun den Algorithmus festlegen, nach dem die Funktionen aktiviert werden sollen. In einer for-Schleife. Und die for-Schleife ist immer dieselbe, egal wie viele Aktionen man mit wie vielen verschiedenen Tastendrücken aktivieren kann.

D.h. "if" gibt es auch in der Verarbeitung immer nur für den Algorithmus an sich, aber nicht für jede Aktion einzeln.

Also der Programmierer gibt den Algorithmus vor, der auf eine Datenstruktur angewendet wird, und der Controller mit seiner Rechenleistung arbeitet das in einer for-Schleife ab.

Du bist aber irgendwie komplett anders dabei, Deine Aktionen zu programmieren: Statt den Controller in einer Datenstruktur nachschlagen zu lassen, mit welchen Buttons er welche Aktion unter welchen Timing-Voraussetzungen aufrufen soll, versuchst Du beim Programmieren schon im Voraus die Bedingungen vorauszudenken, die eventuell erfüllt sein könnten. "Wenn der User jetzt diesen und später den Button für so und so lange drückt, dann ...".

Das KANN man natürlich so machen, aber je komplexer die Programmlogik wird, desto fehlerträchtiger ist es. Ein wenig fehleranfälliges Programm arbeitet deshalb immer mit Datenstrukturen, in denen die Möglichkeiten als Array vorgegeben sind, und mit einem Algorithmus, der auf die Datenstruktur angesetzt wird und das Ding stur von vorne bis hinten abarbeitet.