Farbton FastLED ändert sich; Verwendung von .setBrightness

Hallo zusammen,

ich hab mir eine Wordclock aus einem NodeMCU-µC und WS2812b-LEDs gebaut - läuft auch soweit top.
Zum Ansteuern der LEDs nutze ich FastLED (Version 3.2)

Jetzt habe ich folgendes Problem, wenn ich die LEDs ca. unter Brightness 20 (FastLED.setbrightness) setze: Die Farbe unterscheidet sich von LED zu LED minimal.

Beispiel: die LEDs für das Wort ACHT leuchten.
A und H haben denselben Farbton, C und T einen minimal anderen.
Mit erneutem Aufruf der FastLED.show kehren sich die Farben um. Bei den nächsten 2-3 Aufrufen stimmen die Farben wieder, dann geht das Spiel von vorn los.

Tut mir Leid, dass ich kein Foto machen kann - der Farbunterschied ist nur mit den Augen erkennbar, nicht per Handyfoto.

Codemäßig passiert eigentlich nicht viel.
Ich rufe in der loop setbrightness() (s.u.) und später FastLED.setBrightness(brightness) und FastLED.show

void setbrightness() {
  int high = 255;
  int low = 10;
  int atsun = 40;  //Helligkeit bei Sonnenauf/untergang

  if (timeinmin < laSunset - 240  && timeinmin > laSunrise + 180) {     //tagsüber
    brightness = high;
  }
  if (timeinmin > laSunset + 120 || timeinmin < laSunrise - 60) {                   //nachts
    brightness = low;
  }
  if (timeinmin > laSunset - 240 && timeinmin < laSunset + 120) {     // Zeitraum für Dimmen abends
    if (timeinmin < laSunset) {                            // Dimmen vor Sonnenuntergang, Schwelle 240 min = 4 h vor Sonnenuntergang
      brightness = mapfloat(timeinmin, laSunset, laSunset - 240, atsun, high );
    } else {                                                 // Dimmen nach Sonnenuntergang,  Schwelle 120 min = 2 h nach Sonnenuntergang
      brightness = mapfloat(timeinmin, laSunset + 120, laSunset, low, atsun );
    }
  }
  if (timeinmin < laSunrise + 180 && timeinmin > laSunrise - 60) {          // Zeitraum für Dimmen früh
    if (timeinmin > laSunset) {                        // Dimmen nach Sonnenaufgang, Schwelle 180 min = 3 h nach Sonnenaufgang
      brightness = mapfloat(timeinmin, laSunrise, laSunrise + 180, atsun, high );
      atsun + (high - atsun) / 180 * (timeinmin - laSunrise);
    } else {                                            // Dimmen vor Sonnenaufgang,  Schwelle 60 min = 1 h vor Sonnenaufgang
      brightness = mapfloat(timeinmin, laSunrise - 60, laSunrise, low, atsun );
    }
  }
}

Ich vermute eine Rechenungenauigkeit in der FastLED.setBrightness()-Funktion.

Könnt ihr mir weiterhelfen?

Viele Grüße
Sebastian

In den meisten Fällen ist nicht die Bibliothek schuld. Geht der Fehler weg wenn Du setbrightness() nur einmal pro Sekunde oder noch seltener aufrufst?

Was sollen wir aus dem Sketchteil verstehen?
Du berechnest einen Wert wieso soll der dann bei der Ansteuerung mehrere WS2812 anders sein.
Grüße Uwe

Danke für eure schnellen Antworten.

DrDiettrich:
... Geht der Fehler weg wenn Du setbrightness() nur einmal pro Sekunde oder noch seltener aufrufst?

Set brightness wird nur alle 10 s aufgerufen, hier mal ein Auszug aus der loop:

void loop() {
  if (millis() - millis_start > 18000000) {    //300 min vergangen
    gettimeofday(&tv, nullptr);
    now = time(nullptr);
    millis_start = millis(); //Zeit in ms seit Arduinostart
  } else {
    now = now + 10; // 10 s draufaddieren
  }

  blankscreen(true);           // all pixels to black, "Aufräumen" der LEDs, sonst würden iwann alle leuchten
...
 setbrightness();
  if (LEDs_on) {
    timeToStripe(hourx, minx);
    FastLED.setBrightness(brightness);
    FastLED.show();
  }
  delay(10000 - 675); //675 ms ist die Laufzeit des Programms
}

Uwe, ich geb dir da Recht, mein Gedankengang war da Murks - bin nicht so der leidenschaftliche Hobbyprogrammierer. Für mich ist es eher eine lästige Pflicht, da sich so eine Uhr sonst nicht realisieren lässt.

2 potenzielle Ansätze noch:
Die Variable brightness ist vom Typ byte, kann das bei der Übergabe an die FastLED Prozeduren für Probleme sorgen?
Der Fehler tritt wie gesagt nur bei geringer Helligkeit auf. Deswegen riecht es für mich ziemlich nach einer Art Rundungsfehler beim Berechnen des gedimmten CRGB-Wertes.

Und dann nochmal ohne delay(), siehe BlinkWithoutDelay.

Una dann den vollständigen Sketch. Die Variablendefinition und die setup() Funktion fehlen.

Hallo,

bei dem miesen Wetter hatte ich heute etwas Zeit mich um die Uhr zu kümmern.

Habe es geschafft, die Farbabweichung mal festzuhalten, damit ihr eine Vorstellung habt.

Die Nutzung von Delay() habe ich umgangen, das Problem besteht weiterhin.

#define FASTLED_ESP8266_RAW_PIN_ORDER
#include <FastLED.h>
#include <ESP8266WiFi.h>
#include <time.h>                       // time() ctime()
#include <sys/time.h>                  // struct timeval
#include "wordclock_words.c"
#include "wordclock_eeprom.h"
#include <Dusk2Dawn.h>

#define NUM_LEDS 140
#define NUM_LINES 11
#define DATA_PIN 2
#define SSID "..."
#define KEY "..."

#define TZ              1       // (utc+) TZ in hours
#define DST_MN          0      // use 60mn for summer time in some countries
#define TZ_SEC          ((TZ)*3600)
#define DST_SEC         ((DST_MN)*60)

CRGB leds[NUM_LEDS];
CRGB color_line [NUM_LINES];
CRGB color_line1 [NUM_LINES];
CRGB color_line2 [NUM_LINES];
CRGB color_ambient;

void word2stripe(const int[], int, CRGB color = CRGB::Orange);
void blankscreen(bool commit = false);

bool beforeSunrise = false;
bool afterSunset = false;
bool Ambient_on = false;

int laSunrise  = 0;
int laSunset   = 0;

byte brightness = 255;

timeval tv;
time_t now;

void setup() {

  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  blankscreen(true);

  // config time
  configTime(TZ_SEC, DST_SEC, "pool.ntp.org");

  //connect to WiFi
  WiFi.mode(WIFI_STA);
  WiFi.hostname("WordClock");
  WiFi.begin(SSID, KEY);
  while (WiFi.status() != WL_CONNECTED) {// show "KEIN FUNK"
    word2stripe(word_KEIN, sizeof(word_KEIN) / sizeof(int), CRGB::Red);
    word2stripe(word_FUNK, sizeof(word_FUNK) / sizeof(int), CRGB::Red);
    FastLED.show();
    delay(500);
  }

  blankscreen(true);

  //Farben definieren
  color_line1[1] = CRGB (102, 255, 255);    //kalt
...
  color_line1[10] = CRGB (0, 102, 0);
  color_line2[1] = CRGB (102, 255, 255);    //warm
...
  color_line2[10] = CRGB (255, 50, 0);

  gettimeofday(&tv, nullptr);
  now = time(nullptr);
}

uint32_t millis_start = 0;
uint32_t millis_delay = 0;
uint8_t hourx;
uint8_t minx;
uint8_t wdayx;
int timeinmin;

void loop() {
  if (millis() - millis_delay > (10000 - 677)) { //Anti-delay-Abfrage, Programmlaufzeit berücksichtigt, sonst wird Uhr ungenau
    if (millis() - millis_start > 18000000) {    //300 min vergangen, Zeit neu holen
      gettimeofday(&tv, nullptr);
      now = time(nullptr);
      millis_start = millis(); //Zeit in ms seit Arduinostart
    } else {
      now = now + 10; // 10 s draufaddieren
    }
    blankscreen(true);           // all pixels to black, "Aufräumen" der LEDs, sonst würden iwann alle leuchten
    Ambient_on = false;
    hourx = localtime(&now)->tm_hour;
    minx = localtime(&now)->tm_min;
    wdayx = localtime(&now)->tm_wday;
    timeinmin = hourx * 60 + minx;
    setbrightnes();
    if (LEDs_on()) {
      timeToStripe(hourx, minx);   // calculate time and fill leds array
      FastLED.setBrightness(brightness);
      FastLED.show();
    }
    //printdiagnosis();   //optional, falls Fehlerbehebung
    millis_delay = millis();
  }
}

// push word to leds array
void word2stripe(const int word[], int len, CRGB color) {
  for (int letter = 0; letter < len; letter++) {
    leds[word[letter]] = color;
  }
}

// blank screen (to specific color if given, and put it on the matrix if set)
void blankscreen( bool commit) {
  for (int led = 0; led < NUM_LEDS; led++) {
    leds[led] = CRGB(0, 0, 0);
    if (commit) {
      FastLED.show();
    }
  }
}

//sollen LEDs leuchten?
boolean LEDs_on() {
  if (  ((hourx == 6) &&
         (wdayx > 0 && wdayx < 6))  ||    // früh unter der Woche, 0= So, 1 = Mo, 5 = Fr, 6 = Sa
        ((hourx > 14 && hourx < 23) &&
         (wdayx > 0 && wdayx < 6))  ||    // Mo-Fr abend
        ((hourx > 6 && hourx < 23) &&
         (wdayx == 6 || wdayx == 0))) {    //tagsüber Samstag, Sonntag
    return true;
  } else {
    return false;
  }
}

//Mappingfunktion - Dreisatz
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}


//Helligkeit LEDs festlegen
void setbrightnes() {
  int high = 255;
  int low = 7;
  int atsun = 100;  //Helligkeit bei Sonnenauf/untergang
  int set_startdim = laSunset - 120;  // 2h vor Sonnenuntergang
  int set_enddim = laSunset + 240;
  int rise_startdim = laSunrise - 60;
  int rise_enddim = laSunrise + 180;
  if (timeinmin < set_startdim  && timeinmin > rise_enddim) {     //tagsüber
    brightness = high;
  }
  if (timeinmin > set_enddim || timeinmin < rise_startdim) {                   //nachts
    brightness = low;
  }
  if (timeinmin > set_startdim && timeinmin < set_enddim) {     // Zeitraum für Dimmen abends
    if (timeinmin < laSunset) {                            // Dimmen vor Sonnenuntergang
      brightness = mapfloat(timeinmin, laSunset, set_startdim, atsun, high );
    } else {                                                 // Dimmen nach Sonnenuntergang
      brightness = mapfloat(timeinmin, set_enddim, laSunset, low, atsun );
    }
  }
  if (timeinmin < rise_enddim && timeinmin > rise_startdim) {          // Zeitraum für Dimmen früh
    if (timeinmin > laSunset) {                        // Dimmen nach Sonnenaufgang
      brightness = mapfloat(timeinmin, laSunrise, rise_enddim, atsun, high );
    } else {                                            // Dimmen vor Sonnenaufgang
      brightness = mapfloat(timeinmin, rise_startdim, laSunrise, low, atsun );
    }
  }
}


//calculate if its sunset/rise
void isSun() {
  Dusk2Dawn datenschutz_ort(45, 16, 0);
  laSunrise  = datenschutz_ort.sunrise(2018, localtime(&now)->tm_mon, localtime(&now)->tm_mday, true);
  laSunset   = datenschutz_ort.sunset(2018, localtime(&now)->tm_mon, localtime(&now)->tm_mday, true);
  laSunrise = laSunrise + 100;   // notwendige Korrektur - keine Ahnung weshalb
  if (laSunrise + 120 > timeinmin) {
    beforeSunrise = true;
  }  else   {
    beforeSunrise = false;
  }
  if (laSunset - 60 < timeinmin) {
    afterSunset = true;
  } else {
    afterSunset = false;
  }
}


// push the time (words) to leds array
void timeToStripe(uint8_t hours, uint8_t mins) {

  //Farbe der Buchstaben korrekt auswählen
  isSun();
  if (afterSunset) {
    for (int line = 0; line < NUM_LINES; line++) {
      color_line[line] =  color_line2[line];
    }
  } else {
    for (int line = 0; line < NUM_LINES; line++) {
      color_line[line] =  color_line1[line];
    }
  }

  // show "ES IST"
  word2stripe(word_ES, sizeof(word_ES) / sizeof(int), color_line[1]);
  word2stripe(word_IST, sizeof(word_IST) / sizeof(int), color_line[1]);

  //Ambientebeleuchtung
  if (beforeSunrise) {
    color_ambient = CRGB (255, 255, 255);
    Ambient_on = true;
  } else if (beforeSunrise == false && afterSunset == false) {
    color_ambient = CRGB (0, 0, 0);
  } else if (afterSunset) {
    color_ambient = color_line2[10];
    Ambient_on = true;
  }
  word2stripe(ambient_OBEN, sizeof(ambient_OBEN) / sizeof(int), color_ambient);
  word2stripe(ambient_LINKS, sizeof(ambient_LINKS) / sizeof(int), color_ambient);
...

  if (mins >= 5 && mins < 10) {
    word2stripe(word_FUENF1, sizeof(word_FUENF1) / sizeof(int), color_line[1]);
    word2stripe(word_NACH, sizeof(word_NACH) / sizeof(int), color_line[4]);
  } else if (mins >= 10 && mins < 15) {
...

  int singleMinutes = mins % 5;
  switch (singleMinutes) {
    case 1:
      word2stripe(min_ONE, sizeof(min_ONE) / sizeof(int), color_line[1]);
      break;
...
  }

  switch (hours) {
    case 0:
      word2stripe(word_ZWOELF, sizeof(word_ZWOELF) / sizeof(int), color_line[9]);
      break;
    case 1:
      if (mins > 4) {
        word2stripe(word_EINS, sizeof(word_EINS) / sizeof(int), color_line[6]);
      } else {
        word2stripe(word_EIN, sizeof(word_EIN) / sizeof(int), color_line[6]);
      }
      break;
...
  }

}


// blank screen (to specific color if given, and put it on the matrix if set)
void printdiagnosis() {
...
}

Der Code ist an einigen Stellen etwas gekürzt, um die Übersichtlichkeit zu wahren. Die Struktur ist trotz der Kürzungen immer erkennbar. Falls ihr doch den kompletten Code mit dem ganzen unwichtigen Firlefanz außenrum braucht, schreibt einfach nochmal kurz.

Entweder das interne Temporal Dithering abschalten oder fps deutlich erhöhen oder FastLED.delay() anstelle von delay verwenden.

Details hier nachlesen.

If you are refreshing the LEDs less frequently (e.g., if you have a hundreds of LEDs, or computationally intensive animations), and you are running at a low brightness level, you may see the dithered pixel output as flickering, and you may want to turn it off if the effect is distracting. It's not magic; it's up to you what looks good in your projects.

Gruß, H.

Tatsache, temporal dithering ist die Ursache.

Nachteil ist jetzt natürlich, dass die Farben nicht mehr sauber wiedergegeben werden, das sieht man schon und ist nicht so schick.

Da werde ich bei Gelegenheit mal FastLED.delay probieren und das Blinkwithoutdelay wieder über Bord schmeißen.

Vielen Dank für eure Mühe!

Hi

bug123:
... und das Blinkwithoutdelay wieder über Bord schmeißen.

Lässt vermuten, daß Du 'Blink_without_delay' nicht verstanden hast oder was ganz anderes meinst.
Zumindest kann ich mir nicht vorstellen, daß Du den Ablauf jetzt wieder blockierend 'brauchst', damit die Farben der LEDs zusammen passen ...

MfG