Folge "Animation" mit Neopixel

Hallo zusammen,

ich versuche mich momentan an einer Ampel für unsere Schranke.
Dafür habe ich einen Arduino Nano mit Neopixel (120x 2813).

Die Funktion soll wie folgt sein:

Endlage Schranke Zu -> LED Rot (2 Minuten dauerhaftes leuchten, danach bewegtes Lauflicht wie bei Schranke bewegt sich)

Endlage Schranke Auf -> LED Grün (dauerhaftes leuchten)

Ausgang Schranke Bewegt sich -> LED Gelb (schnelles Lauflicht, von unten nach oben)

Ich habe die Anschlüsse wie folgt belegt:
D3 Input Endlage zu
D5 Input Schranke bewegt sich
D7 Input Endlage Aus
D9 Output Neopixel

Es funktioniert auch Grundlegend. Bei Auf, Zu und Bewegung kommt immer eine andere Farbe.

Was ich leider nicht weiß wie ich das umsetzen kann ist, das nach 2 Minuten "Endlage Zu" die LED anfangen wie bei Schranke bewegt sich zu bewegen.
Bei uns ist längere Zeit die Schranke zu und somit leuchten die LEDs oft Rot und werden warm. Das wollte ich damit verhindern das nach 2 Minuten nur noch ein Teil an ist, der sich auch noch bewegt.
Dafür wäre ganz unten:

static void chase2(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+20; i++) {
      strip.setPixelColor(i  , c);
      strip.setPixelColor(i-20, 0);
      strip.show();
      delay(10);

Kann mir da jemand helfen?

Mein Code sieht so aus:

int neopixel=9;
int schrankezu=3; // Schranke ist zu
int schrankebewegt=5; // Schranke bewegt sich
int schrankeauf=7; // Schranke ist offen
int statuszu=20; // Status Schranke ist zu
int statusbewegt=21; // Status Schranke bewegt sich
int statusauf=22; // Status Schranke ist offen


#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 9 // Datenleitung an Pin 9

#define NUMPIXELS 120

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup()
{
  pinMode(neopixel, OUTPUT);
  pinMode(schrankezu, INPUT);
  pinMode(schrankebewegt, INPUT);
  pinMode(schrankeauf, INPUT);

  strip.begin();
  strip.show();
}

void loop()
{

  statuszu=digitalRead(schrankezu); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu“ mit dem Wert „HIGH“ für 5Volt oder „LOW“ für 0Volt gespeichert.
  statusbewegt=digitalRead(schrankebewegt); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu“ mit dem Wert „HIGH“ für 5Volt oder „LOW“ für 0Volt gespeichert.
  statusauf=digitalRead(schrankeauf); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu“ mit dem Wert „HIGH“ für 5Volt oder „LOW“ für 0Volt gespeichert.
  
  if (statuszu == HIGH)
    {
 
for(int i=0;i<NUMPIXELS;i++)
  {

  
     strip.setPixelColor(i, strip.Color(255,0,0));
     strip.show();
 

    }
  }

  if (statusbewegt == HIGH)
    { 
 chase(strip.Color(255, 255, 0));

  }


  if (statusauf == HIGH)
    {
 
for(int i=0;i<NUMPIXELS;i++)
  {

  strip.setPixelColor(i, strip.Color(0,255,0));
strip.show();

    }
  }
}
static void chase(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+60; i++) {
      strip.setPixelColor(i  , c);
      strip.setPixelColor(i-60, 0);
      strip.show();
      delay(2);
}
}

static void chase2(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+20; i++) {
      strip.setPixelColor(i  , c);
      strip.setPixelColor(i-20, 0);
      strip.show();
      delay(10);
}
}

das heißt du willst eigentlich 4 Status

nämlich

Endlage Schranke Zu -> LED Rot (2 Minuten dauerhaftes leuchten,
danach bewegtes Lauflicht wie bei Schranke bewegt sich)
Endlage Schranke Auf -> LED Grün (dauerhaftes leuchten)
Ausgang Schranke Bewegt sich -> LED Gelb (schnelles Lauflicht, von unten nach oben)

das schreit nach einer Statemachine bzw. Endlicher Automat

Hast du dir schon mal das Beispiel "BlinkWithoutDelay" angesehen um eine Zeitsteuerung hinzubekommen? den genau das braucht es in deinem ersten Status - nach Zeitablauf umschalten in den nächsten.

fett = die einzelnen Status für deine Statemachine
rot = sachen zum Googeln.

Wenn danach noch was unklar ist - konkrete Frage stellen

P.S.: drück mal STRG-T in deiner IDE um die Einrücken zu Verbessern und lösche die unnützen Leerzeilen raus.

edit:
es ist immer noch suboptimal, da dein Chaser ziemlich blockiert, aber das ist ein anderes Thema.
Aber der Code soll grundsätzlich lesbar sein - und auch relativ einfach entsprechend erweiterbar sein.
Z.B. geht mir eine Fehlersituation ab, wenn keiner der Eingänge belegt ist, das kannst du nun aber über einen weiteren "Fehlerzustand" relativ einfach ergänzen.

hardwaremäßig ungetestet, kompiliert und läuft am Nano, Serial Ausgaben schauen soweit ok aus (weswegen ich auch das Problem mit dem Chaser sehe).

// https://forum.arduino.cc/index.php?topic=632634.0

const int neopixel = 9;                                           // hast unten als neopixel - PIN - ich behalte den int mache aber const draus
const int schrankezu = 3;         // Schranke ist zu Pin
const int schrankebewegt = 5;     // Schranke bewegt sich Pin
const int schrankeauf = 7;        // Schranke ist offen Pin
//int statuszu = 20; // Status Schranke ist zu                    // wozu 20? du überschreibst das ohnehin bei jedem Loopdurchlauf
//int statusbewegt = 21; // Status Schranke bewegt sich
//int statusauf = 22; // Status Schranke ist offen

uint32_t previousMillis = 0;       // wir merken uns die letzte Einschaltzeit
const uint16_t shortInterval = 2000; // wie lange soll rot sein

enum status {ZU_LANG, ZU_KURZ, AUF, BEWEGT} mystatus;

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

//#define PIN 9 // Datenleitung an Pin 9                     // hast eh als neopixel = PIN

#define NUMPIXELS 120

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, neopixel, NEO_GRB + NEO_KHZ800);   // hast eh als neopixel = PIN

void setup()
{
  Serial.begin(115200); // damit wir sehen was wir tun
  Serial.println(F("\ndamit wir sehen was wir tun starten wir Serial..."));
  pinMode(neopixel, OUTPUT);
  pinMode(schrankezu, INPUT);
  pinMode(schrankebewegt, INPUT);
  pinMode(schrankeauf, INPUT);

  strip.begin();
  strip.show();
}

void loop()
{
  // EINGABE
  // ... schenken wir uns als separten Schritt, machen wir direkt in der Verarbeitung
  //statuszu = digitalRead(schrankezu); // Hier wird der Pin      ausgelesen (Befehl:digitalRead). Das Ergebnis wird         mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.
  //statusbewegt = digitalRead(schrankebewegt); // Hier wird der Pin      ausgelesen (Befehl:digitalRead). Das Ergebnis wird       mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.
  //statusauf = digitalRead(schrankeauf); // Hier wird der Pin     ausgelesen (Befehl:digitalRead). Das Ergebnis wird  mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.

  // VERARBEITUNG

  if (digitalRead(schrankezu))
  {
    Serial.print(F("schranke ist zu: "));
    if (mystatus != ZU_KURZ && mystatus != ZU_LANG)
    {
      Serial.print(F(" reset timer "));
      previousMillis = millis();                           // wenn wir erstamlig aus einem Status != kurz/lang kommen müssen wir den den timer reseten
    }
    if (millis() - previousMillis <= shortInterval)          // sollen wir noch im kurzem Intervall verbleiben?
    {
      Serial.print(F(" -->ZU_KURZ "));
      mystatus = ZU_KURZ;
    }
    else
    {
      Serial.print(F(" -->ZU_LANG "));
      mystatus = ZU_LANG;
    }
  }
  if (digitalRead(schrankebewegt))
  {
    mystatus = BEWEGT;
  }
  if (digitalRead(schrankeauf))
  {
    mystatus = AUF;
  }


  // AUSGABE
  switch (mystatus)
  {
    case ZU_KURZ:
      Serial.println(F("do ZU_KURZ"));
      for (int i = 0; i < NUMPIXELS; i++)
      {
        strip.setPixelColor(i, strip.Color(255, 0, 0));
      }
      strip.show();  // in dem Fall reicht EIN Aufruf nach dem setzen aller Pixel
      break;
    case ZU_LANG:
      Serial.println(F("do ZU_LANG"));
      chase(strip.Color(255, 255, 0));
      break;
    case AUF:
      Serial.println(F("do AUF"));
      for (int i = 0; i < NUMPIXELS; i++)
      {
        strip.setPixelColor(i, strip.Color(0, 255, 0));
      }
      strip.show();
      break;
    case BEWEGT:
      Serial.println(F("do BEWEGT"));
      chase(strip.Color(255, 255, 0));
      previousMillis = millis();
      break;
  }
}


static void chase(uint32_t c) {
  for (uint16_t i = 0; i < strip.numPixels() + 60; i++) {
    strip.setPixelColor(i  , c);
    strip.setPixelColor(i - 60, 0);
    strip.show();
    delay(2);          // BÖSE!!!!
  }
}

/*
  static void chase2(uint32_t c) {
  for (uint16_t i = 0; i < strip.numPixels() + 20; i++) {
    strip.setPixelColor(i  , c);
    strip.setPixelColor(i - 20, 0);
    strip.show();
    delay(10);
  }
  }
*/

Versuch es mal so:

int neopixel = 9;
int schrankezu = 3; // Schranke ist zu
int schrankebewegt = 5; // Schranke bewegt sich
int schrankeauf = 7; // Schranke ist offen
int statuszu = 20; // Status Schranke ist zu
int statusbewegt = 21; // Status Schranke bewegt sich
int statusauf = 22; // Status Schranke ist offen

boolean schrankeIstzu = false;      // wird zur erkennung benötigt ob timer für dauerrot gestartet ist
unsigned long rotdauerStart = 0;     // Startzeit für Dauerrot speichern
unsigned long rotdauerDelay = 5000;  // Timer für Dauerrot schrankezu  bei 120000 = 2Min.  (5000 = 5 Sekunden nur zum test)

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 9 // Datenleitung an Pin 9

#define NUMPIXELS 120

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup()
{
  pinMode(neopixel, OUTPUT);
  pinMode(schrankezu, INPUT);
  pinMode(schrankebewegt, INPUT);
  pinMode(schrankeauf, INPUT);

  strip.begin();
  strip.show();
}

void loop()
{

  statuszu = digitalRead(schrankezu); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu" mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.
  statusbewegt = digitalRead(schrankebewegt); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu" mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.
  statusauf = digitalRead(schrankeauf); // Hier wird der Pin D3 ausgelesen (Befehl:digitalRead). Das Ergebnis wird unter der Variable „tasterzu" mit dem Wert „HIGH" für 5Volt oder „LOW" für 0Volt gespeichert.

  if (statuszu == HIGH)
  {
    if (schrankeIstzu == false) // schranke soeben geschlossen
    {
      for (int i = 0; i < NUMPIXELS; i++)
      {
        strip.setPixelColor(i, strip.Color(255, 0, 0));
        strip.show();
      }
      rotdauerStart = millis();   // Start der dauerRot Phase
      schrankeIstzu = true;       // Timer für schranke ist zu gestartet
    }
    else
    {
      if (millis() - rotdauerStart > rotdauerDelay)  // schranke länger als 2 Minuten zu
      {
        chase2(strip.Color(255, 0, 0)); // rot bewegt aufrufen
      }
    }
  }

  if (statusbewegt == HIGH)
  {
    schrankeIstzu == false;      // status von schrankeIstzu zurücksetzen
    chase(strip.Color(255, 255, 0));

  }


  if (statusauf == HIGH)
  {

    for (int i = 0; i < NUMPIXELS; i++)
    {

      strip.setPixelColor(i, strip.Color(0, 255, 0));
      strip.show();

    }
  }
}
static void chase(uint32_t c) {
  for (uint16_t i = 0; i < strip.numPixels() + 60; i++) {
    strip.setPixelColor(i  , c);
    strip.setPixelColor(i - 60, 0);
    strip.show();
    delay(2);
  }
}

static void chase2(uint32_t c) {
  for (uint16_t i = 0; i < strip.numPixels() + 20; i++) {
    strip.setPixelColor(i  , c);
    strip.setPixelColor(i - 20, 0);
    strip.show();
    delay(10);
  }
}

zum testen habe ich die dauerrot phase auf 5 sekunden gesetzt. siehe bei 'unsigned long rotdauerDelay'
habe jetzt einfach mal angenommen, dass das 'rot bewegt' im chase2 gemacht werden soll.
ich hoffe so klappts.

LG Stefan

Vielen Dank euch beiden.

Das funktioniert ja super, damit kann ich nun weiter arbeiten und evtl noch verfeinern.