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