Tasten über Interrupt und "Drückdauer"

Hallo und Guten Tag,

zur Zeit beschäftige ich mich mit einem Arduino Due. Hier komme ich nicht weiter, da es mir noch an Verständnis fehlt, wenn es um den "Interrupt" geht.

Ich habe an einen Due vier Taster angeschlossen. Alle vier Taster werden über je einen Interrupt überwacht, da die spätere Anwendung zeitkritisch werden kann.

Hier das einfache funktionierende Programm dazu:

//-----------------------------------------------------------------------------
// Taster-Steuerung
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Die Pin's vom Bord erhalten einen Namen:
volatile int tasterLinks = 2;
volatile int tasterRechts = 3;
volatile int tasterStop = 4;
volatile int tasterWarn = 5;

// Variablen und Konstanten
volatile int irqFlag = 0; // 0=do nothing


//--------------------------------------------------------------------------------
// Setup and Main:
void setup()
{
Serial.begin(9600); // Initialisierung Interface mit 9600 Baud

pinMode(2, INPUT); // Pin ist Eingang
pinMode(3, INPUT); // Pin ist Eingang
pinMode(4, INPUT); // Pin ist Eingang
pinMode(5, INPUT); // Pin ist Eingang

attachInterrupt(2, fTasterLinks, HIGH); // Enable interrupt
attachInterrupt(3, fTasterRechts, HIGH); // Enable interrupt
attachInterrupt(4, fTasterStop, HIGH); // Enable interrupt
attachInterrupt(5, fTasterWarn, CHANGE); // Enable interrupt
}


//--------------------------------------------------------------------------------
void loop()
{
Serial.print("Loop ");
Serial.println(irqFlag);

switch (irqFlag)
{
case 1:
Serial.println("Taste Links ");
delay(1000);
irqFlag=0;
break;

case 2:
Serial.println("Taste Rechts");
delay(1000);
irqFlag=0;
break;

case 3:
Serial.println("Taste Stop");
delay(1000);
irqFlag=0;
break;

case 4:
Serial.println("Taste Warn");
delay(1000);
irqFlag=0;
break;

default:
irqFlag=0; // Reset IRQ flag
}
}


//--------------------------------------------------------------------------------
// Subs and Functions:

// Interrupt-Routine Taster Links
void fTasterLinks()
{
irqFlag = 1;
}

// Interrupt-Routine Taster Rechts
void fTasterRechts()
{
irqFlag=2;
}

// Interrupt-Routine Taster Stop
void fTasterStop()
{
irqFlag=3;
}

// Interrupt-Routine Taster Warn
void fTasterWarn()
{
irqFlag=4;
}

Soweit alles recht einfach. Auf eine Entprellung habe ich zur Zeit verzichtet, da ich diese wohl besser mit einem Widerstand und Kondensator auf Hardwarebasis bewerkstellige.
Was ich jedoch nicht hinbekomme, ist die "Drückzeit eines Taster" zu bestimmen. Ich möchte eine kurze "Kontaktdauer" und eine lange "Kontaktdauer" bei jedem Taster unterscheiden können.
Mit millis() kann ich in den Interruptroutinen nicht arbeiten, der Befehl funktioniert dort nicht.
Ich habe irgendetwas über "Register" gelesen, die man auslesen kann. Verstanden habe ich es nicht.
Wer kann helfen?

Du kannst den Taster zuerst auf Interrupt mit Steigende Flanke programmieren. Beim ersten interrupt dan millis abspeichern, den interrupt auf fallende Flanke Umprogrammieren und dann aus der Interruptroutine sprinen. Bei nächsten Interrupt millis Differenz berechenen.
Alternativ kannst Du auch Flankeninterrupt ( HL und LH nehmen) und den außer millis auch den Zustand des Tasters abspeichern. Auswertung dann bei der fallenden Falanke.
Grüße Uwe

Einmal kannst du millis() schon verwenden. Der Zähler wird lediglich nicht upgedatet, da millis() selbst über einen Timer-Interrupt läuft.

odiug:
zur Zeit beschäftige ich mich mit einem Arduino Due. Hier komme ich nicht weiter, da es mir noch an Verständnis fehlt, wenn es um den "Interrupt" geht.

Ich habe an einen Due vier Taster angeschlossen. Alle vier Taster werden über je einen Interrupt überwacht, da die spätere Anwendung zeitkritisch werden kann.

OMG!

Nur mal zwei Zeilen Beispielcode aus Deinem Programm mit ergänzendem Kommentar von mir:

delay(1000); // Während des laufenden delay() werde eine Taste gedrückt, dabei ein Tasten-Flag gesetzt
irqFlag=0;   // und hier wird das Flag wieder gelöscht.

Tastendrücke während eines laufenden delay() gehen Dir so durch die Lappen.
Und zwar trotz Verwendung von Interrupts.

Im übrigen: Für zeitkritische Aktionen ist es Pflicht, frei von jeglichen delay() Aufrufen zu programmieren, aber es ist (schon gar nicht beim Auswerten von manuell bedienten Tastern) keine Pflicht, Interrupts zu verwenden.

@uwefed
Den Ansatz verstehe ich und werde ihn mal probierern. Bin mir ad hoc noch nicht im klaren, wie ich den Interrupt umprogrammieren kann, aber mal schauen ....
Den zweiten Ansatz verstehe ich nicht.
.... aber vielen Dank Uwe, vielleicht komme ich so schon weiter

@Serenifly
Ich weiß, aber der zweite fehlte ja bisher ....

@jurs
delay ist nur testweise drin, damit man im Monitor besser lesen konnte. Hätte dies erwähnen müssen.
Ich danke aber für den Hinweis !!!

So dank dem Tipp von uwefed jetzt habe ich eigentlich was ich will. Die Tasten sind entprellt, ich kann die Drückdauer bestimmen und je nachdem eine Funktion auslösen.
Leider funktioniert aber der “Interrupt” nicht mehr. Wenn “fTasteLinksKurz” ausgeführt wird, kann ich jede Taste drücken ohne dass es erkannt wird. Verstehe ich nicht, da ich ja in keiner ISR mehr bin
Also habe ich einen Gedankenfehler im Umgang mit “Interrupts”. Wer kann mir bitte einen Tipp geben?

//-----------------------------------------------------------------------------
//  Taster-Steuerung 
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Die Pin's vom Bord erhalten einen Namen:
volatile int lampeLinks = 22;  
volatile int lampeRechts = 23;  
volatile int tasterLinks = 2;             
volatile int tasterRechts = 3;
volatile int tasterStop = 4;
volatile int tasterWarn = 5;
volatile int blinkZeitKurz = 4;


// Variablen und Konstanten
volatile int irqFlag = 0;                  // 1=display counters after IRQ; 0=do nothing
volatile int millisAnfang = 0;
volatile int dauer = 0; 


//--------------------------------------------------------------------------------
// Setup and Main:
void setup()
{
  Serial.begin(9600);                           // Initialisierung Interface mit 9600 Baud
 
  pinMode(22, OUTPUT);                      // Pin ist Ausgang - Lampe Links  
  pinMode(2, INPUT);                            // Pin ist Eingang - Taster Links
  pinMode(3, INPUT);                            // Pin ist Eingang - Taster Rechts
  pinMode(4, INPUT);                            // Pin ist Eingang - Taster Stop 
  pinMode(5, INPUT);                            // Pin ist Eingang - Taster Warn

  attachInterrupt(2, fTasterLinksH, RISING);     // Enable interrupt auf HIGH
  attachInterrupt(3, fTasterRechts, RISING);     // Enable interrupt
  attachInterrupt(4, fTasterStop, CHANGE);      // Enable interrupt
  attachInterrupt(5, fTasterWarn, CHANGE);      // Enable interrupt
}


//--------------------------------------------------------------------------------
void loop()
{
  digitalWrite(lampeLinks, LOW); 
  switch (irqFlag)
  {  
    case 1:
       // Taster Links ist HIGH 
       attachInterrupt(2, fTasterLinksL, FALLING);         // Taster Links auf fallend ein
                                                                                         // später .... detach ... 4 aus
       //Serial.println("Taste Links ist HIGH");               // nur zum testen 
       irqFlag=0;                                        
       break;
         
    case 2:
       // Taster Links ist LOW
       attachInterrupt(2, fTasterLinksH, RISING);          // Taster Links wieder auf steigend ein
       //Serial.println("Taste Links ist LOW");                // nur zum testen
       attachInterrupt(4, fTasterStop, CHANGE);            // Taster Stop wieder ein
       attachInterrupt(5, fTasterWarn, CHANGE);            // Taster Warn wieder ein
       if (dauer > 150)
       { 
          fTasteLinksLang();                               // Taste Links lang
          irqFlag=0;                                       // fertig
       } 
       else if (dauer > 40)
       {
          fTasteLinksKurz();                               // Taste Links Kurz
          irqFlag=0;                                       // fertig
       }
       else
       {
          irqFlag=1;                                       // Taste weder lang noch kurz (entprellen)
       }                                                   // Runde noch einmal 
       break;
  
    case 3:
       Serial.println("Taste Rechts");
       //delay(1000);
       irqFlag=0;
       break;
   
    case 4:
       Serial.println("Taste Stop");
       //delay(1000);
       irqFlag=0;
       break;
      
    case 5:   
       Serial.println("Taste Warn");
       //delay(1000); 
       irqFlag=0;
       break;
    default:   
       irqFlag=0;                          // Reset IRQ flag
  }
}


//--------------------------------------------------------------------------------
// Subs and Functions:


// Interrupt-Routine Taster Links ist nun HIGH 
void fTasterLinksH()
{   
  millisAnfang = millis();      // messen ob kurz oder lang ausgelöst wird 
  irqFlag = 1;                  // zurück nach loop mit 1
}
  
  
// Interrupt-Routine Taster Links ist nun wieder LOW 
void fTasterLinksL()
{ 
  dauer = millis() - millisAnfang;              // Ermittlung der "Drück"-dauer
  irqFlag = 2;                                  // zurück nach loop mit 2
}

  
// Interrupt-Routine Taster Rechts
void fTasterRechts()
{
  irqFlag=3;
}

// Interrupt-Routine Taster Stop
void fTasterStop()
{
  irqFlag=4;
}

// Interrupt-Routine Taster Warn
void fTasterWarn()
{
  irqFlag=5;
}


// Taste Links kurz gedrückt, x-mal blinken
void fTasteLinksKurz()
{
   Serial.println("Taste Links KURZ");      // nur zum testen  
   for (int x=0; x<blinkZeitKurz; x++)      // Blinkfrequenz StVZO 90 Impulse +- 30 / min  
   {                                                                   // 1,5 Hz +- 0,5 Hz
      digitalWrite(lampeLinks, HIGH);       // turn the LED on
      delay(750);                                           // wait 
      digitalWrite(lampeLinks, LOW);        // turn the LED off
      delay(750);                                                    // wait 
   }
}


// Tast Links lang gedrückt, endlos blinken
void fTasteLinksLang()
{
   Serial.println("Taste Links LANG");  
}

odiug:
Also habe ich einen Gedankenfehler im Umgang mit "Interrupts". Wer kann mir bitte einen Tipp geben?

Der Fehler ist Immer noch exakt derselbe wie in meinem Posting weiter oben beschrieben:

Du programmierst immer noch mit delay(), auch wenn das delay() nun in eine Unterfunktion gerutscht ist, und nach Abarbeitung des delays() löschst Du das irqFlag und setzt es auf 0 zurück, so dass nicht nur das Flag des gerade bearbeiteten Interrupts auf 0 gesetzt wird, sondern damit auch alle anderen während des delay() aufgetretenen Interrupts werden damit gelöscht (ohne jemals bearbeitet zu werden). Du machst delay() und nach dem delay() werden die währenddessen aufgetretenen Interrupts unbearbeitet gelöscht.

 else if (dauer > 40)
       {
          fTasteLinksKurz();    // in der Funktion machst Du Programmblockierung mit delay(), nur Interrupts laufen weiter
          irqFlag=0;            // und hier wird das Interrupt-Flag gelöscht, auch für alle während des delay() aufgetretenen Interrupts
       }

Aus Deinem heute geposteten Code meine ich zu erkennen, dass das ein Code für eine Fahrzeug-Blinkerschaltung werden soll.

Wenn ich mich richtig erinnere, habe ich hier im Forum schon mal Code für genau diesen Zweck gepostet:
Zwei Buttons "LinksButton" und "RechtsButton" schalten Ausgänge für Rechtsblinken, Linksblinken und Warnblinken.
Das Warnblinken in meinem Sketch wird aktiviert durch gleichzeitiges Drücken von LinksButton und RechtsButton.

Soll ich Dir den Sketch mal raussuchen?
Den Sketch könnte man sicher leicht umschreiben, wenn man einen dritten Extra-Button für das Warnblinken haben möchte.

Wenn das für einen Fahrzeug-Blinker ist, dann ist es extrem ungünstig, das Blinken mit delay() zu timen, so wie Du es machst:

     digitalWrite(lampeLinks, HIGH);       // turn the LED on
      delay(750);                                           // wait 
      digitalWrite(lampeLinks, LOW);        // turn the LED off
      delay(750);                                                    // wait

Das bedeutet nämlich: Du kannst diese delay()-Abfolge NICHT abbrechen, sobald sie gestartet wurde!

Mal angenommen der Fahrer setzt im Tran einen Blinker kurz falsch, er merkt es sofort und er schaltet mit einer Reaktionszeit von 0,3 Sekunden vom falsch gesetzten links blinken auf rechts blinken um.

Wenn Du die Blinkschaltung mit delay() machst, würde der Blinker nach dem Umschalten (selbst bei korrekter Auswertung) noch immer 1,2 Sekunden lang in die falsche Richtung blinken, obwohl der Fahrer schon längst den Knopf zum Umschalten der Blinkrichtung gedrückt hat. Das ist doch voll Panne!

Ich hatte verstanden, dasss man ein delay() nicht in der eigentlichen ISR (hier: fTasterLinksH undfTasterLinksL) - also in der Routine, die im attachInterrupt() steht - einsetzen soll und diese auch kurz halten soll. Dass ein delay() bei einem Sketch mit Interruptsteuerung ganz draussen vor bleiben muss, habe ich erst jetzt begriffen. Sorry und Merci !!!

Ja, Du hast es richtig erkannt, es ist hier eine Blinkersteuerung für ein Fahrzeug (Motorrad). Und genau für den Fall, den Du beschreibst (Blinken abbrechen), dachte ich mit Interrupts genau richtig zu liegen. Ich dachte als Interupt-Neuling auch delay() mit einem Interrupt abbrechen zu können. OK, habe ich jetz erst verstanden !!

Wenn Du so etwas schon einmal programmiert hast, dann würde ich gerne einen Blick riskieren.

Bei meinem Motorrad muss jedoch der Blinkvorgang mit einer Taste gestoppt werden. (Ich habe 4 Tasten: links, rechts, stop, warnblinken). Ich dachte mit einem kurzen Tastendruck ein 3-4x blinken zu erzeugen (Spurwechsel, Überholvorgang) und mit einem langen Tastendruck ein dauerblinken einleiten zu können bis entweder die Stoptaste oder ein Gyrosensor (abbiegen) den Vorgang wieder stoppt.
Druck beider Blinker-Tasten soll ein Relais (für Garagentor) schließen - Warnblinktaste habe ich ja. Die Stopptaste soll im "blinkfreien Modus"- also kein Blinker in Funktion - ein Display umschalten: Zeit, Aussentemperatur, Öltemperatur.
Mal sehen, wie es gelingt. Den Gyrosensor könnte man ja auch nach als Alarmanlage ...... aber jetzt will ich erst einmal sehen, dass ich das blinken und dessen Abbruch vernüftig hinbekomme. :~

odiug:
Ich hatte verstanden, dasss man ein delay() nicht in der eigentlichen ISR (hier: fTasterLinksH undfTasterLinksL) - also in der Routine, die im attachInterrupt() steht - einsetzen soll und diese auch kurz halten soll. Dass ein delay() bei einem Sketch mit Interruptsteuerung ganz draussen vor bleiben muss, habe ich erst jetzt begriffen. Sorry und Merci !!!

Ein delay() muß nicht bei jedem Programm außen vor bleiben. Insbesondere nicht-interaktive Programme, die vollkommen ohne Benutzereingriff vor sich hinwerkeln und nicht schnell reagieren müssen, können problemlos delay() verwenden. Aber für interaktive Programme, die auf menschliche Aktionen blitzschnell reagieren sollen, ist delay() das reinste Gift für einen flüssigen Programmablauf.

In Deinem Programm mit den vielen Interrupts kannst Du einen flüssigen Programmablauf nur realisieren, wenn sowohl die Tastenauswertung als auch das Blinken innerhalb einer Interrupt-Serviceroutine (ISR) realisiert wird - ohne mit delay() etwas zu schalten. Das ist im Prinzip möglich, da die Funktion digitalWrite() “safe” für die Verwendung innerhalb von Interrupts ist.

Es ist aber nicht notwendig.

Für ein interaktives Programm ohne Interrupts brauchst Du eigentlich nur darauf achten, dass die loop() Funktion mindestens hundert mal pro Sekunde durchläuft, dann ist die Reaktionszeit auf eine Interaktion niemals länger als 1/100 Sek., was für interaktive Anwendungen voll ausreichend ist.

Wenn die loop-Funktion sehr viel schneller abläuft, was in Programmen ohne delay() Verwendung bis weit über 40000 mal pro Sekunde der Fall sein kann, machst Du in der loop den Aufruf einer Funktion, sagen wir mal die nennt sich “tastenAuswertung()”.

Diese Funktion rufst Du hundertmal pro Sekunde auf, also immer nach 10 Millisekunden, und fragst den Tastenstatus ab. Wenn Du innerhalb der Funktion dann nur auf den Statusübergang von “Taste nicht gedrückt” zu “Taste gedrückt” mit einer Programmaktion reagierst, sind die Tasten automatisch softwaremäßig entprellt, denn zwischen zwei Funktionsaufrufen sind ja bereits 10 Millisekunden vergangen.

So kannst Du dann innerhalb von 10 Millisekunden auf jede Taste reagieren.
Und das ohne einen einzigen Interrupt.

odiug:
Ja, Du hast es richtig erkannt, es ist hier eine Blinkersteuerung für ein Fahrzeug (Motorrad). Und genau für den Fall, den Du beschreibst (Blinken abbrechen), dachte ich mit Interrupts genau richtig zu liegen. Ich dachte als Interupt-Neuling auch delay() mit einem Interrupt abbrechen zu können. OK, habe ich jetz erst verstanden !!

Wenn Du so etwas schon einmal programmiert hast, dann würde ich gerne einen Blick riskieren.

Ich hab’s wiedergefunden: Oh, oh, mein eigener Code. Zu kompliziert und zu wenig flexibel erweiterbar. Ich glaube, da bessere ich nochmal nach. Deine gewünschte Programmlogik ist auch nicht ganz berücksichtigt.

odiug:
Bei meinem Motorrad muss jedoch der Blinkvorgang mit einer Taste gestoppt werden. (Ich habe 4 Tasten: links, rechts, stop, warnblinken). Ich dachte mit einem kurzen Tastendruck ein 3-4x blinken zu erzeugen (Spurwechsel, Überholvorgang) und mit einem langen Tastendruck ein dauerblinken einleiten zu können bis entweder die Stoptaste oder ein Gyrosensor (abbiegen) den Vorgang wieder stoppt.
Druck beider Blinker-Tasten soll ein Relais (für Garagentor) schließen - Warnblinktaste habe ich ja. Die Stopptaste soll im “blinkfreien Modus”- also kein Blinker in Funktion - ein Display umschalten: Zeit, Aussentemperatur, Öltemperatur.

OK, also doch ein bisschen mehr als blinken.
Könnte man aber wohl so realisieren.
Und zwar ohne Verwendung von PinChange-Interrupts einfacher als mit.
Und natürlich ohne delay().

Einen kleinen logischen Fehler hast Du aber wohl drin: Man kann keine zwei Tasten wirklich “gleichzeitig” drücken. Wenn Du zwei Tasten “gleichzeitig” drückst, ist immer eine früher und eine später: Die “frühe” Taste schaltet dann auf normales Blinken zu einer Seite, erst die zweite Taste öffnet dann das Garagentor. Man kann es natürlich so programmieren, dass die Garagentorfunktion dann das Blinken wieder abschaltet. Aber trotzdem: In dem Sekundenbruchteil, der zwischen der Erkennung der ersten und der zweiten gedrückten Taste vergeht, blinkt es zu einer Seite. Wollte ich nur erwähnt haben.

Wenn Du etwas mit Interrupts machen möchtest, dann würde ich es eher mit einem Timer-Interrupt machen als mit PinChange-Interrupts. Einfach einen Timer 100 mal pro Sekunde laufen lassen, dabei

  • den aktuellen Tastenstatus im Timer-Interrupt setzen
  • auf geänderten Tastenstatus mit Änderungen im Blinkstatus reagieren
  • entsprechend des Blinkstatus und Zeitablaufs die Blinkerlampen ein/aus schalten
    Die Logik in Interrupts zu programmieren ist nur leider wenig anfängerfreundlich: Die Funktion “Serial.print()” ist nämlich nicht Interrupt-safe und das Programm kann sich aufhängen, wenn man sich in der ISR Debug-Meldungen auf Serial ausgeben lassen möchte.

Ich würde es vollkommen Interrupt-frei und Delay-frei machen.
Ich lasse mir mal was einfallen. Meinen Code vom letzten Jahr habe ich noch, auch wenn mir der aus heutiger Sicht zu ineffektiv und schlecht anpassbar erscheint, so dass ich daran nochmal nacharbeiten werde.

Hier mal Code für den “Luxus-Blinker mit 4 Tasten” zum Testen:

// Luxus-Blinker mit 4 Tasten by 'jurs' for German Arduino Forum

#define INPUTTYPE INPUT_PULLUP

#define DEBUG 1 // diese Zeile auskommentieren für Version ohne Serial-Debugcode

#define BUTTONLEFT 2   // digitaler Eingang linker Taster
#define BUTTONRIGHT 3  // digitaler Eingang rechter Taster
#define BUTTONSTOP 4  // digitaler Eingang Stopp-Taster
#define BUTTONWARN 5  // digitaler Eingang Warnblink-Taster

#define DEBOUNCETIME 10 // Zeit zum Entprellen in Millisekunden
#define LONGKEYPRESSTIME 250 // Zeit ab der ein Tastendruck "lang" ist

#define BLINKPINLEFT 22   // digitaler Ausgang linke Lampe
#define BLINKPINRIGHT 23  // digitaler Ausgang rechte Lampe
#define BLINKDELAY 500

void setup() 
{
#ifdef DEBUG  
  Serial.begin(9600);
#endif
  pinMode(BUTTONLEFT, INPUTTYPE);
  pinMode(BUTTONRIGHT, INPUTTYPE);
  pinMode(BUTTONSTOP, INPUTTYPE);
  pinMode(BUTTONWARN, INPUTTYPE);
  // Die beiden LED-Pins auf digitalen Output schalten
  pinMode(BLINKPINLEFT, OUTPUT);
  pinMode(BLINKPINRIGHT, OUTPUT);
}

void setCurrentKeystate(byte &state)
{
  state= state<<4; // Den alten Status um 4 Bit nach links schieben
  if (INPUTTYPE==INPUT_PULLUP) // vertauschte Logik bei Verwendung der internen PullUps
  {
    bitWrite(state,3,!digitalRead(BUTTONWARN)); // neuer Status Warn-Taste in Bit-3
    bitWrite(state,2,!digitalRead(BUTTONSTOP)); // neuer Status Stop-Taste in Bit-2
    bitWrite(state,1,!digitalRead(BUTTONLEFT)); // neuer Status linke Taste in Bit-1
    bitWrite(state,0,!digitalRead(BUTTONRIGHT));// neuer Status rechte Taste in Bit-0
  }
  else  // normale Logik bei Verwendung von PullDown Widerständen
  {
    bitWrite(state,3,digitalRead(BUTTONWARN)); // neuer Status Warn-Taste in Bit-3
    bitWrite(state,2,digitalRead(BUTTONSTOP)); // neuer Status Stop-Taste in Bit-2
    bitWrite(state,1,digitalRead(BUTTONLEFT)); // neuer Status linke Taste in Bit-1
    bitWrite(state,0,digitalRead(BUTTONRIGHT));// neuer Status rechte Taste in Bit-0
  }
}

void handleStateChange(byte keystate,byte &blinkstate, unsigned long &startBlinkMillis, byte &shortButtonPress)
{
  switch (keystate)
  {
    case 0b00000010:      // Übergang 0000 nach 0010
      blinkstate=0b10;    // Blinker Links setzen
      startBlinkMillis=millis();
      shortButtonPress=false; // Flag für kurzen Tastendruck löschen
      break;
    case 0b00000001:      // Übergang 0000 nach 0001
      blinkstate=0b01;    // Blinker Rechts setzen
      startBlinkMillis=millis();
      shortButtonPress=false; // Flag für kurzen Tastendruck löschen
      break;
    case 0b00000100:      // Übergang 0000 nach 0100
      blinkstate=0b00;    // Blinker Stopp
      shortButtonPress=false; // Flag für kurzen Tastendruck löschen
      break;
    case 0b00001000:      // Übergang 0000 nach 1000
      blinkstate=0b11;    // Warnblinker setzen
      startBlinkMillis=millis();
      shortButtonPress=false; // Flag für kurzen Tastendruck löschen
      break;
  }
  // Sonderfall Garagentoröffner wenn Links/Rechts gedrückt, 
  // aber vorher nicht beide Tasten schon gedrückt waren
  // Übergang von "nicht beide gedrückt" auf "beide gedrückt"
  if ((keystate & 0b11) == 0b11 && (keystate & 0b00110000) < 0b00110000)
    blinkstate=0b100; // Bit-2 für Garagetoröffner setzen, nicht blinken
  // Sonderfall kurzer Tastendruck beim Links- oder Rechtsblinken
  if (keystate==0 && millis()-startBlinkMillis<LONGKEYPRESSTIME && (blinkstate==0b10 || blinkstate==0b01))
    shortButtonPress=true;
}

void garagentorFunktion()
{
#ifdef DEBUG  
  Serial.println("Garagentorfunktion");
#endif
}

void handleBlinkState(byte keystate, byte &blinkstate, unsigned long blinkTime, byte &shortButtonPress)
{
  boolean blinkPhase, currentOn;
  if (bitRead(blinkstate,2)) // Garagentoröffner-Bit gesetzt?
  {
    garagentorFunktion();
    blinkstate=0; // Nach Ausführung der Garagentorfunktion blinkstate löschen
  }
  // Prüfen, ob nach Einschalten mit kurzem Buttondruck die Blinkdauer abgelaufen ist
  if (shortButtonPress && blinkTime>=5*BLINKDELAY && (blinkstate==0b01 || blinkstate==0b10))
    blinkstate=0b00; // falls ja, Blinken hier automatisch beenden
  if ((blinkTime/BLINKDELAY)%2==0) 
    blinkPhase=true;
  else
    blinkPhase=false;

  // Soll der linke Blinker gerade an sein?    
  currentOn=bitRead(blinkstate,1) && blinkPhase;
  if (digitalRead(BLINKPINLEFT)!=currentOn) 
  {
#ifdef DEBUG  
    if (currentOn) Serial.println("L");
    else Serial.println("l");
#endif
    digitalWrite( BLINKPINLEFT,currentOn);
  }  

  // Soll der rechte Blinker gerade an sein?    
  currentOn=bitRead(blinkstate,0) && blinkPhase;
  if (digitalRead(BLINKPINRIGHT)!=currentOn) 
  {
#ifdef DEBUG  
    if (currentOn) Serial.println("R");
    else Serial.println("r");
#endif    
    digitalWrite( BLINKPINRIGHT,currentOn);
  }  
}


void blinkTask()
{
  static byte keystate=0; // Tastenstatus: je 4 Bit letzter und aktueller Tastenstatus
  static byte blinkstate=0;  // Blinkstatus in den untersten 2 Bits
  static unsigned long lastTaskRun=0; // Zeit wann der Task zum letzten mal gelaufen ist
  static unsigned long startBlinkMillis=0; // Zeitpunkt beim Setzen des Blinkers
  static boolean shortButtonPress=false; // Wird true sobald ein kurzer Tastendruck erkannt wurde
  if (millis()-lastTaskRun<DEBOUNCETIME) return; // Task soll nur einmal pro DEBOUNCETIME laufen
  lastTaskRun=millis();
  setCurrentKeystate(keystate);
#ifdef DEBUG  
//  Serial.println(keystate,BIN);
#endif
  handleStateChange(keystate,blinkstate, startBlinkMillis, shortButtonPress);
  handleBlinkState(keystate,blinkstate, millis()-startBlinkMillis, shortButtonPress);
}

void loop()
{
  blinkTask();
}

Ich habe den Code komplett ohne dranhängende Schaltung getestet, daher ist serieller Debug-Code enthalten, den man aber durch Auskommentieren einer Zeile entfernen kann.

Zum Verständnis: Im Endeffekt werden alle Funktionen durch zwei Bytes im RAM-Speicher gesteuert, und zwar:
static byte keystate=0; // Tastenstatus: je 4 Bit letzter und aktueller Tastenstatus
static byte blinkstate=0; // Blinkstatus in den untersten 2 Bits

Jedes Byte hat 8 Bits, davon werden die untersten 4 Bits jeweils beim Abfragen des Tastenstatus gesetzt:
keystate Bit-0 entspricht der Taste zum Rechtsblinken
keystate Bit-1 entspricht der Taste zum Linksblinken
keystate Bit-2 entspricht der Stop-Taste
keystate Bit-3 entspricht der Warnblink-Taste
Im nächsten Durchlauf werden diese 4 Bits um 4 Stellen nach links geschoben:
keystate Bit-4 entspricht der Taste zum Rechtsblinken bei der vorherigen Abfrage
keystate Bit-5 entspricht der Taste zum Linksblinken bei der vorherigen Abfrage
keystate Bit-6 entspricht der Stop-Taste bei der vorherigen Abfrage
keystate Bit-7 entspricht der Warnblink-Taste bei der vorherigen Abfrage
Und die Bits-0 bis -3 wieder auf den aktuellen Status der Taste gesetzt.

Also beim Programmstart ist
keystate= 0b00000000; // Alle Tasten im letzten Durchlauf ungedrückt und jetzt auch
Dann werde die Taste zum Rechtsblinken gedrückt, dann ändert sich der keystate auf
keystate= 0b00000001; // Alle Tasten im letzten Durchlauf ungedrückt, jetzt ist die Rechtsblinken-Taste gedrückt
Wenn diese Taste weiter gedrückt bleibt, ändert sich keystate auf
keystate= 0b00010001; // Rechtsblinken-Taste im letzten Durchlauf und jetzt auch gedrückt
Immer die “linken” vier Bits für den letzten Zustand, die “rechten” vier Bits für den aktuellen Tastenzustand

Und nun braucht man den Blinkstatus “blinkstate” nur passend zu ändern, wenn sich der Tastenstatus ändert.
Reagiert wird dabei immer auf das Drücken der Tasten.
Also wenn sich der Tastenstatus von 0000 auf 0001 ändert, bedeutet es: Jetzt soll rechts geblinkt werden.
Dies wird dargestellt durch blinkstate=0b01;
Wenn sich der Tastenstatus von 0000 auf 0010 ändert, bedeutet es: Jetzt links blinken: blinkstate=0b10;
Wenn sich der Tastenstatus von 0000 auf 0100 ändert, bedeutet es: Stop mit Blinken: blinkstate=0b00;
Und wenn sich der Tastenstatus von 0000 auf 1000 ändert: Warnblinken, blinkstate=0b11;
Fast alle anderen Statusänderungen kann man unbeachtet lassen, insbesondere wenn Tasten losgelassen werden oder wenn der Tastenstatus unverändert bleibt.

Die Garagentoröffnerfunktion ist ebenfalls eingebaut, für diese habe ich das dritte Bit (Bit-2) im blinkstate vorgesehen.

Und last but not least ist auch die Funktion vorgesehen, dass wenn die Taste, mit der ein Blinkvorgang gestartet wurde, nur ganz kurz gedrückt wird, blinkt es nur dreimal und dann schaltet die Blinkfunktion sich selbst wieder ab.

Mangels Hardwareaufbau konnte ich es nicht besonders ausführlich testen, aber so sollte es mit Deinen Angaben passen,
Und zwar ohne einen einzigen Interrupt zu verwenden und ohne ein einziges delay() im ganzen Programm. Damit ich Tastendrücke mit einem Stück Draht zwischen GND und Buttonpin simulieren kann, habe ich die internen Pullups verwendet. Wenn Du externe PullDown-Widerstände an den Buttons angeschlossen hast, mußt Du den INPUTTYPE im Programm ändern auf:
#define INPUTTYPE INPUT

Wow, jurs, ich bin geplättet ....
Also erst einmal vielen, vielen Dank. Ich habe das Ganze so direkt noch nicht verinnerlicht, da ich nicht so der Byte-Schieber bin. Ich werde leider erst am Samstag oder Sonntag die Möglichkeit haben in "Byte-Exerzitien" gehen zu können. Dann versuche ich die Wege des Meisters zu ergründen :wink:

odiug:
Wow, jurs, ich bin geplättet ....

Kleinigkeiten werden sofort erledigt. Nur Wunder dauern etwas länger. :smiley:

odiug:
Also erst einmal vielen, vielen Dank. Ich habe das Ganze so direkt noch nicht verinnerlicht, da ich nicht so der Byte-Schieber bin. Ich werde leider erst am Samstag oder Sonntag die Möglichkeit haben in "Byte-Exerzitien" gehen zu können. Dann versuche ich die Wege des Meisters zu ergründen :wink:

Ja, die Bitschubsereien in den zwei Statusbytes sind für Anfänger vielleicht ein bisschen gewöhnungsbedürftig. Man sollte sich nur klarmachen, dass ein Byte aus 8 Bits besteht, die man für sich als "0" und "1" darstellen kann, und zwar auch im Quellcode als Binärdarstellung:
byte b1= 255; // Dezimaldarstellung eines Byte
und
byte b1= 0b11111111; // Binärdarstellung eines Byte
bedeuten eben dasselbe, nur eben einmal als Dezimaldarstellung und einmal als Binärdarstellung. Im Quellcode habe ich möglichst immer die Binärdarstellung gewählt, damit man sehen kann, welches Bit in einer Statusvariablen jeweils gesetzt oder nicht gesetzt ist.

Die Zählung der Bits sollte dabei klar sein, ganz rechts steht immer das niederwertigste Bit mit der Bitnummer-0, die Bitnummern werden von rechts nach links hochgezählt:
Variable= 0b00000000;
Bit-Nr.: 76543210

Um die Bitschubsereien und Aktionen im Programm vollständig zu verstehen, wäre es sinnvoll, sich auch über den Bitoperator einfaches Kaufmanns-Und "&" für "bitweise Verknüpfung" und die "logische Verknüpfung" mit dem doppelten Kaufmann-Und "&&" klarzuwerden, und was diese jeweils bedeuten.

Wenn Du Fragen hast, einfach fragen!

Wie mir gerade auffällt, steckt in dem geposteten Code auch noch eine kleine Macke: Wenn man es drauf anlegt, könnte man jede Blinkerlampe auch auf "ständig leuchtend" schalten. Vielleicht findest Du anhand des Codes oder beim Ausprobieren heraus, wie das möglich ist und was man machen muß, damit der Blinker dauernd leuchtet statt zu blinken oder aus zu sein.

Außerdem sehe ich gerade noch Deine Anforderung der Displayumschaltung mit der Stoptaste, die ich in dem geposteten Code noch nicht berücksichtigt habe.

Aufgrund der Programmstruktur ist aber beides leicht änderbar, also "unerwünschte Dauerlicht-Schaltmöglichkeit raus" und "Displayumschaltung rein" in den Code, ohne großen Änderungsaufwand am Code. Dazu könnte ich Dir dann noch Anregungen geben, wenn Du es nicht siehst, wo man im Code ansetzen müßte.

Aber probier ruhig erstmal mit Deiner Schaltung aus, wie das "look-and-feel" mit dieser Blink-Firmware ist!

Übrigens: Im Notfall und wenn Du noch was ans Programm dranstrickst und doch wieder delay() verwendest (wovon ich dringend abrate), könnte man auch meinen Code auf Interrupt-Auswertung von Tastendrücken umstellen bzw. erweitern. Allerdings wäre es dann so, dass dann zwar keine Tastendrücke verlorengehen, aber dass dann um das verwendete Delay verzögert auf den Tastendruck reagiert wird. Also wenn Du irgendwo delay(1000) einbaust, würde die Schaltung bis zu 1 Sekunde Reaktionszeit benötigen, bis sich ein gesetzter Blinkstatus nach einem Tastendruck ändert. Also lieber komplett ohne delay() programmieren, dann werden keine Interrupts benötigt und es wird (so gut wie) verzögerungsfrei auf Tastendrücke reagiert.

Vielen Dank für die nähere Darstellung. Ein paar Dinge davon waren mir bekannt ... 1 Byte = 8 Bit, von rechts zählen ... aber ich habe es halt noch nie angewendet und bin sehr froh, dass ich hier nun die Gelegenheit bekomme, diese Lücken zu füllen.
Konnte es nicht abwarten und habe gerade mal das Board angeschlossen (Im Büro :astonished:) und geladen. Ne, funzt nicht, Rechte LED ist Dauerlicht und der Monitor meldet Garagentorfunktion und 1. Hier habe ich nun keine Gelegenheit mich dran zu machen, am Wochenende wird es hoffentlich passen .....

odiug:
Rechte LED ist Dauerlicht und der Monitor meldet Garagentorfunktion und 1. Hier habe ich nun keine Gelegenheit mich dran zu machen, am Wochenende wird es hoffentlich passen …

Thank god it’s friday!

Die Fehlfunktion könnte durch einen falsch gesetzten pinMode für die Buttons bedingt sein.

Grundsätzlich können Buttons ja auf zwei Arten angeschlossen werden:

Button mit PullDown-Widerstand ==> LOW bei nicht gedrücktem Button, HIGH bei gedrücktem Button
Button mit PullUp-Widerstand ==> HIGH bei nicht gedrücktem Button, LOW bei gedrücktem Button

Mein Sketch ist auf beide Anschlussfälle vorbereitet, aber die Definition von INPUTTYPE muß ggf. geändert werden, damit die Schaltlogik stimmt.

Für Vewendung von PullUp-Widerständen beim Anschließen der Buttons:
#define INPUTTYPE INPUT_PULLUP

Für Vewendung von PullDown-Widerständen beim Anschließen der Buttons:
#define INPUTTYPE INPUT

Ich schätze mal, Du verwendest PullDown-Widerstände und hast es im Quellcode nicht geändert.

In dem Fall erkennt das Programm nämlich alle Buttonzustände genau vertauscht, d.h. bei unbetätigten Buttons werden alle Buttons als gleichzeitig gedrückt erkannt, was bedeutet, dass auch der Links- und der Rechtsbutton geleichzeitig als gedrückt erkannt werden, was dann sofort beim Programmstart die Garagentoröffnerfunktion auslöst. Ändere also auf:
#define INPUTTYPE INPUT
wenn Deine Buttons tatsächlich mit PullDown-Widerständen beschaltet sind.

Ja ich verwende Pull-Down Widerstände. Hatte dies dann aber auch im Sketch geändert und siehe da, die Garagentormeldung blieb aus. Die Rechte Lampe ist aber immer noch im Betrieb (egal welche Taster) . Kann mich aber im Büro nicht voll auf das Thema stürzen ... Der Monitor lässt auf jeden Fall nun die 0 schön durchlaufen.

odiug:
Der Monitor lässt auf jeden Fall nun die 0 schön durchlaufen.

Wenn Du den Sketch in Betrieb nimmst, gehst Du am besten so vor:

Zunächst mal von der aukommentierten Zeile die Kommentarstriche entfernen, also statt:
// Serial.println(keystate,BIN);
setze:
Serial.println(keystate,BIN);

Dann den Sketch mit dem seriellen Monitor laufen lassen und sehen, welche Werte durchlaufen.

Falls kein Button gedrückt, sollten nur einzelne Nullen durchlaufen.
0
0
0
0

Falls BUTTONLEFT gedrückt ist, sollten sich die durchlaufenden Werte ändern in:
100010
100010
100010
100010

Falls BUTTONRIGHT gedrückt ist, sollten sich die durchlaufenden Werte ändern in:
10001
10001
10001
10001

Falls BUTTONSTOP gedrückt ist:
1000100
1000100
1000100
1000100

Falls BUTTONWARN gedrückt ist:
10001000
10001000
10001000
10001000

Wenn das nicht in Ordnung ist: Verkabelung der Buttons prüfen!
Wenn es in Ordnung ist: Die Kommentarstriche wieder vor die Zeile setzen, Sketch neu kompilieren und hochladen.

Ja, die Printfunktion habe ich zur Zeit aktiviert. Die 0 läuft auch durch und die Tasten werden auch richtig gemeldet.

Das Prinzip 4 Bits nach links schieben und dann die die 4 rechten Bits neu einlesen (setCurrent Keystate) auch klar. Dann gehts ab in die handleStateChange-Routine und mal schauen welches Bit sich geändert hat .. Soweit habe ich dies schon verstanden.
ShortButtonPress muss ich noch einmal nachvollziehen, da hat es noch nicht klick gemacht ...

Aber: Wenn ich das Bord einschalte / resette, dann bltzt kurz die linke LED auf und die recht geht auf Dauerlicht. Der Monitor behinnt mit einer 0, dann kommt eine 1 und dann läuft er mit 0 endlos weiter und meldet eventuell gedrückte Tasten wie beschrieben.
Wenn ich richtig verstande habe, bedeutet die ausgegebene 1, dass das erste - also rechte Bit - beim zweiten Durchlauf (erst 0 dann 1) den Wert 1 erhält. Wert 1 für Bit 0 bedeutet rechtet LED ein.

Soweit bin ich schon mal. Morgen werde ich mich mal ganz intensiv damit beschäftigen und den Ablauf verlangsamen und an diversen Stellen Monitorausgaben einbauen, damit der Ablauf deutlicher wird ...

Macht echt Spass und ich danke Dir recht herzlich für Deine "Betreuung".

odiug:
Das Prinzip 4 Bits nach links schieben und dann die die 4 rechten Bits neu einlesen (setCurrent Keystate) auch klar. Dann gehts ab in die handleStateChange-Routine und mal schauen welches Bit sich geändert hat … Soweit habe ich dies schon verstanden.
ShortButtonPress muss ich noch einmal nachvollziehen, da hat es noch nicht klick gemacht …

Das mit dem kurzen Tastendruck funktioniert im Prinzip so:
Die Blinkfunktion fängt beim Drücken des Buttons sofort an zu blinken und merkt sich die Zeit, zu dem die Blinkfunktion eingeschaltet wurde. Dadurch braucht das Ende eines Tastendrucks gar nicht abgewartet werden, also ob es ein kurzer oder langer Tastendruck ist, es blinkt einfach beim Drücken der Taste sofort los.
Erst während des Blinkens wird dann regelmäßig nachgeschaut, ob die Taste, die für das Starten des Blinkens verantwortlich war, innerhalb einer bestimmten Frist nach Blinkbeginn wieder losgelassen wurde. Wenn das der Fall ist, wird das Blinken nach einer bestimmten Blinkdauer automatisch beendet.

odiug:
Soweit bin ich schon mal. Morgen werde ich mich mal ganz intensiv damit beschäftigen und den Ablauf verlangsamen und an diversen Stellen Monitorausgaben einbauen, damit der Ablauf deutlicher wird …

Das mit der “1” am Anfang des Sketches ist merkwürdig.
Vielleicht solltest Du als letzte Zeile in der setup() Funktion noch ein kurzes delay() einfügen, also

void setup
{

delay(25); // letzte Zeile im setup
}

Ansonsten brauchst Du, wenn die Tastenfunktion wie gewünscht funktioniert, nur diese Zeile wieder auskommentieren, indem Du die Kommentarstriche wieder davorsetzt und den Sketch dann hochlädst:
// Serial.println(keystate,BIN);

Dann sollten nur noch die Blinkaktionen als Debug-Meldungen erscheinen, dabei gilt:
r - rechte Blinkerlampe aus
R- rechte Blinkerlampe an
l - linke Blinkerlampe aus
L - linke Blinkerlampe an

Das kann man eigentlich per Auge nachverfolgen, ggf. die Blinkdauer etwas verlängern.
Und zum Anhalten der Ausgabe einfach zeitweise das “automatisch Scrollen” im seriellen Monitor abhaken.

Ich selbst habe den Sketch nur mit dem seriellen Monitor getestet, nicht mit Blinklampen.
Wenn sich tatsächlich eine Diskrepanz zeigt zwischen den mit r,R,l,L angezeigten Blinkaktionen und der Lampenschaltung, müßte ich mal selbst probeweise LEDs dranhängen und nachsehen, ob da noch ein Kinken drin ist.

Wenn Du die Debug-Ausgaben für den “keystate” mal auskommentiert hast, siehst Du dann die Blinkaktionen, z.B. für Linksblinken dann so im seriellen Monitor:
L
l
L
l
L
l
oder Rechtsblinken so
R
r
R
r
R
r
oder Warnblinken so
L
R
l
r
L
R
l
r
etc. und das ganze im Takt der Blinkfrequenz fortlaufend?

Also ein delay() im setup hat nichts bewirkt. Ich hatte es sogar auf 1250 gesetzt ... egal, nur mal am Rande!

Habe die von Dir genannte Zeile auskommentiert und die Tasten probiert. Die Linke funktioniert wie dargestellt l, L, l, L ....
Bei der rechten Taste kommen nur RRRRRRRRRR... und zwar rasend schnell. Bei der Warntaste kommt L, l, RRRRRRRRRRRRRR, L,l, ...
Irgendwas ist bei rechter Taste also noch nicht ok .... Habe jetzt ein halbes Stündchen Zeit, mal sehen ob ich dahinter komme.

odiug:
Irgendwas ist bei rechter Taste also noch nicht ok

Hast Du die Schaltung erstmal auf einem Steckbrett aufgebaut?
Oder verwendest Du gleich die Schalter am Motorrad?

Prüfe mal den PullDown-Widerstand (Widerstandswert, Anschluss, Spannungspegel) am rechten Taster!
Der rechte Taster ist bestimmt nicht sauber auf LOW, wenn der gleich am Start als HIGH ausgewertet wird!