Bastelanleitung: Wortuhr mit Arduino
Ein Mikrocontroller steuert 144 LEDs, die sich hinter 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 immer wie spät es ist und hin und wieder zeigt sie auch mal eine versteckte Extra-Botschaft an.

Das Material
Die folgende Aufstellung führt die benötigten Utensilien zum Bau 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. (Update:) Der Rahmen wird mittlerweile nicht mehr angeboten.

- Quadratischer, tiefer Bilderrahmen mit Innenmaßen von ca. 21 cm x 21 cm x 4 cm
- Mikrocontroller 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, mindestens eines mit Anschluss für den PC auf der anderen Seite
- Litzen, Steckverbinder, Lötkolben u.ä.
- Schwarze Pappe, bedruckbare Folien, Bastelmaterial
Die Elektronik
Die WS2812B-LEDs werden in zwölf Streifen mit je 12 LEDs zertrennt und im Abstand von 1,67 cm (von Mitte zu Mitte) auf die Rückwand des Bilderrahmens geklebt. Nun muss jeder Streifen mit 5 V und mit der Masse vom Netzteil versorgt werden. Außerdem müssen nun die Datenleitungen der Streifen wieder miteinander verbunden werden, wobei auf die richtige Polarität geachtet werden muss: Zunächst wird der Din
-Kontakt der allerersten LED wird über einen Widerstand (etwa 400–500 Ω) mit einem Digitalpin des Arduinos verbunden. Dann wird Do
der letzten LED in der ersten Reihe mit Din
der nächsten Reihe verbunden und so weiter. Die LED-Kette läuft in unserem Fall in Serpentinen von oben nach unten:

Um die Zeit speichern und 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 Uhrzeit auch ohne Netzspannung weiterläuft, braucht das Modul noch eine handelsübliche CR2032-Batterie oder einen Akku vom Typ LIR-2032.
Der Mikrocontroller 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 der Masse. Ein Kondensator mit 1000 µF puffert etwaige Spannungsspitzen aus dem Netzteil.
Die Schablone
Meine Schablone besteht aus 144 Buchstaben und passt auf eine DIN-A4-Seite. Die Wörter
Es ist
um|kurz|fünf|zehn|viertel|zwanzig
Minuten
vor|nach
halb
ein(s)|zwei|drei|vier|fünf|sechs|sieben|acht|neun|zehn|elf|zwölf
Uhr
morgens|mittags|nachmittags|abends|nachts
können mit Überlappungen in 132 Buchstaben untergebracht werden, sodass zwölf Buchstaben zur freien Verwendung übrig bleiben (in der Vorlage X). Natürlich ist es auch denkbar, für die Zeitausgabe verwendete Buchstaben für eine individuelle Botschaft mitzunutzen oder andere Formulierungen für die Uhrzeiten zu wählen. Zum Bearbeiten meiner Vorlage empfiehlt sich zum Beispiel Inkscape.
Mit einer dicktengleichen Schrift (monospace) liegen die Buchstaben genau übereinander. Die Schablone muss nun – am besten in zweifacher Ausfertigung für eine höhere Opazität (Lichtundurchlässigkeit) – auf Folie gedruckt werden.
Der Zusammenbau
Beim Zusammensetzen der Uhr ist noch etwas Bastelarbeit gefragt. In den Rahmen werden zunächst die zwei bedruckten Folien (ggf. mit Passepartout aus schwarzer Pappe) auf die Acrylglasscheibe gelegt – und zwar möglichst exakt übereinander. Danach folgen einige Lagen genarbter Prospekthüllen, die das Licht streuen, sodass die Buchstaben später gleichmäßiger ausgeleuchtet werden. Wichtig ist dann das etwa 1 cm hohe Raster, das zum Beispiel aus 2 x 11 schwarzen Pappstreifen in stundenlanger Pfriemelei gebastelt werden kann (Zuschriften mit besseren Ideen sind herzlich willkommen).

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 den USB-Anschluss umstecken zu können. Auch an eine Bohrung für den Stromanschluss sollte gedacht werden.

Allgemein können zu diesen Bastelschritten nicht viele Tipps gegeben werden, weil die Vorgehensweise stark davon abhängt, wie die Abmessungen des Rahmens sind. IKEA hat den Aufbau des „Ribba“-Rahmen in den letzten Jahren etwas verändert. (Update:) Dieser Rahmen ist nicht mehr im Angebot.

Das Programm
Um die Uhr zum Leuchten zu bringen, werden die Bibliotheken Time
, DS1307RTC
und Adafruit NeoPixel
benötigt, die über die Arduino IDE einfach zu installieren sind. In meinem Code habe ich zunächst einige Konstanten definiert: Für jedes Wort auf der Uhr ein Array mit der Wortlänge und die Positionen aller LEDs, die dazu gehören. Außerdem sollte man sich jetzt Gedanken darüber machen, in welchen Farben die Uhr leuchten soll. Die definiert man als RGB: {[0–255], [0–255], [0–255]}. Bei mir wechselt die Farbe mit dem Wochentag, wobei das Leuchten zwischen 22 und 8 Uhr gedimmt wird. Für die Uhr reicht grundsätzlich ein Bruchteil der maximalen LED-Helligkeitswerte aus. Für volle Helligkeit liefert das 2-A-Netzteil auch nicht genügend Strom.
/* Wortuhr.ino by Lutz Schneider, https://lutzschneider.eu 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 // 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}; // Specify colors for Su-Mo (7 day colors followed by 7 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}}; tmElements_t tm; // Initialise 144 NeoPixel LEDs Adafruit_NeoPixel Leds = Adafruit_NeoPixel(144, LED_PIN, NEO_GRB + NEO_KHZ800); // Sync with RTC and start LEDs void setup() { setSyncProvider(RTC.get); Leds.begin(); } // Update LEDs according to time and day of the week every second void loop() { if (RTC.read(tm)) { if (isSummer(tm)) { breakTime(makeTime(tm)+3600, tm); } Leds.clear(); byte color = getColor(tm.Hour, tm.Wday); setMinutes(tm.Minute, color); setHour(tm.Minute, tm.Hour, color); Leds.show(); } delay(1000); } // Return true if a given date is between the last sunday of March and the last sunday of October, 2 a.m. bool isSummer(tmElements_t tm) { if (tm.Month < 3 || tm.Month > 10) { return false; } else if (tm.Month > 3 && tm.Month < 10) { return true; } else if (tm.Month == 3) { return tm.Day - tm.Wday >= 24 && (tm.Wday != 1 || tm.Hour >= 2); } else { return tm.Day - tm.Wday < 24 || tm.Wday == 1 && tm.Hour < 2; } } // Set LEDs for the minutes: 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 for the hours: 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); } } // Switch 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])); }; }; // Return the day or night color for a given time and day of the week byte getColor(byte h, byte w) { if (h > 8 && h < 22) { return w-1; } else { return w+6; } };
Arduino-Sketch. Wortuhr.ino hier herunterladen. Auch auf gist.github.com verfügbar.
Die Wortuhr fragt alle 10 Sekunden die aktuelle Zeit von der Echtzeituhr ab und schaltet dann die genau die richtigen LEDs an. Optional kann man gelegentlich (bei mir alle 102 Schleifendurchläufe, also 17 Minuten) auch andere Botschaften anzeigen lassen.

Die Uhrzeit einstellen
Mein Programm arbeitet stets mit der Lokalzeit. Vor der ersten Verwendung und jedes halbe Jahr (Stand: 2023) muss die Uhrzeit natürlich neu eingestellt werden. Dabei ist das Skript TimeRTCSet
aus der Bibliothek Time
behilflich, das auf den Arduino hochgeladen wird und dann einen Zeitstempel auf der USB-Verbindung entgegennimmt. In der Linux-Bash 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 Mitteleuropäischen Sommerzeit ("3600 seconds"
, für MEZ) und hängt noch zwei Sekunden technische Verzögerung an. Die Befehle müssen von einem Unix-Benutzer ausgeführt werden, der Schreibzugriff auf /dev/ttyUSB0
hat. Nach dem Einstellen der Uhr wird das Wortuhr-Programm wieder auf den Mikrocontroller zurückgeschrieben.