Arduino volatile Variable und Funktionsaufruf

Hallo zusammen.
eine Baustellenampel soll beim Druck der Taste 1 auf der Fernbedienung zwei Ampeln im festen Rhythmus schalten, Taste 3 soll auf gelbes Blinklicht umschalten.
Der Empfänger der Fernbedienung hängt an Pin 2 und unterbricht das Programm, wenn eine beliebige Taste auf der Fernbedienung gedrückt wird.
Nun zum Problem:
Nach einer unterschiedlichen Anzahl von Durchläufen der Funktionen werden die Funktionen nicht mehr ausgeführt.
Hat jemand von euch dazu eine Idee?
Kann es etwas mit den delays zu tun haben?
Danke
Hartmut

#include "IRremote.hpp"

// Pin für den Auslöser des Interrupts
int InterruptPin = 2;

enum LED 
{
  ROT = 5,
  GELB,
  GRUEN,
  ROT1,
  GELB1,
  GRUEN1
};

volatile bool Status = true;

void setup() 
{
  // Empfänger starten
  IrReceiver.begin(InterruptPin);

  // pinMode LEDs
  for (int i = ROT; i <= GRUEN1; i++) 
  {
    pinMode(i, OUTPUT);
  }

  // Funktion Schalten() dem Interrupt-Pin zuordnen
  attachInterrupt(digitalPinToInterrupt(InterruptPin), Schalten, FALLING);
  Serial.begin(9600);
}

void loop() 
{
  // Daten lesen
  if (IrReceiver.decodedIRData.address == 0) 
  {
    if (IrReceiver.decode()) 
    {
      delay(200);

      // nächsten Wert lesen
      IrReceiver.resume();
      
      Status = true;
      /*
        solange der Status true ist, wird die jeweilige while-Schleife
        ausgeführt, ein weiterer Druck auf eine beliebige Taste 
        der Fernbedienung löst den Interrupt aus
        -> Status wird zu false, die while-Schleife wird nicht erneut
        ausgeführt
      */
    
      switch (IrReceiver.decodedIRData.command) 
      {
        // Taste 1: Ampel starten
        case 22:
          while (Status) AmpelStarten();
          alleLEDAus();
          break;

        // Taste 3:gelbes Blinklicht
        case 13:
          while (Status) GelbesBlinklicht();
          alleLEDAus();
          break;
      }
    }
  }
}

void Schalten() 
{
  Status = false;
}

void AmpelStarten() 
{  
  // wenn der Interrupt ausgelöst wurd .> Status = false
  // Funktion verlassen
  Serial.println(i);
  digitalWrite(GRUEN1, HIGH); 
  digitalWrite(ROT, HIGH);    
  if(!Status) return;
  delay(5000);                
  
  digitalWrite(GRUEN1, LOW);   
  digitalWrite(GELB1, HIGH);   
  if(!Status) return;
  delay(1000);                 
  
  digitalWrite(GELB1, LOW);   
  digitalWrite(ROT1, HIGH);    
  if(!Status) return;
  delay(2000);                
  
  digitalWrite(GELB, HIGH);   
  if(!Status) return;
  delay(1000);                 
  
  digitalWrite(ROT, LOW);     
  digitalWrite(GELB, LOW);     
  digitalWrite(GRUEN, HIGH);  
  if(!Status) return;
  delay(5000);               

  digitalWrite(GRUEN, LOW);  
  digitalWrite(GELB, HIGH);    
  if(!Status) return;

  delay(1000);                 
  digitalWrite(GELB, LOW);    
  digitalWrite(ROT, HIGH);  
  if(!Status) return;
  delay(2000);              

  digitalWrite(GELB1, HIGH);   
  if(!Status) return;
  delay(1000);                

  digitalWrite(ROT1, LOW);   
  digitalWrite(GELB1, LOW);   
  digitalWrite(GRUEN1, HIGH); 
}

void GelbesBlinklicht() 
{
  digitalWrite(GELB, HIGH);
  digitalWrite(GELB1, HIGH);
  if(!Status) return;
  delay(500);
  
  digitalWrite(GELB, LOW);
  digitalWrite(GELB1, LOW);
  if(!Status) return;
  delay(500);

  digitalWrite(GELB, HIGH);
  digitalWrite(GELB1, HIGH);
  if(!Status) return;
  delay(500);

  digitalWrite(GELB, LOW);
  digitalWrite(GELB1, LOW);
  if(!Status) return;
  delay(500);
}

void alleLEDAus() 
{
  for (int i = ROT; i <= GRUEN1; i++) 
  {
    digitalWrite(i, LOW);
  }
}

so was in der loop taugt nix.

Und delay + IR Empfänger auch nicht.

Lerne wie man eine Statemaschiene (Schrittkette) baut. Da brauchst du kein while und kein delay. Deinen Code wirfst du am besten komplett weg und fängst neu an. Ein Interrupt brauchst du auch nicht.

Du verwendest keinen Interrupt. Das ist gut!
Nur die Kommentare könnten andere verwirren.
( Merke: Falsche Kommentare sind schlechter als gar keine )

[Edit] falsch geguckt [/Edit]

while innerhalb der loop() ist schlecht, weil das loop unnötig kompliziert macht. Oder, wie bei dir, falsch. (Weil Status nicht mehr zurückgesetzt wird)

Ganz herzlichen Dank! Diese Belehrung hilft mir wirklich weiter!

Wieso antwortest du mir?

Weil du recht hast :slight_smile: , sorry. (war als kleine Ergänzung für wallerha gedacht. Ich habe dich zitiert, aber generell geantwortet, oder?)

Moin @wallerha ,

delays sind i.d.R. hinderlich, wenn es um schnelles Reagieren geht und insbesondere bei der Auswertung von Sensordaten, wie hier dem IR-Empfänger.

Hier ein Beispiel (aufbauend auf Deinem Sketch), das diese Hürden durch Einsatz der millis()-Funktion umgeht:

/*
  Forum: https://forum.arduino.cc/t/arduino-volatile-variable-und-funktionsaufruf/1369418
  Wokwi: https://wokwi.com/projects/427065070114070529

  ec2021
  2025/04/01

*/

#include "IRremote.h"

// Pin für den Auslöser des Interrupts
int InterruptPin = 2;

enum Modes {
  AMPEL, BLINKEN
};

Modes mode = BLINKEN;

enum States {
  aROT,
  aROTGELB,
  aGRUEN,
  aGELB
};

States ampel1State = aROT;

enum LED
{
  ROT1 = 5,
  GELB1,
  GRUEN1,
  ROT2,
  GELB2,
  GRUEN2
};


void setup()
{
  // Empfänger starten
  IrReceiver.begin(InterruptPin);

  // pinMode LEDs
  for (int i = ROT1; i <= GRUEN2; i++)
  {
    pinMode(i, OUTPUT);
  }
  Serial.begin(115200);
}

void loop()
{
  getIRCommand();
  stateMachine();
}


void setAmpel1(byte stateRot, byte stateGelb, byte stateGruen) {
  digitalWrite(ROT1, stateRot);
  digitalWrite(GELB1, stateGelb);
  digitalWrite(GRUEN1, stateGruen);
}

void setAmpel2(byte stateRot, byte stateGelb, byte stateGruen) {
  digitalWrite(ROT2, stateRot);
  digitalWrite(GELB2, stateGelb);
  digitalWrite(GRUEN2, stateGruen);
}

void allOff() {
  setAmpel1(LOW, LOW, LOW);
  setAmpel2(LOW, LOW, LOW);
}

void stateMachine() {
  switch (mode) {
    case AMPEL:
      ampelMachine(true);
      break;
    case BLINKEN:
      blinkMachine(true);
      break;
  }
}


void getIRCommand() {
  if (IrReceiver.decodedIRData.address == 0)
  {
    if (IrReceiver.decode())
    {
      int val = IrReceiver.decodedIRData.command;
      IrReceiver.resume();
      switch (val)
      {
        // Taste 1: Ampel starten
        case 48:  // statt 22
          if (mode != AMPEL) {
            allOff();
            ampelMachine(false);
            mode = AMPEL;
          }
          break;

        // Taste 3:gelbes Blinklicht
        case 122: // statt 13
          if (mode != BLINKEN) {
            allOff();
            blinkMachine(false);
            mode = BLINKEN;
          }
          break;
        default:
          Serial.println(val);
      }
    }
  }
}

void blinkMachine(boolean Run) {
  static unsigned long lastBlink = 0;
  static byte lastState = LOW;
  if (!Run) {
    lastBlink = 0;
    lastState = LOW;
    return;
  }
  if (millis() - lastBlink > 500) {
    lastBlink = millis();
    lastState = !lastState;
    setAmpel1(LOW, lastState, LOW);
    setAmpel2(LOW, lastState, LOW);
  }
};

boolean timeOver(unsigned long startTime, unsigned long wait) {
  return (millis() - startTime >= wait);
}

void ampelMachine(boolean Run) {
  static unsigned long lastChange = 0;
  if (!Run) {
    lastChange = 0;
    ampel1State = aROT;
    return;
  }
  switch (ampel1State) {
    case aROT:
      if (timeOver(lastChange, 5000)) {
        lastChange = millis();
        setAmpel1(HIGH, LOW, LOW);
        setAmpel2(LOW, LOW, HIGH);
        ampel1State = aROTGELB;
      }
      break;
    case aROTGELB:
      if (timeOver(lastChange, 5000)) {
        lastChange = millis();
        setAmpel1(HIGH, HIGH, LOW);
        setAmpel2(LOW, HIGH, LOW);
        ampel1State = aGRUEN;
      }
    case aGRUEN:
      if (timeOver(lastChange, 5000)) {
        lastChange = millis();
        setAmpel1(LOW, LOW, HIGH);
        setAmpel2(HIGH, LOW, LOW);
        ampel1State = aGELB;
      }
      break;
    case aGELB:
      if (timeOver(lastChange, 5000)) {
        lastChange = millis();
        setAmpel1(LOW, HIGH, LOW);
        setAmpel2(HIGH, HIGH, LOW);
        ampel1State = aROT;
      }
  }
};

Zum Ausprobieren auf Wokwi: https://wokwi.com/projects/427065070114070529

Es lässt sich auch in diesem Beispiel noch einiges verbessern, aber das Prinzip sollte erkennbar sein:

  • Trennung von Eingaben und der Reaktion darauf:
    • getIRCommand() behandelt die IR-Eingaben und setzt nur die Startbedingungen für die jeweiligen "Maschinen" ampelMachine() und blinkMachine()
  • blinkMachine() und ampelMachine() verwenden die millis()-Funktion zur zeitlichen Steuerung der Abläufe und blockieren damit die loop() - und somit das Empfangen/Decodieren der IR-Signale - nicht
  • Die Modi der "Maschine" lassen sich einfach erweitern; z.B. könnte man auch einen Modus "AUS" ergänzen, bei dem beide Ampeln ausgeschaltet bleiben (um nachts Strom zu sparen :wink: )

Um beim Umschalten der Modi AMPEL/BLINKEN die Startbedingungen setzen zu können, habe ich noch den Aufrufparameter "boolean Run" eingeführt. Damit werden die statischen Variablen der Maschinen definiert gesetzt.

Weiterhin viel Spass mit Arduino-Projekten!
ec2021

Super, Danke!