E
S
I
I
S
T
C
K
U
R
Z
H
U
M
L
V
I
E
R
T
E
L
I
E
Z
W
A
N
Z
I
G
B
F
Ü
N
F
Z
E
H
N
E
M
I
N
U
T
E
N
V
O
R
N
A
C
H
D
H
A
L
B
D
R
E
I
A
C
H
T
Z
W
E
I
S
E
C
H
S
E
L
F
Z
E
H
N
V
I
E
R
Z
W
Ö
L
F
Ü
N
F
E
I
N
S
I
E
B
E
N
E
U
N
U
H
R
I
M
O
R
G
E
N
S
C
A
B
E
N
D
S
N
A
C
H
T
S
H
N
A
C
H
M
I
T
T
A
G
S

Anleitung: Wortuhr im Bilderrahmen

Ein Mikrocontroller steuert 144 kleine LEDs, die sich in einem 12x12-großen Raster in einem Bilderrahmen verstecken. Eine schwarze Schablone mit transparenten Buchstaben lässt die Uhr Wörter und Sätze bilden. Dank des batteriebetriebenen Echtzeituhr-Moduls weiß die Wortuhr auch nach unterbrochenener Stromversorgung noch, wie spät es ist und hin und wieder zeigt sie auch mal eine versteckte Extra-Botschaft an.

Wer findet die versteckte Nachricht?

Achtung!

Dies ist mehr ein Erfahrungsbericht als eine zuverlässige Anleitung. Es gibt keine Garantie für eine korrekte (geschweige denn sinnvolle ;-)) elektronische Verschaltung. Die Haftung für kaputte Bauteile, versengte Augenbrauen oder andere mögliche Schäden beim Umgang mit elektrischem Strom trägt jeder selbst.

Das Material

Die folgende Aufstellung führt die benötigten Utensilien zum Nachbau der Wortuhr auf. Die größte Schwierigkeit besteht darin, ein Gehäuse mit ausreichender Tiefe zu finden, in dem alles verstaut werden kann. Die Wahl fiel hier auf einen Bilderrahmen des Modells „Ribba“ von IKEA.

Ein Multimeter kann auch nicht schaden.
  • Quadratischer, tiefer Rahmen, Innenmaße ca. 21 cm x 21 cm x 4 cm
  • Arduino Nano (oder nachgebautes Produkt)
  • LED-Streifen WS2812B mit 144 LEDs mit Abständen von 1,67 cm (60 LEDs/m)
  • RTC-Modul DS3231
  • Kondensator mit der Kapazität 1000 µF
  • Widerstand mit etwa 400–500 Ω
  • Netzteil 5 V, 2 A, mit passender Buchse
  • 2 Mini-USB-Kabel, eines mit Anschluss für den PC
  • Litzen, Steckverbinder, Lötkolben u.ä.
  • Schwarze Pappe, bedruckbare Folien, Bastelmaterial

Die Elektronik

5V Din GND 5V Do GND 5V Din GND 5V Do GND 1000µF 440Ω 5V Din GND 5V Do GND 5V Din GND 5V Do GND 5V Din GND 5V Do GND WS2812B Mini- USB 5V Din GND 5V Do GND 32K SCL SDA GND VCC SQW SCL SDA VCC GND DS3231 5V Din GND 5V Do GND DC 5V 2A Rx D6~ L D3~ RST A6 A0 D13 Pwr D12 D2 A7 D11~ RX<-0 3V3 RST D9~ A3 D10~ A1 TX->1 D8 AREF GND GND 5V Vin A4/SDA D5~ D7 A2 D4 A5/SCL ARDUINO NANO Tx 5V Din GND 5V Do GND . . . . . . . . . +5V SDA SCL +5V +3,3V GND +5V
Schaltplan (Anklicken zum Vergrößern)

Die WS2812B-LEDs werden in zwölf Streifen mit je 12 LEDs zertrennt und im Abstand von 1,67 cm (Mitte-Mitte) auf die Rückwand des Bilderrahmens geklebt. Nun muss jeder Streifen mit 5 V und mit Masse vom Netzteil versorgt werden. Außerdem werden die Datenleitungen miteinander verbunden, wobei auf die richtige Polarität geachtet werden muss: Din der ersten LED wird über einen Widerstand mit etwa 400–500 Ω mit einem Digitalpin des Arduinos verbunden. Do der letzten LED dieser Zeile wird mit Din der nächsten Zeile verbunden und so weiter. Die LED-Kette läuft dann beispielsweise serpentinenartig von oben nach unten:

Die erste LED ist oben rechts, die 144. LED unten rechts.

Um die aktuelle Zeit abrufen zu können, wird das Echtzeituhr-Modul DS3231 mit den SDA- und SCL-Pins des Arduinos verbunden. Die Uhr wird mit 3,3 V Spannung versorgt. Damit die Zeit auch ohne Netzspannung weiterläuft, braucht das Modul eine handelsübliche CR2032-Batterie oder einen Akku vom Typ LIR-2032.

Hinweis

Wenn eine Batterie und kein Akku verwendet wird, muss die elektrische Verbindung zwischen VCC und der Anode des Batteriehalterung unterbrochen werden, beispielsweise durch das Entfernen der dazwischenliegenden Diode. Andernfalls würde eine Spannung an der nicht-wiederaufladbaren Batterie anliegen, wodurch die Batterie beschädigt werden kann.

Ohne die Diode liegt keine Netzspannung an der Batterie mehr an.

Der Arduino selbst wird über den USB-Anschluss mit Strom versorgt. Dazu kann ein Mini-USB-Kabel verwendet werden, das durchgeschnitten und vorsichtig abisoliert wird. Die rote Ader aus dem Kabel wird mit 5 V verbunden, die schwarze Ader mit Masse. Ein Kondensator mit 1000 µF puffert etwaige Spannungsspitzen aus dem Netzteil.

Die Schablone

Die Schablone besteht aus 144 Buchstaben und passt auf eine DIN-A4-Seite. Die Formulierungen
Es ist (um/kurz/viertel/fünf/zehn/zwanzig) (Minuten) (vor/nach) (halb) 1–12 Uhr (morgens/mittags/nachmittags/abends/nachts)
können mit Überlappungen in 132 Buchstaben untergebracht werden, sodass zwölf Buchstaben (in der Vorlage X) zur freien Verwendung übrig bleiben. Natürlich ist es auch denkbar, die bereits verwendeten Buchstaben für eine individuelle Botschaft mitzunutzen oder andere Formulierungen für die Uhrzeiten zu wählen. Zum Bearbeiten der Vorlage empfiehlt sich zum Beispiel Inkscape.

E S X I S T X K U R Z XU M X V I E R T E L X XZ W A N Z I G X F Ü N FZ E H N X M I N U T E NV O R N A C H X H A L BD R E I A C H T Z W E IS E C H S E L F Z E H NV I E R Z W Ö L F Ü N FE I N S I E B E N E U NU H R X M O R G E N S XA B E N D S N A C H T SX N A C H M I T T A G S
Schablone (Anklicken zum Herunterladen)

Mit einer dicktengleiche Schrift (monospace) liegen die Buchstaben, die in dieser Vorlage durch Leerzeichen getrennt sind, genau übereinander. Die Schablone muss nun – am besten in zweifacher Ausfertigung für eine stärkere Opazität (Lichtundurchlässigkeit) – auf Folie gedruckt werden.

Der Zusammenbau

Beim Zusammensetzen der Uhr ist noch etwas Bastelarbeit gefragt. In den Rahmen werden auf die Acrylglasscheibe die zwei bedruckten Folien (ggf. mit Passepartout aus schwarzer Pappe) exakt übereinander gelegt. Danach folgen einige Lagen genarbter Prospekthüllen, um das Licht zu streuen und so die Buchstaben gleichmäßiger auszuleuchten. Wichtig ist jetzt ein etwa 1 cm hohes Raster, das zum Beispiel aus 2 x 11 schwarzen Pappstreifen in stundenlanger Pfriemelei gebastelt werden kann (Zuschriften mit besseren Ideen sind herzlich willkommen).
Das Raster sorgt für eine bessere Ausleuchtung der Buchstaben.
Zum Schluss wird die Rückwand mit den LEDs eingesetzt und die Elektronik so angebracht, dass sie gut sitzt, aber auch noch Spiel hat, um später noch den USB-Anschluss umstecken zu können. Auch sollte an eine Bohrung für den Stromanschluss gedacht werden.
Arduino und Echtzeituhr finden auf der Rückseite des Rahmens Platz.
Allgemein können zu diesen Bastelschritten nicht viele Tipps gegeben werden, weil die Vorgehensweise stark davon abhängt, wie die Abmessungen des Rahmens sind. Auch hat IKEA den Aufbau des „Ribba“-Rahmens in den letzten Jahren verändert.
Die fertige und bereits beleuchtete Uhr.

Das Programm

Um die Uhr zum Leuchten zu bringen, werden die Bibliotheken Time, DS1307RTC und Adafruit NeoPixel benötigt, die in den Paketquellen der Arduino IDE zu finden sind. Anschließend werden einige Konstanten definiert: Für jedes Wort auf der Uhr müssen die Positionen der LEDs abgezählt werden und diese werden dann zusammen mit der Wortlänge in einen Array geschrieben. Außerdem werden im RGB-Format {[0–255], [0–255], [0–255]} die Farben angegeben, in denen die Uhr leuchten soll. Die Farbe wechselt hier je nach Wochentag, wobei für die Zeit zwischen 22 und 8 Uhr jeweils zusätzlich eine gedimmte Farbe angegeben ist. Es erfordert durchaus etwas Ausprobieren, Farben zu finden, die die Buchstaben schön und gleichmäßig ausleuchten. Grundsätzlich sollte hier ein Bruchteil der maximalen LED-Helligkeitswerte ausreichen. Bei größerer Leuchtstärke reicht ein 2-A-Netzteil möglicherweise nicht mehr aus, um die LEDs mit Strom zu versorgen.

/*
Wortuhr.ino by Walfried Schneider, www.walfriedschneider.de
Controlling a wordclock with WS2812B LED strips and a DS3231 RTC module using the following libraries:
  Time, 1.6.0, https://github.com/PaulStoffregen/Time
  DS1307RTC, 1.4.1, https://github.com/PaulStoffregen/DS1307RTC
  Adafruit NeoPixel, 1.8.1, https://github.com/adafruit/Adafruit_NeoPixel
*/

// Include libraries
#include "TimeLib.h"
#include "DS1307RTC.h"
#include "Adafruit_NeoPixel.h"

// Set the Arduino pin connected to the LEDs
#define LED_PIN 2

// Initialise 144 NeoPixel LEDs
Adafruit_NeoPixel Leds = Adafruit_NeoPixel(144, LED_PIN, NEO_GRB + NEO_KHZ800);

// Set the locations of words/letters
const byte esist[5+1] = {5,144,143,141,140,139};
const byte kurz[4+1] = {4,137,136,135,134};
const byte viertel[7+1] = {7,124,125,126,127,128,129,130};
const byte zwanzig[7+1] = {7,120,119,118,117,116,115,114};
const byte fuenf[4+1] = {4,112,111,110,109};
const byte zehn[4+1] = {4,97,98,99,100};
const byte minuten[7+1] = {7,102,103,104,105,106,107,108};
const byte vor[3+1] = {3,96,95,94};
const byte nach[4+1] = {4,93,92,91,90};
const byte halb[4+1] = {4,88,87,86,85};
const byte stunden[][7] = {{5,53,54,55,56,57},{3,48,47,46},{4,81,82,83,84},{4,73,74,75,76},{4,49,50,51,52},{4,57,58,59,60},{5,72,71,70,69,68},{6,45,44,43,42,41,40},{4,77,78,79,80},{4,40,39,38,37},{4,64,63,62,61},{3,67,66,65}};
const byte s[1+1] = {1,45};
const byte uhr[3+1] = {3,25,26,27};
const byte morgens[7+1] = {7,29,30,31,32,33,34,35};
const byte abends[6+1] = {6,24,23,22,21,20,19};
const byte nachts[6+1] = {6,18,17,16,15,14,13};
const byte nachm[4+1] = {4,2,3,4,5};
const byte mittags[7+1] = {7,6,7,8,9,10,11,12};
const byte botschaft[12+1] = {12,142,138,133,123,125,126,113,107,73,76,78,79};

// Specify colors for Su-Mo (day colors followed by night colors) in RGB mode
const byte colorset[14][3] = {{36,8,0},{0,24,8},{16,16,0},{24,0,0},{0,16,16},{0,32,0},{0,8,24},{9,2,0},{0,8,2},{8,8,0},{8,0,0},{0,4,4},{0,8,0},{0,4,12}};

// Declare necessary variables
byte c;
byte i;

// Sync with RTC and start LEDs
void setup() {
  setSyncProvider(RTC.get);
  Leds.begin();
  i = 0;
}

// Update LEDs according to time and day of the week every 10 seconds
// Show special message every 102 * 10 seconds = 17 minutes
void loop() {
  Leds.clear();
  c = getColor(hour(), weekday());
  if (i == 0){
    turnOn(botschaft, c);
  }
  else {
    setMinutes(minute(), c);
    setHour(minute(), hour(), c);
  };
  Leds.show();
  i = (i+1)%102;
  
  delay(10000);
}

// Set LEDs according to (m)inutes: es ist, kurz, viertel, zwanzig, fünf, zehn, minuten, vor, nach, halb
void setMinutes(byte m, byte c) {
  turnOn(esist, c);
  if ((m > 0 && m < 3) || m == 28 || m == 29 || m == 31 || m == 32 || m > 57 ){turnOn(kurz, c);}
  if ((m > 12 && m < 18) || (m > 42 && m < 48)){turnOn(viertel, c);}
  if ((m > 17 && m < 23) ||(m > 37 && m < 43)){turnOn(zwanzig, c);}
  if ((m > 2 && m < 8) ||(m > 22 && m < 28) || (m > 32 && m < 38) ||(m > 52 && m < 58)){turnOn(fuenf, c);}
  if ((m > 7 && m < 13) || (m > 47 && m < 53)){turnOn(zehn, c);}
  if ((m > 2 && m < 13) || (m > 17 && m < 28) || (m > 32 && m < 43) || (m > 47 && m < 58)){turnOn(minuten, c);}
  if ((m > 22 && m < 30) || (m > 37)){turnOn(vor, c);}
  if ((m > 0 && m < 23) || (m > 30 && m < 38)){turnOn(nach, c);}
  if (m > 22 && m < 38){turnOn(halb, c);}
}

// Set LEDs according to (h)ours: 1-12, uhr, ein(s), nachts, morgens, mittags, nachmittags, abends
void setHour(byte m, byte h, byte c) {
  byte h2;
  if (m < 23) {h2 = h;}
  else {h2 = h+1;}
  turnOn(stunden[h2%12], c);
  
  if (m < 3 || m > 57){turnOn(uhr, c);}
  else if (h2%12 == 1) {turnOn(s, c);}

  if (h2 < 6 || h2 > 22){turnOn(nachts, c);}
  if (h2 > 5 && h2 < 12){turnOn(morgens, c);}
  if (h2 > 11 && h2 < 18){turnOn(mittags, c);}
  if (h2 > 14 && h2 < 18){turnOn(nachm, c);} 
  if (h2 > 17 && h2 < 23){turnOn(abends, c);} 
  }

// Turn the LEDs on
void turnOn(byte words[], byte c) {
  for(byte i = 1; i < words[0] + 1; i++){
  Leds.setPixelColor(words[i]-1, Leds.Color(colorset[c][0],colorset[c][1],colorset[c][2]));
  };
};

// Get the day or night color for a time and day of the week
byte getColor(byte h, byte w) {
  if (h > 8 && h < 22) {
    return w-1;
  }
  else {
    return w+6;
  }
};

Die Wortuhr fragt alle 10 Sekunden die aktuelle Zeit ab und entscheidet dann nach einem Algorithmus, welche LEDs angeschaltet werden müssen. Alle 102 Zyklen, also alle 17 Minuten, wird als Extrafunktion statt der Uhrzeit die zusätzliche Botschaft angezeigt.

Welche Farbe darf es sein?

Die Uhrzeit einstellen

Das Programm der Uhr arbeitet stets mit der Lokalzeit. Vor der ersten Verwendung und jedes halbe Jahr (Stand: 2021 ;-)) muss die Uhrzeit neu eingestellt werden. Dabei ist das Skript TimeRTCSet aus der Bibliothek Time behilflich, das auf den Arduino hochgeladen werden muss und dann einen Zeitstempel auf der USB-Verbindung entgegennimmt. Unter Linux kann mit folgenden Befehlen die aktuelle Uhrzeit an das USB-Gerät ttyUSB0 gesendet werden:

exec 3<> /dev/ttyUSB0
date +T%s --date="7202 seconds" >&3

Hierbei gibt date +T%s den aktuellen Unix-Zeitstempel aus und --date="7202 seconds" korrigiert die Unixzeit zur Lokalzeit, indem beispielsweise eine ("3600 seconds", für MEZ) oder zwei Stunden ("7200 seconds", für MESZ) plus ggf. einige Sekunden technische Verzögerung angehängt werden. Die Befehle müssen von einem Unix-Benutzer ausgeführt werden, der Schreibzugriff auf /dev/ttyUSB0 hat. Zum Schluss wird das Wortuhr-Programm wieder auf den Mikrocontroller zurückgeschrieben.