Adventskranz für Elektroniker

Inspiriert von der Idee von Alexander Weber mit seinem Geeky Advent auf tinkerlog wollte ich einen eigenen Adventskranz bauen. Basis sollte ein ATtiny85 mit 4 daran angeschlossenen LEDs werden, die man je nach Adventswoche um eine weitere "Kerze" durchschalten kann. Als Spannungsquelle sollen 5V über eine Micro-USB-Buchse von einer Powerbank, dem PC o.ä. kommen.

Prototyp

Um Aufbau und Programmierung leichter testen zu können, habe ich mich für einen schnellen Prototyp auf dem Breadboard unter der Verwendung eines Arduino Uno entschieden.

Verwendete Bauteile

Aufbau mit dem Arduino Uno

Schaltplan des Prototyps
Abb.: Schaltplan des Prototyps (Anstelle des Tilt-Schalters könnte auch jeder beliebige andere Schalter-Typ verwendet werden)

Für die Dimensionierung des Vorwiderstandes für die diffusen 10mm-LEDs habe ich folgende Daten zugrunde gelegt:
Vorwärtsspannung (gemessen): 1,82V
Betriebsspannung: 5V (vom Arduino/ATtiny)
Vorwiderstand: 220Ω
Stromstärke pro LED: 14,4mA
Verlustleistung am Widerstand: 45,79mW
Leistung der LED: 72mW

Sketch

Der der finale Adventskranz mit einem ATtiny85 laufen soll und dieser keine 4 PWM-Kanäle verfügbar hat, habe ich mich beim Prototypen nicht auf die Hardware-PWM-Pins verlassen, sondern die Library SoftPWM verwendet, die es ermöglicht ein PWM-Signal an jedem beliebigen Pin des Arduino Uno zu erstellen.
Da der Adventskranz sich auch über einen Reset oder eine Abschaltung merken soll, wie der letzte Stand der leuchtenden Kerzen (LEDs) war, benutze ich hier den EEPROM zum Speichern der Daten, der über eine EEPROM-Library leicht geschrieben und gelesen werden kann.

#include <SoftPWM.h>
#include <EEPROM.h>

#define BUTTON_DELAY 500
#define LED_DELAY    500
#define lmillis() ((long)millis())

const byte numLeds = 4;
const byte eepromAddress = 0;
const byte buttonPin = 7;
const byte *ledPins[numLeds] = {8, 9, 10, 11};
byte ledsLit = 0;
long lastButtonPress, lastLedToggle;

void setup()
{
    EEPROM.get(eepromAddress, ledsLit);
    ledsLit = max(0, min(4, ledsLit));

    pinMode(buttonPin, INPUT);
    SoftPWMBegin();
    for (byte i = 0; i < numLeds; i++) {
        SoftPWMSet(ledPins[i], 0);
        SoftPWMSetFadeTime(ledPins[i], LED_DELAY, LED_DELAY);
    }
    toggleLeds();
    lastButtonPress = lmillis() + BUTTON_DELAY;
    lastLedToggle = lmillis() + LED_DELAY;
}

void loop()
{
    if (lmillis() - lastButtonPress >= BUTTON_DELAY && digitalRead(buttonPin) == HIGH) {
        lastButtonPress = lmillis() + BUTTON_DELAY;
        ledsLit++;
        ledsLit %= (numLeds + 1);
        EEPROM.put(eepromAddress, ledsLit);
    }

    if (lmillis() - lastLedToggle >= LED_DELAY) {
        lastLedToggle = lmillis() + LED_DELAY;
        toggleLeds();
    }
}

void toggleLeds()
{
    for (byte i = 0; i < numLeds; i++) {
        if ((i + 1) <= ledsLit) {
            SoftPWMSet(ledPins[i], random(100, 256));
        } else {
            SoftPWMSet(ledPins[i], 0);
        }
    }
}
Video: Durch Berührung des Tilt-Schalters können die einzelnen "LED-Kerzen" angeschaltet werden. Da im EEPROM des Arduino der Status gespeichert wird, geht auch nach einem Reset (oder Ausschalten) des Arduino der Stand der beleuchteten Kerzen nicht verloren.

Adventskranz mit dem ATtiny85

Um den Adventskranz auf einen ATtiny85 zu verkleinern, müssen einige Änderungen zum Prototypen gemacht werden. Es hat sich herausgestellt, dass der Tilt-Schalter zu empfindlich ist und daher wird ein normaler Mikrotaster verwendet.

Verwendete Bauteile

Aufbau

Schaltplan mit dem ATtiny85

Da es mir auch nach etlichen Versuchen nicht gelungen ist, mit dem PWM-Code einen zusätzlichen Button auf Pin PB2 auszulesen, um die Kerzen durchzuschalten, entschied ich mich den Button auf den RESET-Pin des ATtiny85 zu legen und so ein Durchschalten zu ermöglichen.

Sketch I. - Flackernde LEDs

Da der ATtiny85 nur über zwei PWM-Pins verfügt, konnte SoftPWM nicht einfach verwendet werden und ich habe daher auf den Code von David Johnson-Davies zurückgegriffen, der erklärt, wie man 4× PWM-Ausgänge am ATtiny85 erhält.
Der folgende Sketch demonstriert vier zufällig flackernde LEDs:

// ATtiny85 outputs
const byte* pins[4] = {0, 1, 4, 3};

void setup()
{
    for (byte i = 0; i < 4; i++) {
        pinMode(pins[i], OUTPUT);
    }

    // Configure counter/timer0 for fast PWM on PB0 and PB1
    TCCR0A = 3 << COM0A0 | 3 << COM0B0 | 3 << WGM00;
    TCCR0B = 0 << WGM02 | 3 << CS00; // Optional; already set

    // Configure counter/timer1 for fast PWM on PB4
    TCCR1 = 1 << CTC1 | 1 << PWM1A | 3 << COM1A0 | 7 << CS10;
    GTCCR = 1 << PWM1B | 3 << COM1B0;

    // Interrupts on OC1A match and overflow
    TIMSK = TIMSK | 1 << OCIE1A | 1 << TOIE1;
}

ISR(TIMER1_COMPA_vect)
{
    if (!bitRead(TIFR, TOV1)) bitSet(PORTB, 3);
}

ISR(TIMER1_OVF_vect)
{
    bitClear(PORTB, 3);
}

void loop()
{
    OCR0A = random(0, 50);
    OCR0B = random(0, 50);
    OCR1A = random(0, 50);
    OCR1B = random(0, 50);
    delay(100);
}
4 flackernde LEDs mit dem ATtiny85
Abb.: 4 flackernde LEDs mit dem ATtiny85

Sketch II. - EEPROM ansprechen

Der zweite Sketch steuert den EEPROM des ATtiny85 an und soll den Zustand der brennenden LEDs speichern. Zum Glück kann die Library EEPROM.h ebenfalls verwendet werden und somit ist die Adaptierung auf den ATtiny recht einfach. Hier wird auch schon der Mikrotaster verwendet, der ohne einen externen Pullup-Widerstand auskommt, denn dieser wird per Software dazugeschaltet:

#include <EEPROM.h>

#define EEPROM_ADDRESS 0
#define PIN_BUTTON     A1
#define BUTTON_DELAY   500
#define LED_AMOUNT     4
#define lmillis() ((long)millis())

const byte *ledPins[LED_AMOUNT] = {0, 1, 4, 3};
long lastButtonPress;
byte ledsLit = 0;

void setup()
{
    ledsLit = max(0, min(LED_AMOUNT, EEPROM.read(EEPROM_ADDRESS)));

    pinMode(PIN_BUTTON, INPUT_PULLUP);
    for (byte i = 0; i < LED_AMOUNT; i++) {
        pinMode(ledPins[i], OUTPUT);
    }
    showLeds();

    lastButtonPress = lmillis() + BUTTON_DELAY;
}

void loop()
{
    if (lmillis() - lastButtonPress >= BUTTON_DELAY && analogRead(PIN_BUTTON) < 500) {
        lastButtonPress = lmillis() + BUTTON_DELAY;
        ledsLit++;
        ledsLit %= (LED_AMOUNT + 1);
        EEPROM.write(EEPROM_ADDRESS, ledsLit);
        showLeds();
    }
}

void showLeds()
{
    for (byte i = 0; i < LED_AMOUNT; i++) {
        if ((i + 1) <= ledsLit) {
            digitalWrite(ledPins[i], HIGH);
        } else {
            digitalWrite(ledPins[i], LOW);
        }
    }
}
EEPROM des ATtiny85 ansprechen
Abb.: Durch das Drücken des Mikrotasters werden die einzelnen LEDs durchgeschaltet und der Status im EEPROM des ATtiny85 gespeichert.

Sketch III. - ATtiny85-Adventskranz

In diesem finalen Sketch wird die aktuelle Anzahl der leuchtenden LEDs durch einen RESET des ATtiny ausgelöst, d.h. bei jedem Neustart wird die setup()-Funktion aufgerufen und der Wert der leuchtenden LEDs erhöht.
Desweiteren ist das Flackern der Kerzen nun etwas unregelmäßiger/natürlicher, denn jede LED hat nun ihre eigene Verzögerungszeit bekommen.

#include <EEPROM.h>

#define EEPROM_ADDRESS 0
#define LED_AMOUNT     4
#define lmillis() ((long)millis())

const byte *ledPins[LED_AMOUNT] = {0, 1, 4, 3};
long *nextFlicker[LED_AMOUNT] = {0, 0, 0, 0};
byte ledsLit = 0;

void setup()
{
  for (byte i = 0; i < LED_AMOUNT; i++) {
    pinMode(ledPins[i], OUTPUT);
    toggleLed(ledPins[i], 255);
  }

  // Configure counter/timer0 for fast PWM on PB0 and PB1
  TCCR0A = 3 << COM0A0 | 3 << COM0B0 | 3 << WGM00;
  TCCR0B = 0 << WGM02 | 3 << CS00; // Optional; already set

  // Configure counter/timer1 for fast PWM on PB4
  TCCR1 = 1 << CTC1 | 1 << PWM1A | 3 << COM1A0 | 7 << CS10;
  GTCCR = 1 << PWM1B | 3 << COM1B0;

  // Interrupts on OC1A match and overflow
  TIMSK = TIMSK | 1 << OCIE1A | 1 << TOIE1;

  ledsLit = ((byte)EEPROM.read(EEPROM_ADDRESS) + 1) %  (LED_AMOUNT + 1);
  EEPROM.write(EEPROM_ADDRESS, ledsLit);
}

ISR(TIMER1_COMPA_vect)
{
  if (!bitRead(TIFR, TOV1)) {
    bitSet(PORTB, 3);
  }
}

ISR(TIMER1_OVF_vect)
{
  bitClear(PORTB, 3);
}

void loop()
{
  for (byte i = 0; i < LED_AMOUNT; i++) {
    if (lmillis() - (long)nextFlicker[i] >= 0) {
      nextFlicker[i] = lmillis() + (long)random(10, 1500);
      if ((i + 1) <= ledsLit) {
        toggleLed(ledPins[i], random(0, 50));
      } else {
        toggleLed(ledPins[i], 255);
      }
    }
  }
}

void toggleLed(byte ledPin, byte ledValue)
{
  switch (ledPin) {
    case 0:
      OCR0A = ledValue;
      break;
    case 1:
      OCR0B = ledValue;
      break;
    case 4:
      OCR1A = ledValue;
      break;
    case 3:
      OCR1B = ledValue;
      break;
  }
}
Reset des ATtiny85 mit Mikrotaster
Abb.: Testen des fertigen Sketches mit dem Aufbau auf dem Breadboard (Reset des ATtiny85 mit Mikrotaster)

Aufbau mit ATtiny85 (Freeform)

Der fertige Aufbau soll in Freeform, d.h. ohne Platine, erfolgen. Als Spannungsversorgung soll eine Mikro-USB-Buchse dienen und da ich einen kleinen Anschluss aus einem alten USB-Adapter wiederverwenden konnte, wurde dieser verwendet.

Platine mit Mikro-USB-Anschluss
Abb.: Platine mit Mikro-USB-Anschluss aus altem Adapter-Gerät
Kontakte der Platine
Abb.: Kontakte der Platine
Löten der Messingdrähte
Abb.: Einige Messingdrähte (Durchmesser: 0,8mm) wurde gebogen und verlötet.
Drahtring mit Beinen
Abb.: Resultat ist ein Ring mit Beinen als Abstandshalter.
Mikrotaster auf Platine
Abb.: Der Mikrotaster für den RESET-Knopf und ein 10kΩ-Widerstand werden auf der Platine verlötet.
LED wird auf Ring verlötet
Abb.: Die LEDs werden mit der Kathode (-) auf dem Messingring verlötet
ATtiny85 auf Platine
Abb.: Nachdem der ATtiny85 mit dem fertigen Sketch programmiert wurde, kann er dem Aufbau hinzugefügt werden.
Fertiger LED-Adventskranz
Abb.: Fertiger LED-Adventskranz

Da nach dem Aufbau die Platine nur sehr wackelig stehen wollte, habe ich kurzerhand eine Aufnahme mit dem 3D-Drucker gefertigt:

Standfuß für ATtiny85-Adventskranz

Standfuß für ATtiny85-Adventskranz advent_mount.scad
Standfüße für den LED-Adventskranz
Abb.: Zwei selbstgedruckte Standfüße geben dem LED-Adventskranz mehr Halt und sorgen zusätzlich für etwas Abstand zur Unterlage.
Video: Demonstration des fertigen LED-Adventskranzes mit ATtiny85
zurück