FastLED Library- strukturiertes Vorgehen bei genauen Vorstellungen

Chris72622:
Zustände:

  1. BGD (Hintergrundanimationen sichtbar)
  2. Weiche Wischblende von BGD nach Layer (ggfs. von beiden Seiten, für den Fall wenn zwei Personen die Treppe gleichzeitig betreten)
    Sonderfall: Während vom Layer zu BGD geblendet wird (siehe 4.), betritt mind. eine Person erneut die Treppe. Hierbei sollte dann weiterhin der Layer ausgeblendet werden, während die weiche(n) Wischblende(n) aus 2. das Ganze erneut überlagern sollen.
  3. Layer (“Füllfarbe” überlager BGD vollumfänglich)
  4. Blende von Layer nach BGD

Fades:

  1. “PIR1” (unten) löst aus: Per weicher Wischblende wird BGD von unten nach oben von “Layer” überlagert
  2. “PIR2” (oben) löst aus: Per weicher Wischblende wird BGD von oben nach unten von “Layer” überlagert
  3. Layer-Zeitüberschreitung: Sobald Layer ist x Sekunden sichtbar war, soll gleichmäßig weich in BGD geblendet werden.

Sobald ein Fade gestartet wurde, darf dieser während er läuft nicht erneut ausgelöst werden.
Würde jedoch z.B. der “PIR unten”-Fade ausgelöst, soll zusätzlich gegebenenfalls noch der “PIR oben”-Fade ausgelöst werden können.

Um die Problemstellen zu erkennen, habe ich mir Deinen Code bislang nicht angesehen, sondern es mal so gemacht, wie ich es angehen würde. Herausgekommen sind zwei endliche Automaten für jede Richtung und eine Mischung von drei Feldern für drei getrennt gestaltete Animationen zu einem vierten für die Ausgabe. Die PIRs lösen eine schlichte Animation aus, die die LEDs auf Weiß schalten. Da kann man noch mehr machen, aber Dir geht es ja vorwiegend um die Logik.

Ich teste mit 62 APA102C-LEDs an einem Nano mit Hardware-SPI, das mußt Du eventuell anpassen. Hardware-SPI solltest Du aber auch nutzen. Bin gespannt auf Dein Echo :slight_smile:

EDIT 22:38: Überflüssige delays entfernt.

Test_Forum.ino (4.43 KB)

Hallo agmue,

ich hab mir Deinen Code jetzt mal angeschaut, obwohl ich leider noch immer nicht mit LED-Streifen testen kann.

Variablen mit Namen wie z.B. anausRauf oder Zeilen wie zustandRauf = ANRAUF; machen es dem Leser nicht leicht den Code zu durchschauen.

Was mir jedoch gefallen hat, sind in der anzeige-Funktion diese getLuma-Geschichten. :slight_smile:

Ich hatte nun auch mal etwas Zeit, mich an den von mir gewünschten weichen Wischblenden (4:14min) zu versuchen.

Diese würde ich in der finalen Version gerne per blend und einer for-Schleife implementieren.

Anbei ein funktionsfähiger Beispielsketch mit serieller Ausgabe.

Was mir noch nicht gefällt ist die Tatsache, dass an aufeinander folgende Pixel jeweils fest berechnete Werte (6, 12, 19,…) “gesendet” werden, anstatt dass die Pixel jeweils einzeln berechnet werden.

Ich kann es leider nicht besser beschreiben, aber wenn man sich die serielle Ausgabe einmal anschaut, versteht man was gemeint ist.

Bin für jeden Tipp dankbar, wie man den Fadevorgang noch weicher gestalten kann. Mit “Fadevorgang” meine ich die Werterhöhung der einzelnen Index von 0 auf 255. Diese Index sollen später die Basis für den Fadevorgang erzeugen.

Gruß Chris

#define NUM_LEDS 20    // Alle LEDs

byte fadePir1[NUM_LEDS]; // Zustand des PIR1-Keys
byte fadePir2[NUM_LEDS]; // Zustand des PIR2-Keys
byte fadeBgd[NUM_LEDS];  // Zustand des BGD-Keys
byte *fade[] = {fadeBgd, fadePir1, fadePir2};

byte blending[NUM_LEDS];  // Zustand des Keys

enum {OFF, START, FADE, BGD, LAYERSTART, LAYER};
byte globalState = BGD;

byte fadeState[] = {0, 0, 0}; // Speichert den Zustand der BGD-, PIR 1- und PIR 2-Fades
byte pAmount = 40;            // Anzahl der Pixel für die Rampe

static unsigned long fadeTime;
const int fadeDur = 10000;
int p;
int pix;
int pixWrite;

void setup()
{
  Serial.begin(9600);
  delay(5000);
  Serial.println("Start");
  fadeTime = millis();
}

void loop()
{
  p = (millis() - fadeTime) / (fadeDur / NUM_LEDS); // Berechnet permanent die Position von p

  if (fade[1][NUM_LEDS] != 255) // So lange die letzte LED noch nicht den Wert 255 hat
  {
    for (int i = 0; i <= pAmount; i++)
    {
      pix = p - i;      // pix ausgehend von p gleichmäßig reduzieren
      if (pix <= NUM_LEDS && pix >= 0)
      {
        fade[1][pix] = (255.0 / pAmount) * i; // Rampe beschreiben
      }
    }
  }
  else
  {
    Serial.println("Fertig!");
  }
  serielleAusgabe();
}

void serielleAusgabe()
{
  static unsigned long lastTime = 0;

  if (millis() - lastTime >= 500)
  {
    for (byte i = 0; i < NUM_LEDS; i++)
    {
      Serial.print(fade[1][i]);
      Serial.print("\t");
      if (i == NUM_LEDS - 1) Serial.println("");
    }
    lastTime = millis();
  }
}

Das geht einfacher und besser. :wink:

Ich empfehle erneut (und im Wissen, was Du später vorhast) den Ansatz, 2 unabhängige Animationen in 2 extra Arrays zu berechnen. Eins davon kann ja komplett mit “Schwarz” gefüllt sein.

Dann wird alles sehr einfach. Ein Beispiel - die Wischblende. Animation A sind rote Streifen, B ein Regenbogen. Grenze für den Wischeffekt berechnen / festlegen und dann nach leds kopieren.

So hier:

void animation37() {
  // Animation A (rot)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds2[i] = CRGB(millis() + (i * 10), 0, 0);
  }

  // Animation B (Regenbogen)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds3[i] = CHSV(millis()-(i*10), 255, 255);
  }

  // Grenze vom Wischeffekt bestimmen
  uint16_t ratio = (beatsin8(20) * NUM_LEDS) / 255;

  // Stripanfang bis zur Grenze Animation A
  for (int i = 0; i < ratio; i++) {
    leds[i] = leds2[i];
  }

  // Grenze bis Ende Animation B
  for (int i = ratio; i < NUM_LEDS; i++) {
    leds[i] = leds3[i];
  }

  FastLED.show();
}

FÜr die Überblendung ganz genau so:

void animation38() {
  // Animation A (rot)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds2[i] = CRGB(millis() + (i * 10), 0, 0);
  }

  // Animation B (Regenbogen)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds3[i] = CHSV(millis()-(i*10), 255, 255);
  }

  // Verhältnis der Überblendung bestimmen
  uint16_t ratio = (beatsin8(20));

  // Crossfade
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = blend( leds2[i], leds3[i], ratio );
  }

  FastLED.show();
}

Vorteile von meinem Ansatz:

  1. Die Animation läuft unabhängig von Framerate und Prozessor immer gleich schnell. (Zeitgesteuert)
  2. Du schöpfst die 8 Bit Auflösung der Steuerparamer aus. (=weiche Animation)
  3. Später musst Du nur noch die Grenze und/oder Überblendratio zwischen den Animationen in Anhängigkeit der Bewegungsdaten verändern. (Universeller Ansatz)
  4. Du hast Animationen und “Videoeffekte” komplett getrennt und musst Dir keinen Kopf über Wechselwirkungen machen und kannst Dich auf die Logik der State Machine konzentrieren.
  5. Last but not least: keine Floats!

Hoffe, das hilft.
Beste Grüße,

Helmuth

Hi Helmuth,

ich will und werde, genau so wie Du es vorgeschlagen hast, jede Animation (bei mir als "Pattern" benannt) in einem eigenen Array speichern, da ich ja weiss, dass während den Wischblenden Teile zweier Animationen/Pattern auf jeden Fall gleichzeitig zu sehen sein werden.

Bin gerade noch dabei, die von Dir vorgeschlagenen Codeideen zu analysieren.. dauert bei mir immer etwas. ;)

beatsin8 hab ich da z.B. entdeckt, was ich bisher nicht kannte.

Gruß Chris

Probier den Code einfach aus, dann erklärt sich vieles von selbst. Nimm einen Testsketch, lege 3 CRGBs leds leds2 und leds3 an and schon kann es losgehen.

Es erleichtert die Programmlogik ungemein, wenn IMMER 2 Animationen laufen.

Beatsin8 habe ich nur genommen, um mit wenig Code einen sich verändernden Parameter zu haben.

Später kontrollierst Du den selbst.

Gruß, H.

Chris72622:
ich hab mir Deinen Code jetzt mal angeschaut, obwohl ich leider noch immer nicht mit LED-Streifen testen kann.

Das ist schade, da ich vermute, bei praktischer Anschauung wirst Du den ein oder anderen theoretischen Ansatz noch verwerfen. Ist mir so gegangen.

Chris72622:
Variablen mit Namen wie z.B. anausRauf oder Zeilen wie zustandRauf = ANRAUF; machen es dem Leser nicht leicht den Code zu durchschauen.

Dabei habe ich mir extra soooo viel Mühe gegeben, selbsterklärende Namen zu erfinden :frowning: Die Variable anausRauf gibt an, ob für die Raufbewegung der die Treppe betretenden Person die LEDs an oder nach Zeitverzögerung ausgeschaltet werden sollen. Also Wischblende rauf für Leds an oder Leds aus.

Zwei kreative Köpfe mit unterschiedlichen Ergebnissen. Es ist immer schwierig, fremde Programme zu lesen.

Anbei die Weiterentwicklung meines Sketches, nun mit zwei Hintergrundanimationen, die mittels Wischblende abwechselnd angezeigt werden.

Test_Forum.ino (5.69 KB)

Mittlerweile konnte ich nun mit RGB-Streifen testen.

Ich habe die beiden Codebeispiele von Dir, Helmuth so adaptiert, dass ich mit ihnen etwas anfangen konnte.

Innerhalb von animation37() erfolgt der Übergang zwischen led2 und leds3 leider nicht weich und ist somit keine weiche Wischblende.

Einfach mal in dem von mir verlinkten Film bei 9,5 Sek auf Pause klicken. Am Übergang zwischen den beiden Szenen ist im Standbild keine harte Linie, sondern ein weicher Übergang zu sehen.

So möchte ich es praktisch nicht:

sds.jpg
Man beachte die harten Fades beim Betreten der Treppe.

animation38() werde ich genau so übernehmen. Ist mit der hohen Auflösung echt butterweich- topp!

In den Code von Dir, agmue muss ich mich zwar nochmals genauer einlesen, konnte ihn jedoch auch kurz aufspielen und testen. Hach, ich bräuchte einfach mal mehr Zeit am Stück. :frowning:

Gruß Chris

Wenn Du bei der Wischblende die Grenze zwischen den Animationen nicht hart sondern als Crossfade haben willst, musst Du das in 3 Schritten machen:

  1. Anfang von leds2 kopieren
  2. im Grenzbereich eine Überblendung (wie in animation38 - aber mit von 0 auf 255 ansteigendem Verhältnis "ratio") berechnen
  3. hinter dem Crossfade Bereich leds3 kopieren

Gruß, H.

Denkst Du dass es Sinn macht, den zu beschickenden Bereich NUM_LEDS (in meinem Fall also knappe 200 LEDs) über den kompletten Wertebereichs z.B. eines vorzeichenlosen Integers abzubilden, damit kontinuierlich feinere Zwischenwerte für die einzelnen LEDs errechnet werden können?

Stell Dir vor ich würde den [u]Übergang steil[/u] ansteigen lassen und die [u]Wischblende sehr langsam[/u] ausführen.

Ich will vermeiden, dass der Übergang dann von LED zu LED "springt", sondern die LEDs kontinuiertlich soft eingeblendet werden (gut sichtbar z.B. wenn von schwarz nach weiß geblendet wird).

Was denkst Du bzgl. dieser Problematik?

Gruß Chris

Definiere einen Übergangsbereich, z.B. 20 Leds. Innerhalb dieses Bereiches sorgst Du dafür, dass die erste Led ratio 0 und die 20. ratio 255 bekommt. Und auf die restlichen 18 Leds verteilst du den Anstieg von ratio. Das wird butterweich. Ggf. vergrößerst Du den Überblendbereich auf mehr als 20 Leds. An der Auflösungsgrenze bist Du erst, wenn der Übergangsbereich 256 Leds lang wäre.

Gedanken machen musst Du Dir, wie Du den Anfangs- und Endbereich des Strips behandelst. Der Übergang soll ja "aus dem Nichts" kommen, sich dann den Strip entlang schieben und schließlich wieder im Nichts verschwinden.

Gruß,

Helmuth

Absolut.

Ich werde dranbleiben.

Es gibt zwei Auflösungsgrenzen, eine zeitliche und eine wertemäßige.

Das was Du mit Auflösungsgrenze gemeint hast, bezieht sich aus meiner Sicht auf maximal abbildbaren Helligkeitswerte der LEDs. Die Auflösungsgrenze die mir jedoch mehr Sorgen macht ist die zeitliche.

Ich werde nun versuchen mittels eines Integers den kompletten Übergangsbereich in kleinere Teile zu zerlegen. Damit meine ich Teile die kleiner sind als der Abstand zwischen zwei LEDs.

Wenn ich hierzu ein schlüssiges Konzept ausgedacht habe, werde ich mich um die Vor- und Nachlaufsproblematik, die in der Tat besteht, kümmern.

Gruß Chris

Das was Du mit Auflösungsgrenze gemeint hast, bezieht sich aus meiner Sicht auf maximal abbildbaren Helligkeitswerte der LEDs.

Nein, ich meinte damit die 8 Bit Auflösung des Übergabeparameters für blend.

Die Auflösungsgrenze die mir jedoch mehr Sorgen macht ist die zeitliche.

Bei 200 APAs?? Da sehe ich kein Problem.

Ich werde nun versuchen mittels eines Integers den kompletten Übergangsbereich in kleinere Teile zu zerlegen. Damit meine ich Teile die kleiner sind als der Abstand zwischen zwei LEDs.

Was soll das bringen? Die Animationen haben doch nur Daten für die Anzahl der Leds. Verstehe gerade nicht, welches Problem das lösen soll.

Aber mach und zeig einfach mal!

Gruß, H.

Stell Dir vor, der Übergangsbereich wäre so steil, dass sich für LEDs im Übergangsbereich folgende Werte ergeben würden:

0, 63, 127, 191, 255

Wenn ich nun einen extrem langsamen Wipe ausführe und [u]die zeitliche Auflösung nicht feiner ist als der LED-Abstand[/u] würde sich folgender Übergang ergeben:

t = 000: 0, 0, 0, 0, 0, 0, 0, 0 t = 100: 63, 0, 0, 0, 0, 0, 0, 0 t = 200: 127, 63, 0, 0, 0, 0, 0, 0 t = 300: 191, 127, 63, 0, 0, 0, 0, 0

usw.

Das möchte ich nicht. Ich möchte Folgendes erreichen:

t = 000: 0, 0, 0, 0, 0, 0, 0, 0 t = 010: 4, 0, 0, 0, 0, 0, 0, 0 t = 020: 8, 4, 0, 0, 0, 0, 0, 0 t = 030: 16, 8, 4, 0, 0, 0, 0, 0

Es sollen somit praktisch permanent sämtliche LEDs "geupdatet" werden und dies kann meiner Meinung nach nur dadurch funktionieren, indem man die zeitliche Auflösung erhöht.

Bin dran..

Gruß Chris

Wenn Du 4er Schritte willst, musst Du den Übergangsbereich einfach 64 Leds lang machen.

Jedenfalls, wenn Du t = 030: 12, 8, 4, 0, 0, 0, 0, 0 meinst.

Hier mal ein quick&dirty Testcode, welcher einen Übergangsbereich von 59 Leds zeigt:

void animation41() {

  // Animation A (rot)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds2[i] = CRGB(millis() + (i * 10), 0, 0);
  }

  // Animation B (Regenbogen)
  for (int i = 0; i < NUM_LEDS; i++) {
    leds3[i] = CHSV(millis() - (i * 10), 255, 255);
  }

  // 0-39 nur kopieren
  for (int i = 0; i < 40; i++) {
    leds[i] = leds2[i];
  }

  // 40-99 Crossfade 
  for (int i = 40; i < 100; i++) {
    leds[i] = blend( leds2[i], leds3[i], ((i - 40) * 255) / 59 );
  }

  // 100-Ende nur kopieren
  for (int i = 100; i < NUM_LEDS; i++) {
    leds[i] = leds3[i];
  }

  FastLED.show();
}

Jetzt musst Du die Grenze nur noch bewegen und dir etwas für die Ränder einfallen lassen.

Gruß, H.

Chris72622: In den Code von Dir, agmue muss ich mich zwar nochmals genauer einlesen, konnte ihn jedoch auch kurz aufspielen und testen. Hach, ich bräuchte einfach mal mehr Zeit am Stück. :(

Mein Schwerpunkt lag ja auf der Logik, nicht auf der Animation. Aber wenn die Animation für Rauf oder Runter einfaden würde, gäbe es dank getLuma() auch einen weichen Übergang. Hoffe ich zumindest, bin ich nur noch nicht dazu gekommen.

Meine Frau braucht 0,5 Sekunden für eine Stufe. Da frage ich mich, wieviel Schnickschnack beim Übergang von Hintergrund-Animation zu "bitte Treppe beleuchten, damit niemand stolpert" sinnvoll ist. Meine Frau hätte sovieso lieber die Showtreppe mit stufenweisem Einblenden. Wie sind Deine LEDs angebracht, stufenweise oder an der Seite?

An der Seite.

Guten Morgen. :slight_smile:

Bin bzgl. des Themas mit der Auflösung weitergekommen.

Anbei der aktuelle (noch fehlerhafte) Code. Ich kann einen der Fehler jedoch eingrenzen.

In Folgender Zeile entstehen Rundungsfehler, bei denen ich nicht weiss, wie ich sie umgehen kann:

iFine = (wipeDur / NUM_LEDS) * i; // Höher aufgelöster i-Wert

Wenn i den Wert 15 hat, wird iFine nicht 10000 übergeben, sondern lediglich 9324.

Ändere ich die Zeile um auf…

iFine = ((float)wipeDur / NUM_LEDS) * i; // Höher aufgelöster i-Wert

…komme ich immerhin auf 9333.

Was kann ich tun, um diesen Rundungsfehler zu umgehen?

Der Sinn dieser Zeile besteht darin, jeder einzelnen LED einen feiner aufgelösten Wert zuzuordenen, der im Anschluss dann mit pFine verglichen werden kann.

Gruß Chris

#define FASTLED_ALLOW_INTERRUPTS 0
#include "FastLED.h"

#if FASTLED_VERSION < 3001000
#error "Requires FastLED 3.1 or later; check github for latest code."
#endif

#define DATA_PIN 11     // Manuelle Eingabe!
#define CLOCK_PIN 13    // Manuelle Eingabe!
#define NUM_LEDS 15     // Alle LEDs (inkl. der pysikalisch nicht vorhandenen)
#define BRIGHTNESS  64  // Manuelle Eingabe!

byte start = true;

unsigned long now;
unsigned long startTime;      // Startzeitpunkt
unsigned int wipeDur = 10000; // Übergangsdauer
int iFine;                    // Fein aufgelöste LED Position
int pFine;                    // Fein aufgelöste aktuelle Position
unsigned int pFineOld;        // Fein aufgelöste letzte Position
int transDur = 2000;          // Übergangsdauer Übergangsbereich
float ramp;                   // Steigung
unsigned int shift;           // Versatz innerhalb der transDur

byte trans[NUM_LEDS];         // Werte für "blend"


void setup()
{
  Serial.begin(9600);
  ramp = -((float) 255 / transDur);
  delay(5000);
  Serial.print("Steigung: ");
  Serial.println(ramp,4);
  for (byte i = 0; i < NUM_LEDS; i++)
  {
    trans[i] = 0;
  }
}

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

void berechnung()
{
  now = millis();
  switch (start)
  {
    case true:
      startTime = millis();
      start = false;
      break;
    case false:
      if (trans[NUM_LEDS - 1] != 255) // Letzte LED bereits auf 255?  // ok
      {
        pFine = millis() - startTime;
        if (pFine != pFineOld)  // Hat sich was geändert seit dem letzten Durchlauf?
        {
          for (int i = 0; i < NUM_LEDS; i++) // Einzeln die LEDs durchgehen
          {
            iFine = (wipeDur / NUM_LEDS) * i; // Höher aufgelöster i-Wert
            {
              if (iFine < pFine - transDur) trans[i] = 255; // Alles nach der Blende beschreiben
              else if (iFine < pFine) // Blende
              {
                shift = pFine - iFine;
                trans[i] = (shift * ramp) + 255;
              }
            }
          }
          pFineOld = pFine;
        }
      }
      break;
  }
}

void ausgabe()
{
  static unsigned long lastTime;
  if (millis() - lastTime >= 500)
  {
    for (int i = 0; i < NUM_LEDS; i++)
    {
      Serial.print(trans[i]);
      Serial.print("\t");
    }
    Serial.println("");
    lastTime = millis();
  }
}

Edit: Es muss wohl folgendermaßen lauten:

iFine = ((float)wipeDur / (NUM_LEDS - 1)) * i;

Jetzt kann es weitergehen mit der Fehlersuche. :slight_smile:

iFine = ( (i+1) * wipeDur ) / NUM_LEDS;

müsste funktionieren. Multiplikation immer zuerst, dann brauchst du keine floats.

Hast du meinen Code ausprobiert?

Gruß, H.

Hi Helmuth,

ich habe ihn heute früh, bevor ich mich an meine Version machte überflogen.

Da ein sehr kniffliger Part (zeitliche Auflösung) darin nicht behandelt wurde, entschloss ich mich dazu meine Version zunächst Mal fortzuführen.

Ich habe es nun aber offenbar tatsächlich geschafft.

Siehe u.a. Code.

Den von Dir aufgeführten Tipp mit der Multiplikation muss ich noch einbauen.

1000 Dank Dir und agmue! Ohne Euren Input wäre ich niemals so weit gekommen! :grin:

Gruß Chris

#define FASTLED_ALLOW_INTERRUPTS 0
#include "FastLED.h"

#if FASTLED_VERSION < 3001000
#error "Requires FastLED 3.1 or later; check github for latest code."
#endif

#define DATA_PIN 11     // Manuelle Eingabe!
#define CLOCK_PIN 13    // Manuelle Eingabe!
#define NUM_LEDS 14     // Alle LEDs (inkl. der pysikalisch nicht vorhandenen)
#define BRIGHTNESS  64  // Manuelle Eingabe!

byte start = true;

unsigned long now;
unsigned long startTime;      // Startzeitpunkt
unsigned int wipeDur = 10000; // Übergangsdauer
int iFine;                    // Fein aufgelöste LED Position
int pFine;                    // Fein aufgelöste aktuelle Position
unsigned int pFineOld;        // Fein aufgelöste letzte Position
int transDur = 4000;          // Übergangsdauer Übergangsbereich
float ramp;                   // Steigung
unsigned int shift;           // Versatz innerhalb der transDur

byte trans[NUM_LEDS];         // Werte für "blend"


void setup()
{
  Serial.begin(9600);
  ramp = -((float) 255 / transDur);
  delay(1000);
  Serial.print("Übergang: ");
  Serial.print(transDur);
  Serial.print("ms, Steigung: ");
  Serial.println(ramp, 4);
  Serial.println("");
  for (byte i = 0; i < NUM_LEDS; i++)
  {
    trans[i] = 0;
  }
}

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

void berechnung()
{
  now = millis();
  switch (start)
  {
    case true:
      startTime = millis();
      start = false;
      break;
    case false:
      if (trans[NUM_LEDS - 1] != 255) // Letzte LED bereits auf 255?  // ok
      {
        pFine = millis() - startTime;
        if (pFine != pFineOld)  // Hat sich was geändert seit dem letzten Durchlauf?  // ok
        {
          for (int i = 0; i < NUM_LEDS; i++) // Einzeln die LEDs 0 bis 14 durchgehen  // ok
          {
            iFine = ((float)wipeDur / (NUM_LEDS - 1)) * i; // Höher aufgelöster i-Wert  // ok
            {
              if (iFine >= 0)
              {
                if (iFine < pFine - transDur) trans[i] = 255; // Alles nach der Blende beschreiben  // ok
                else if (iFine < pFine) // Blende
                {
                  shift = transDur - (pFine - iFine);
                  trans[i] = (shift * ramp) + 255;
                }
              }
            }
          }
          pFineOld = pFine;
        }
      }
      break;
  }
}

void ausgabe()
{
  static unsigned long lastTime;
  if (millis() - lastTime >= 1)
  {
    for (int i = 0; i < NUM_LEDS; i++)
    {
      Serial.print(trans[i]);
      Serial.print("\t");
    }
    Serial.println("");
    lastTime = millis();
  }
}

Da ein sehr kniffliger Part (zeitliche Auflösung) darin nicht behandelt wurde, entschloss ich mich dazu meine Version zunächst Mal fortzuführen.

Ich verstehe leider immer noch nicht, was Du konkret meinst.

ich habe ihn heute früh, bevor ich mich an meine Version machte überflogen.

Probier´ ihn doch bei Gelegenheit aus und dann sag, was am Ergebnis nicht passt. Ich würde das Problem wirklich gern verstehen.

Gruß, H.