Für eine Ampel wird weder ein Delay noch ein Interrupt benötigt.
Der Ablauf hat ja ein festes Schema, somit würde ich das Ganze in einzelne Zustände aufteilen.
Jeder einzelne Zustand hat feste Zeiten, die einzige Ausnahme ist die Grünphase der Verkehrsampel.
Wir erstellen uns also erst einmal ein paar #defines um jedem Zustand einen Namen zu geben (erhöht die Lesbarkeit des Codes).
Zu der Zuständen im Einzelnen und deren Anzahl komme ich später noch.
Wichtig: Die Zustände aufsteigend durchnummerieren.
Dann brauchen wir noch ein paar Arrays. Die Größe jedes Array entspricht der Anzahl der Zustände.
Wir haben fünf Lampen (Rot/Gelb/Grün Verkehr, Rot/Grün Fußgänger), also fünf Arrays für den Status (HIGH/LOW) der LED-Pins.
Dann brauchen wir noch ein Array in dem die Ablaufdauer jedes Zustandes gespeichert wird.
Dann benötigen wir noch zwei Variablen:
Ein int, um den aktuellen Zustand zu speichern.
Ein unsigned long, dort wird abgelegt, wann der aktuelle Zustand begonnen hat.
Legen wir nun die Zustände und den möglichen Ablauf fest:
Zustand 1 (Z_GRUEN_ROT): Verkehrsampel ist Grün, Fußgängerampel ist Rot (Zeit: variabel)
Zustand 2 (Z_GRUEN_ROT_TASTE): Verkehrsampel ist Grün, Fußgängerampel ist Rot, Taster wurde gedrückt (Zeit: fest)
Zustand 3 (Z_GELB_ROT): Verkehrsampel ist Gelb, Fußgängerampel ist Rot (Zeit: fest)
Zustand 4 (Z_ROT_ROT_A): Verkehrsampel ist Rot, Fußgängerampel ist Rot (Zeit: fest)
Zustand 5 (Z_ROT_GRUEN): Verkehrsampel ist Rot, Fußgängerampel ist Grün (Zeit: fest)
Zustand 6 (Z_ROT_ROT_E): Verkehrsampel ist Rot, Fußgängerampel ist Rot (Zeit: fest)
Zustand 7 (Z_ROTGELB_ROT): Verkehrsampel ist Rot/Gelb, Fußgängerampel ist Rot (Zeit: fest)
Innerhalb von setup() wird die Zustandsvariable auf Z_GRUEN_ROT gesetzt. Die Zeitvariable ist erst mal egal, da Z_GRUEN_ROT ja keine feste Zeitvorgabe hat.
Anhand der Werte für Z_GRUEN_ROT in den Lampen-Arrays werden die Pins gesetzt.
Innerhalb von loop() kommt nur eine if-else Abfrage ob der aktuelle Zustand Z_GRUEN_ROT ist. Die ganze Funktionalität spielt sich entweder im if oder im else Block ab. Danach ist loop() auch schon zuende und beginnt von vorne.
Solange Z_GRUEN_ROT aktuell ist, trifft die Abfrage zu.
Innerhalb der if-Schleife wird nun der Taster abgefragt.
Ist er nicht gedrückt, wars das auch schon. if wird verlassen, loop() beginnt von vorne.
Ist der Taster gedrückt, wird der aktuelle Zustand auf Z_GRUEN_ROT_TASTE gesetzt. In der Zeitvariable wird der aktuelle Wert von millis() gespeichert. Das wars, if Schleife verlassen, loop() beginnt von vorne.
Nun wird es spannend:
Da der aktuelle Zustand ja nun nicht mehr Z_GRUEN_ROT ist, wird in den else-Teil der if-Abfrage gesprungen.
Dort wird zuerst die Ablaufdauer des aktuellen Zustands (egal welcher das ist) aus dem Array ausgelesen. Solange der Wert der Zeitvariable + Ablaufdauer größer als millis() ist, ist die Zeit für den aktuellen Zustand noch nicht abgelaufen. Es gibt also nichts zu tun, else wird verlassen, loop() beginnt von vorne.
Ist die Ablaufzeit erreicht, so wird in den nächsten Zustand übergegangen. Da die Abfolge immer gleich ist, kann man die Zustandsvariable einfach um eins hochzählen. Ist der Wert > Z_ROTGELB_ROT wird er auf Z_GRUEN_ROT gesetzt.
Anhand der Werte für den aktuellen Zustand in den Lampen-Arrays werden wieder die Pins gesetzt.
In der Zeitvariable wird wieder der aktuelle Wert von millis() gespeichert.
Das wars, else verlassen, loop() beginnt von vorne.
Zur Info: Der Zustand Z_GRUEN_ROT_TASTE wird benötigt, da ja nach dem drücken der Taste nicht sofort eine Ampelschaltung erfolgt.
Was noch eine sinnvolle Erweiterung wäre:
Den Zeitpunkt der letzten Ampelschaltung speichern. Wenn die Fußgängerampel erst vor kurzem Grün war, sollte die Pause nach dem Drücken der Taste länger sein.
Ich bin mir jetzt nicht sicher ob ich jetzt alles bedacht habe und der Ablauf so einwandfrei funktioniert. Aber vom Ansatz sollte es passen.