Kurze Verständnisfrage zu Variablen

Hallo miteinander,
ich versuche mich an folgendem Projekt: und zwar möchte ich eine Lichtsteuerung von zwei Treppengeländern realisieren. Dazu habe ich mir in eine Fräsnut in den Geländerbalken 12V LED-Bänder eingelassen. Gesteuert werden sollen diese über PIR-Sensoren und Helligkeitssensor und die LED-Bänder über eine 4-fach Relaiskarte. Ein Geländer besteht aus drei Einzelteilen und das zweite aus nur einem. Jetzt möchte ich folgendes: Bei dem Geländer mit den drei Einzelteilen (zwei PIR-Sensoren, einer oben und einer unten sowie drei “Lichtbalken”) sollen diese je nachdem aus welcher Richtung man kommt um eine Sekunde Verzögerung angehen und auch so wieder aus. Soweit kein Problem. Jetzt ist mein Problem, dass die andere Treppe (mit einem PIR-Sensor und einem “Lichtbalken”) unabhängig von der ersten agieren soll. Ich habe mir dazu noch ein DS3231 RTC gekauft um mir von dort die Zeit und die aktuellen Sekunden auszulesen und um mit diesen die Werte für die Schaltung der Balken zu errechnen. Funktioniert soweit auch, nur das mein Problem ist, dass sich ja die Variablen jede Sekunde verändern, somit funktioniert mein Programm nicht. Nun meine Frage, ist es möglich eine Art “Momentaufnahme” der Auslesevariable für die Sekunden zu erstellen um diese dann zum Berechnen zu benutzen und dann wieder mit der aktuellen Zeit zu vergleichen?

Auszug aus dem Programm (Hier für den einzelnen Balken der zweiten Treppe)

if (digitalRead(4)==HIGH)                       // Einlesen Status Eingang 4
  {
    int Sekunden_auslesen = Clock.getSecond();  // Aktuelle Sekunden von RTC
    Serial.println(Sekunden_auslesen);          // Ausgabe auf serielle Schnittstelle
    int Wartezeit = Sekunden_auslesen+60;       // Ausschaltverzögerung um eine Minute
    Serial.println(Interval);
    if (Sekunden_auslesen < Wartezeit)
     { 
    digitalWrite(BALKEN_4, LOW);
     }
     else
     {
    digitalWrite(BALKEN_4, HIGH);
     }
  }

BennoK:
ist es möglich eine Art “Momentaufnahme” der Auslesevariable für die Sekunden zu erstellen um diese dann zum Berechnen zu benutzen und dann wieder mit der aktuellen Zeit zu vergleichen?

Nimm doch einfach eine extra Variable als Vergleich, die Du nur änderst wenn Du es auch möchtest.

Hallo,

beschäftige dich mal mit lokalen und globalen Variablen. Du definierst lokale Variablen, die nur innerhalb der Klammern der IF-Abfrage gültig sind. Zum Zwischenspeichern brauchst du globale Variablen. Sie definiert man im Kopf des Programms und sind innerhalb des gesamten Sketch gültig und verlieren nicht ihren Wert, natürlich es sei denn ein neuer Wert wird ihnen zugewiesen. Bei einer Minute kann man auch problemlos mit den internen Timern des Arduinos arbeiten. Die RTC ist nur nötig, wenn man eine Zeitschaltuhr braucht, da die RTC auf mehrere Tage genauer geht und bei Stromausfall weiter läuft..

Hallo,
danke schon mal für die Antworten. Ich habe mich für den RTC entschieden, weil ich in Zukunft vorhabe das Programm noch um eine Zeitsteuerung zu erweitern. Und da dacht ich mir, kann ich die ausgegebene Uhrzeit gleich nutzen. Bin relativ neu was den Umgang mit dem Arduino angeht. Das mit der extra Variable als Vergleich erschließt sich mir noch nicht ganz…

BennoK:
ich versuche mich an folgendem Projekt

Soweit kein Problem.

Gratulation zur tollen Projektbeschreibung!
Endlich mal jemand, der genau verrät, was er vorhat und programmieren möchte, ohne einfach nur ein paar Zeilen verwurstelten Code zu zeigen, der irgendwie entwurstelt werden soll, ohne dass die Forenteilnehmer wissen, worum es eigentlich geht.

BennoK:
Nun meine Frage, ist es möglich eine Art “Momentaufnahme” der Auslesevariable für die Sekunden zu erstellen um diese dann zum Berechnen zu benutzen und dann wieder mit der aktuellen Zeit zu vergleichen?

Momentaufnahme ist gut. Dein Problem bietet sich geradezu für eine schulmäßige Abarbeitung an.

Damit meine ich, das Programm in drei Funktionsbereiche zu trennen, und zwar nach dem EVA-Prinzip:

  • Eingabe (Zustand der PIR-Sensoren auslesen und bei Änderungen einige Variablen anders setzen)
  • Verarbeitung (den derzeitigen logischen Schaltzustand der Lampen ermitteln)
  • Ausgabe (den ermittelten logischen Schaltzustand tatsächlich an den Lampen physikalisch schalten)

Codevorschlag für die loop-Funktion, wie man ein typisches Programm immer aufbauen kann:

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

D.h. verschiedene Programmbereiche haben verschiedene Aufgaben, die von verschiedenen Programmteilen bzw. Funktionen verarbeitet werden.

Das geht in Deinem kurzen Codeabschnitt schon mal wild durcheinander: Von Eingabe (digitalRead) über die Verarbeitung bis zur Ausgabe (digitalWrite) ist bei Dir alles in einer einzigen if-Abfrage untergebracht. Außer bei ganz trivialen Programmen geht das meist ziemlich in die Hose und führt zu extrem unübersichtlichem Programmcode, der manchmal gar nicht funktioniert und bei dem die eigentliche Programmlogik am Ende undurchschaubar und fehleranfällig wird.

Der Grund ist die fehlende Trennung von “logischem Schaltzustand” und “physikalischem Schaltzustand”. Wenn Du im Programm nämlich die Programmlogik von der Schaltlogik trennst, dann kannst Du einen Lichtbalken in Gedanken während der Verarbeitung x-mal an und ausschalten und zig Bedingungen berücksichtigen, die dafür zuständig sind. Und erst am Ende, wenn alle Bedingungen verarbeitet sind, wird die Ausgabefunktion aufgerufen, die nach dem vollständigen Verarbeitungsschritt den letztendlich gültigen logischen Schaltzustand an der Lampe tatsächlich schaltet. Das braucht nicht lange zu dauern, sondern kann zigtausendmal pro Sekunde durchlaufen.

Die Aufteilung des Programms nach dem EVA-Prinzip wäre das eine.

Das andere Ding ist ein übersichtlicher Code durch symbolische Konstanten. Denn Du hast diverse Dinge, die mit kleinen Zahlenkonstanten im Programm vorkommen:

  • Du hast zwei Treppen
  • Du hast vier PIR-Sensoren
  • Du hast vier Lichtbalken
    Wenn damit programmiert wird, entsteht eine Übersichtlichkeit durch symbolische Konstaten und diese scheinst Du bereits teilweise zu verwenden, wie ich an dem “BALKEN_4” im Code bereits sehe. Das müßte im Code konsequent durchgezogen werden, und zwar nicht nur für die Relais-Pins. Du verwendest “BALKEN_4” offenbar für den Relais-Pin, mit dem der vierte Lichtbalken physikalisch geschaltet wird.

Tatsächlich benötigt werden aber auch symbolische Konstanten, um nicht nur die physikalischen Pins damit zu benennen, sondern auch die logischen Schaltzustände, die während der Verarbeitung ermittelt/gesetzt werden. Geeignete Datenstrukturen für sein Programm zu verwenden ist genau so wichtig wie die Programmlogik. Wenn die grundlegenden Datenstrukturen Mist sind, kann die Programmlogik auch kein schönes Programm mehr daraus machen.

Ich würde mit symbolischen Konstanten und Arrrays arbeiten, also z.B. für die Balken:

enum {BALKEN_1A, BALKEN_1B, BALKEN_1C, BALKEN2};
#define NUMBALKEN 4
int outputPins[NUMBALKEN]={6,7,8,9};
boolean outputStatus[NUMBALKEN]={LOW,LOW,LOW,LOW};

Damit hast Du mit dem outputStatus-Array unabhängige Variablen für einen logischen Schaltzustand, den Du beliebig ändern kannst:

outputStatus[BALKEN2]=LOW;
if (dieses) outputStatus[BALKEN2]=HIGH;
if (jenes) outputStatus[BALKEN2]=LOW;
if (diesUNDdas) outputStatus[BALKEN2]=HIGH;

Und erst wenn alle möglichen Eventualitäten in der Schaltlogik berücksichtigt sind und der logische Schaltzustand feststeht, nachdem er sich möglicherweise mehrmals geändert hat, wenn verschiedene logische Bedingungen berücksichtigt haben, wird am Ende in der Ausgabefunktion an allen Ausgangspins der jeweils gültige logische Schaltzustand tatsächlich geschaltet:

void ausgabe()
{
  for (int i=0;i<NUMBALKEN;i++) 
    digitalWrite(outputPins[i],outputStatus[i]);
}

Falls das jetzt etwas viel Stoff war und nicht ganz verständlich, hier nochmal die Kurzfassung:

  • Trenne das Programm in logische Funktionseinheiten “Eingabe”, “Verarbeitung”, “Ausgabe”
  • Trenne für die Verarbeitung die logischen Schaltzustände von den physikalischen Schaltzuständen

Soll ich Dir mal ein Programmgrundgerüst zusammenbauen?

Hallo,
danke schon mal für deine ausführliche und verständliche Ausführung! Das ist ganz schön viel Input :astonished:
Wenn es für dich nicht zu viel Aufwand ist , wäre mir mit einem Grundgerüst zum Verständnis des schematischen Aufbaues doch sehr geholfen...

BennoK:
Wenn es für dich nicht zu viel Aufwand ist , wäre mir mit einem Grundgerüst zum Verständnis des schematischen Aufbaues doch sehr geholfen...

OK, ich werde mal schauen, wieviel Aufwand es ist und mir dazu ein paar Gedanken machen.

Hast Du schon Ideen, wie Du die kniffeligen Schaltfälle behandeln möchtest?
Du schreibst:

BennoK:
sollen diese je nachdem aus welcher Richtung man kommt um eine Sekunde Verzögerung angehen und auch so wieder aus.

Mal am konkreten Fall: Der Sensor unten an der Treppe löst aus, und eine halbe Sekunde später löst der Sensor oben an der Treppe aus. Da entstehen "Race-Conditions" (Wettlaufsituationen) zwischen verschiedenen Schaltfolgen, und es muß festgelegt sein, wie dann vernünftigerweise zu verfahren ist und welche Schaltbedingung die Oberhand behält.

In welcher Schaltfolge sollen dann die drei Lichtbalken-Segmente ein- und ausgeschaltet wenn "oben am PIR" die Schaltfolge an derselben Treppe eine halbe Sekunde später ausgelöst wird als "unten am PIR"? In dem Fall besteht ja die hohe Wahrscheinlichkeit, dass zwei verschiedene Personen das Treppenlicht gestartet haben.

In Frage kämen (mit x+Sekunden) bezeichne ich mal die Zeit
x Schalter unten betätigt, Lichtbalken 1 geht an
x+0,5 Schalter oben betätigt, Lichtbalken 3 geht an
x+1 Lichtbalken 2 geht an
x+ 60,5 Lichtbalken 3 geht aus
x+ 61,5 Lichtbalken 2 geht aus
x+62,5 Lichtbalken 1 geht aus

Andererseits würde eine Person, die die Treppe benutzt, immer auch am anderen Ende der Treppe den Sensor auslösen, wenn sie oben ankommt. Es sei denn, die Person löst den Sensor an einem Ende der Treppe aus, überlegt es sich anders, macht kehrt und benutzt die Treppe nicht.

Die einfachste Logik wäre wahrscheinlich: Solange eine Schaltfolge läuft, weitere Schaltbefehle ignorieren. So dass sich im oben genannten Fall ergibt:
x Schalter unten betätigt, Lichtbalken 1 geht an
x+0,5 Schalter oben betätigt, keine Auswirkungen
x+1 Lichtbalken 2 geht an
x+2 Lichtbalken 3 geht an
x+ 60 Lichtbalken 1 geht aus
x+61 Lichtbalken 2 geht aus
x+62 Lichtbalken 3 geht aus
Und erst dann wäre die Schaltung wieder "scharf" geschaltet und würde auf neue Schaltbefehle reagieren.

Diverse andere Reaktionsmöglichkeiten auf Race-Conditions sind denkbar. Das würde dann die Funktion "verarbeitung" im Detail festlegen, wie es genau abläuft.

Und wegen der möglichen Race-Conditions (zwei Schalter steuern eine Treppenbeleuchtung) ist eine in sich stimmige Programmlogik mit einer Trennung von logischem und physikalischem Schaltzustand wichtig. Eben weil es gar nicht so klar ist, wie auf das Betätigen eines Schalters reagiert werden soll, wenn noch Reaktionen auf das vorherige Betätigen desselben oder eines anderen Schalters anhängig sind.

Ich überlege mir mal was, kann aber einen Tag dauern.

Ich habe mir das so überlegt gehabt, dass wenn bereits einer der beiden Sensoren an der Treppe 1 geschalten hat, eine Art Sperre auslöst, wenn einer der Balken bereits an ist. So das der andere Sensor ignoriert wird. Die Treppe zwei mit dem einen Sensor und einem Balken kann aber trotzdem agieren. Ich dachte es so, dass wenn der Sensor oben auslöst, zuerst der oberste Balken angeht, dann eine Sekunde versetzt der mittlere und wieder eine Sekunde versetzt der untere. Und genau so sollen sie auch wieder nach einer Minute ausgehen. Wenn der untere Sensor auslöst soll erst der untere, dann der mittlere und dann der obere angehen, und im selben Schema wieder ausgehen. Der dritte Sensor schaltet unabhängig von den anderen einfach den vierten Balken für eine Minute an.
Hatte mir das am Anfang alles ein wenig leichter vorgestellt :~
Mein erstes Programm nur für die eine Treppe und drei Balken funktionierte mit delays auch recht gut. Aber mit der zweiten Treppe ist das ja nicht mehr möglich...

Hatte mir das am Anfang alles ein wenig leichter vorgestellt

Bei jeder Steuerungslogik steckt der Teufel im Detail ... :wink:

Mein erstes Programm nur für die eine Treppe und drei Balken funktionierte mit delays auch recht gut. Aber mit der zweiten Treppe ist das ja nicht mehr möglich...

Mehrere "Schalt-Programme" gleichzeitig geht, wenn man statt delay() millis() nimmt, und sich jeweils Zustand und Startzeit merkt.

Wenn die Treppen komplett unabhängig voneinander sind und du jurs' EVA magst, kannst du auch beliebig mehrere davon verwenden.

void loop() {
   Treppe1();
   Treppe2();
   Treppe3();
}
void Treppe1 () {
    Erfassung1();
    Verarbeitung1();
    Ausgabe1();
}
...

wobei ich es nicht soo unübersichtlich finde, in einem Schaltprogramm wie Treppe1()
die zwei digitalRead Aufrufe am Anfang gleich hinzuschreiben.

Solange eine Schaltfolge läuft, weitere Schaltbefehle ignorieren

Ich denke, die Schluss-Sequenz (x+60 ff) macht nur Sinn, wenn zwischenzeitlich keine neue Anforderung (von unten oder oben) kam ... :wink:

Vermutlich ist es einfacher, nachdem eine Zeitlang nichts mehr los war , das Licht auszumachen (auf- oder abwärts je nach letztem PIR -Signal)

Theseus:
Zum Zwischenspeichern brauchst du globale Variablen. Sie definiert man im Kopf des Programms und sind innerhalb des gesamten Sketch gültig und verlieren nicht ihren Wert, natürlich es sei denn ein neuer Wert wird ihnen zugewiesen.

Besser sind oft lokale static Variablen. Die verhalten sich wie globale Variablen was die Gültigkeit des Wertes betrifft, aber haben lokalen Scope.

Globale Variablen sollte man nicht inflationär einsetzen. Die braucht man meistens nur für Konstanten (dann ist es oft übersichtlicher wenn alle an einem Ort sind) und Variablen die wirklich in mehreren Funktionen sichtbar sein müssen.

michael_x:
Wenn die Treppen komplett unabhängig voneinander sind und du jurs’ EVA magst, kannst du auch beliebig mehrere davon verwenden.

OK, die Idee greife ich mal auf. Wenn man es richtig macht, ist es ja praktisch kein zusätzlicher Aufwand, ein Programm “für beliebig viele Treppenbeleuchtungen” zu schreiben im Vergleich zu einem Programm “für zwei Treppenbeleuchtungen”.

Ich packe dazu alle relevanten Variablen einer Treppe in ein “struct”, und dann können beliebig viele Treppen geschaltet werden, solange die Treppen mit 2 Schaltern geschaltet werden und entweder eine dreiteilige mit Zeitversatz geschaltete oder eine einteilige Beleuchtung haben. Die Datenstruktur für eine Treppe sieht dabei so aus:

struct dreiSegmentTreppe_t {
  byte schalterUntenPin; // Pin des Schalters unten an der Treppe
  byte schalterObenPin;  // Pin des Schalters oben an der Treppe
  byte schalterState;    // letzter Schaltzustand (in den beiden untersten Bits codiert
  byte geschaltet;    // NICHTGESCHALTET,UNTENGESCHALTET,OBENGESCHALTET}
  unsigned long startZeit;  // enthält die Zeit, wann das Licht eingeschaltet wurde
  byte lichtA_Pin;   // Pin für Lampe-A
  byte lichtB_Pin;   // Pin für Lampe-B (falls nur eine Lampe, identisch mit lichtA_Pin)
  byte lichtC_Pin;   // Pin für Lampe-C (falls nur eine Lampe, identisch mit lichtA_Pin)
};

Die relevanten Definitionen stehen hier für jede Treppe im Code als treppen-Array gespeichert:

// Ein Array mit 2 Elementen definieren und mit Werten belegen, entsprechend 2 Treppen
dreiSegmentTreppe_t treppen[]={
  {2,3,0b00,NICHTGESCHALTET,0,8,9,10},
  {4,5,0b00,NICHTGESCHALTET,0,11,11,11},
};

Die beiden ersten Zahlen in einer Zeile stehen für den Pin des unteren und oberen Lichtschalters.
Die letzten drei Zahlen in einer Zeile stehen für die Pins der angeschlossenen drei Lichtsegmente.
Wenn eine Treppe nur ein einziges Lichtsegment hat, einfach dreimal denselben Pin definieren.

Um den Code ohne angeschlossene Beleuchtung testen zu können, gibt es Debug-Ausgaben auf Serial.

Die Schalter müssen entweder per Push-Pull angesteuert werden oder es muß ein Taster mit PullDown-Widerstand angeschlossen sein. Für invertierte Logik mit Schaltern, die die internen PullUp-Widerstände des Atmegas nutzen, müßte der Code geringfügig geändert werden.

Die Anzahl der ansteuerbaren Treppen ist nur durch die Zahl der freien I/O-Pins begrenzt. Im Sketch selbst wird die Programmlogik jeweils in einer Schleife abgearbeitet, mit der Treppennummer als Schleifenindex.

  for(int i=0;i<anzahlTreppen;i++)
  {
  // Hier der Code pro Treppe
  }

Der vollständige Sketch:

#define TIMEOUT 10000L   // Nach 60000 ms = 60 s Licht abschalten
#define TIMEDELAY 1000L  // 1000 ms = 1 s Zeitverzögerung zwischen den Segmenten
// Eine Treppe als "struct" mit verschiedenen Elementen definieren
enum {NICHTGESCHALTET,UNTENGESCHALTET,OBENGESCHALTET};

struct dreiSegmentTreppe_t {
  byte schalterUntenPin; // Pin des Schalters unten an der Treppe
  byte schalterObenPin;  // Pin des Schalters oben an der Treppe
  byte schalterState;    // letzter Schaltzustand (in den beiden untersten Bits codiert
  byte geschaltet;    // NICHTGESCHALTET,UNTENGESCHALTET,OBENGESCHALTET}
  unsigned long startZeit;  // enthält die Zeit, wann das Licht eingeschaltet wurde
  byte lichtA_Pin;   // Pin für Lampe-A
  byte lichtB_Pin;   // Pin für Lampe-B (falls nur eine Lampe, identisch mit lichtA_Pin)
  byte lichtC_Pin;   // Pin für Lampe-C (falls nur eine Lampe, identisch mit lichtA_Pin)
};

// Ein Array mit 2 Elementen definieren und mit Werten belegen, entsprechend 2 Treppen
dreiSegmentTreppe_t treppen[]={
  {2,3,0b00,NICHTGESCHALTET,0,8,9,10},
  {4,5,0b00,NICHTGESCHALTET,0,11,11,11},
};

int anzahlTreppen=sizeof(treppen)/sizeof(treppen[0]);

void eingabe()
{
  byte schalterState;
  for(int i=0;i<anzahlTreppen;i++)
  {
    if(treppen[i].geschaltet!=NICHTGESCHALTET) continue; // Schleifenindex abbrechen, falls bereits geschaltet
    schalterState=0b00;
    bitWrite(schalterState,0,digitalRead(treppen[i].schalterUntenPin));
    bitWrite(schalterState,1,digitalRead(treppen[i].schalterObenPin));
    if (schalterState!=0b00 && treppen[i].schalterState==0b00)
    {
      if(bitRead(schalterState,0)) treppen[i].geschaltet=UNTENGESCHALTET;
      else if(bitRead(schalterState,1)) treppen[i].geschaltet=OBENGESCHALTET;
      treppen[i].startZeit=millis();
    }
    treppen[i].schalterState=schalterState;
  }
}


void ausgabeSegment(char treppe, char segment, boolean newState, byte pin)
{
  if(digitalRead(pin)==newState) return; // keine Umschaltung notwendig, es bleibt wie es war
  digitalWrite(pin,newState); // Lampe 
  float f=millis()/1000.0; // Aktuelle Zeit in Gleitkommazahl wandeln
  Serial.print(treppe+1);Serial.print(segment);
  if(newState) Serial.print(" EIN  Pin-"); else Serial.print(" AUS  Pin- ");
  Serial.print(pin);Serial.print("  ");
  Serial.print(f,3);
  Serial.println("s");
}

void ausgabe()
{
  boolean lichtA, lichtB, lichtC;  
  unsigned long now=millis(); // Millisekundentimer aktuell
  for(int i=0;i<anzahlTreppen;i++)
  {
    // Erste Annahme: Das Treppenlicht sei nicht geschaltet und alle Lampen an dieser Treppe aus
    lichtA=false;lichtB=false;lichtC=false;
    // Zuerst prüfen, ob Abschaltzeit erreicht ist und ggf. abschalten
    if (treppen[i].geschaltet && now-treppen[i].startZeit>TIMEOUT+2*TIMEDELAY) treppen[i].geschaltet=false;
    if (treppen[i].geschaltet==UNTENGESCHALTET) // Es ist doch Licht geschaltet
    {
      if(now-treppen[i].startZeit<TIMEOUT) lichtA=true;
      if(now-treppen[i].startZeit>TIMEDELAY && now-treppen[i].startZeit<TIMEOUT+TIMEDELAY) lichtB=true;
      if(now-treppen[i].startZeit>2*TIMEDELAY) lichtC=true;
    }
    else if(treppen[i].geschaltet==OBENGESCHALTET) // Es ist doch Licht geschaltet
    {
      if(now-treppen[i].startZeit<TIMEOUT) lichtC=true;
      if(now-treppen[i].startZeit>TIMEDELAY && now-treppen[i].startZeit<TIMEOUT+TIMEDELAY) lichtB=true;
      if(now-treppen[i].startZeit>2*TIMEDELAY) lichtA=true;
      if(treppen[i].lichtA_Pin==treppen[i].lichtB_Pin) lichtA=lichtC;
    }
    // Jetzt unterschiedliche Logik für Treppen mit 1 oder 3 Lampen
    ausgabeSegment(i,'A', lichtA, treppen[i].lichtA_Pin);
    if(treppen[i].lichtA_Pin!=treppen[i].lichtB_Pin)
    {
      ausgabeSegment(i,'B', lichtB, treppen[i].lichtB_Pin);
      ausgabeSegment(i,'C', lichtC, treppen[i].lichtC_Pin);
    }  
  }
}


void setup() {
  Serial.begin(9600);
  for(int i=0;i<anzahlTreppen;i++)
  {
    pinMode(treppen[i].schalterUntenPin,INPUT); // unterer Schalter ist INPUT
    pinMode(treppen[i].schalterObenPin,INPUT);  // oberer Schalter ist INPUT
    pinMode(treppen[i].lichtA_Pin,OUTPUT);      // Lampen als OUTPUT
    pinMode(treppen[i].lichtB_Pin,OUTPUT);
    pinMode(treppen[i].lichtC_Pin,OUTPUT);
  }
}

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

Guten morgen
sorry für die späte Rückmeldung. Der Code sieht ja mal echt super aus. Ich werde gleich heute Abend mal damit experimentieren :slight_smile: Vielen Dank schon mal!!!