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.
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.
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
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);
}
}
}
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.
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.
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);
}
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);
}
}
}
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;
}
}
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.
Da nach dem Aufbau die Platine nur sehr wackelig stehen wollte, habe ich kurzerhand eine Aufnahme mit dem 3D-Drucker gefertigt: