Springe zum Hauptinhalt
t.animal
Anagraphein!
t.animal
Anagraphein!

Projekte

Wenn ich Zeit habe, bin ich am Basteln. Hier landet Dokumentation zu fertigen und halbfertigen Projekten.

Realistische LED-Kerzen

Es gibt ja zu Hauf "LED Kerzen" bei Amazon für wenige Euro zu kaufen. Allerdings sehen die meistens nicht sehr nach Kerze aus. Da ich jedes Jahr im Winter zur Dekoration Kerzen benötige und aus Sicherheitsgründen vor ein paar Jahren auf LED-Kerzen umgestiegen bin, wollte ich das einmal "richtig" machen - naja zumindest "richtiger".

Der erste Grund, warum LED-Kerzen immer kacke aussehen, ist die Lichtfarbe. Meistens bestehen die Kerzen aus einer LED, die mehr oder weniger gelbes Licht erzeugt. Manche Bastelanleitungen im Internet empfehlen eine gelbe und eine rote LED zu nehmen. Kerzenlicht ist aber viel "kälter" als man das so denkt, wie man im direkten Vergleich ganz gut erkennen kann. Daher habe ich eine RGB-LED mit mattem Köpfchen genommen und die einzelnen Kanäle mithilfe des Vorwiderstands gedimmt.

Das Ergebnis konnte einer kurzen Twitter-Umfrage stand halten. Die Vorwiderstände variieren vermutlich stark von LED zu LED und auch ein bisschen nach "Geschmack" welche Lichtfarbe persönlich am besten gefällt. Dennoch als Richtwerte hier die Vorwiderstände, die ich benutzt habe. Rot: 150Ω, Grün: 690Ω, Blau: 10kΩ.

LED mit Vorwiderständen auf Platine.

LED mit Vorwiderständen auf Platine.

Auf einer Lochrasterplatine lassen sich die Teile platzsparend und sicher anbringen. Anschließend kann man sie in das Gehäuse einer billigen LED-Kerze stecken, wenn man die originale LED rausbricht.

Ich wollte aber, dass die Lichter auch ein bisschen flackern. Also zwei ULN2003 Darlington-Transistor arrays als Treiber an einen Arduino Uno angeschlossen (meine LEDs fressen bis zu 60mA pro Stück - bei 13 LEDs viel mehr, als der Uno pro bereitstellen kann). Der IC ist glaube ich zwar etwas überdimensioniert, aber von Elektrotechnik versteh ich zu wenig um auf die schnelle was besseres zu finden und die Dinger sind billig.

Für den Code griff ich zunächst auf einen Blogpost zurück. Der dortige Code funktioniert auch prima, aber nicht bei 13 LEDs. Denn das Flackern wird immer für eine ganze LED auf einmal durchgeführt, was dazu führt, dass die Kerzen über kurz oder lang sequentiell flackern, weil die aktuelle LED die Aktivierung einer weiteren verhindert. Ein kurzer Versuch, das Problem mit einer scheduling library zu lösen war nicht erfolgreich, weil die Library beim delay-call nicht die Kontrolle übernimmt und andere Tasks einscheduled. Ausgehend dem Code aus den Blogpost, habe ich daher eine Kerzen-Klasse erstellt, die die Kontrolle beim Flackern nicht an sich reißt.

Schlussendlich flackern Kerzen meistens aufgrund von leichten Luftbewegungen und daher gleich mehrere auf einmal. Dass "zufällige Flackern" sah nur auf den ersten Blick realistisch aus. Erst als ich den Code dahingehend erweitert habe, dass nebeneinander platzierte Kerzen gleichzeitig flackern, wurden die Kerzen glaubwürdig.

Hier is der Code. Ich hab mir jetzt nicht die Mühe gemacht, Header und Implementierung zu trennen, denn mein C++ is etwas eingerostet und, hey, es is n arduino-Fummel-Projekt.

kerzen.ino (Source)

#include "SoftPWM.h"
int pulseMin = 1; // Minimum number of pulses in each set
int pulseMax = 5; // Maximum number of pulses in each set
int brightMin = 128; // Baseline LED brightness. Cannot exceed 128.
int minDelay = 3000; // Minimum delay between pulse sets (ms)
int maxDelay = 8000; // Maximum delay between pulse sets (ms)
int minPulseSpeed = 3; // Minimum dimming speed for pulses (lower is faster)
int maxPulseSpeed = 5; // Maximum dimming speed for pulses (lower is faster)
int minPulseGap = 10; // Minimum delay between two pulses (ms)
int maxPulseGap = 15; // Maximum delay between two pulses (ms)
const int numberOfCandles = 14;
//#define DEBUG
class Candle {
private:
    int pin;
    unsigned long nextActionTimestamp;
    int pulsesLeftInCurrentFlicker;
    
    int brightnessOfCurrentPulse;
    int currentBrightness;
    int currentPulseSpeed;
    int currentPulseDuration;
    Candle* adjacentCandles[numberOfCandles];
    bool triggerAdjacentFlicker = false;
public:
    Candle() : Candle(0){};
    Candle(int pin) : pin(pin) {
      for(int i=0; i<numberOfCandles; i++) {
        adjacentCandles[i] = NULL;
      }
      scheduleNextFlicker();
    }
    void addAdjacentCandle(Candle* otherCandle){
      for(int i=0; i<numberOfCandles; i++) {
        if(adjacentCandles[i] == NULL){
          adjacentCandles[i] = otherCandle;
          return;
        }
      }
    }
    void adjacentFlicker() {
      scheduleNextFlicker();
      nextActionTimestamp = millis();
      triggerAdjacentFlicker = false;
    }
    void execute() {
        if(nextActionTimestamp > millis()){
            return;
        }
        if(triggerAdjacentFlicker) {
          for(int i=0; i<numberOfCandles; i++) {
            if(adjacentCandles[i] != NULL){
              adjacentCandles[i]->adjacentFlicker();
            }
          }
          triggerAdjacentFlicker = false;
        }
        if(pulsesLeftInCurrentFlicker == 0) {
            scheduleNextFlicker();
            return;
        }
        if(currentBrightness != brightnessOfCurrentPulse) {
            brightnessStep();
            waitFor(currentPulseSpeed);
            return;
        }
        if(currentBrightness == brightMin){
            pulsesLeftInCurrentFlicker--;
            waitFor(currentPulseDuration);
            determineSettingsOfNextPulse();
        } else {
            brightnessOfCurrentPulse = brightMin;
        }
    }
private:
    void scheduleNextFlicker() {
        pulsesLeftInCurrentFlicker = random(pulseMin, pulseMax);
        determineSettingsOfNextPulse();
        triggerAdjacentFlicker = true;
        waitFor(random(minDelay, maxDelay));
    }
    void determineSettingsOfNextPulse() {
        currentPulseSpeed = random(minPulseSpeed, maxPulseSpeed);
        currentPulseDuration = random(minPulseGap, maxPulseGap);
        brightnessOfCurrentPulse = 224 - random(96);
    }
    void waitFor(int milliseconds){
        nextActionTimestamp = millis() + milliseconds;
    }
    void brightnessStep() {
        if(currentBrightness < brightnessOfCurrentPulse) {
            currentBrightness++;
        } else {
            currentBrightness--;
        }
        SoftPWMSet(pin, currentBrightness);
    }
};
Candle candles[numberOfCandles];
void setup()
{
    #ifdef DEBUG  
      Serial.begin(115200);
      Serial.println("started");
    #endif
    randomSeed(analogRead(A0));
    SoftPWMBegin();
    for (byte i = 0; i < numberOfCandles; i++) {
        candles[i] = Candle(i);
    }
    Candle *buerger = candles + 6;
    Candle *bild = candles + 3;
    Candle *erker = candles + 4;
    Candle *schmied = candles + 7;
    Candle *bauern = candles + 5;
    Candle *spitzgiebel = candles + 9;
    Candle *gasthaus = candles + 12;
    Candle *kapelle = candles + 11;
    Candle *rathaus = candles + 10;
    buerger->addAdjacentCandle(bild);
    buerger->addAdjacentCandle(bauern);
    buerger->addAdjacentCandle(erker);
    bild->addAdjacentCandle(buerger);
    bild->addAdjacentCandle(erker);
    erker->addAdjacentCandle(bild);
    erker->addAdjacentCandle(buerger);
    erker->addAdjacentCandle(schmied);
    spitzgiebel->addAdjacentCandle(gasthaus);
    gasthaus->addAdjacentCandle(spitzgiebel);
    gasthaus->addAdjacentCandle(kapelle);
    gasthaus->addAdjacentCandle(rathaus);
    kapelle->addAdjacentCandle(gasthaus);
    kapelle->addAdjacentCandle(rathaus);
    rathaus->addAdjacentCandle(kapelle);
    rathaus->addAdjacentCandle(gasthaus);
}
void loop()
{
    for (byte i = 0; i  < numberOfCandles; i++) {
        candles[i].execute();
    }
}