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.

Wortuhr mit Arduino
Wortuhr mit Arduino

Achtung!

Ich bin kein Elektriker! Keine Garantie für korrekte (geschweige denn sinnvolle ;-)) Schaltpläne! Keine Haftung für kaputte Bauteile, oder andere Schäden beim Umgang mit elektrischem Strom! (sind aber nur 5 V)

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.

Material für die Wortuhr
Ein Multimeter kann auch nicht schaden.
  • 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

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
Mein 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 (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:

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

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.

Hinweis

Wenn kein Akku, sondern eine nicht-wiederaufladbare Batterie 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 Batterie anliegen, wodurch diese beschädigt werden kann.

Nach dem Heraustrennen der Diode liegt keine Ladespannung an der Batterie (auf der Rückseite) mehr an.

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.

Meine Schablone (Anklicken zum Herunterladen)

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).

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 den USB-Anschluss umstecken zu können. Auch an eine Bohrung für den Stromanschluss sollte 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. IKEA hat den Aufbau des „Ribba“-Rahmen in den letzten Jahren etwas verändert. (Update:) Dieser Rahmen ist nicht mehr im Angebot.

So soll's mal aussehen.

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.

Update
Ich habe mein Programm 2023 überarbeitet, da die Abschaffung der Zeitumstellung wohl noch auf sich warten lässt. Die Uhr wechselt nun automatisch zwischen Sommer- und Winterzeit. Die alte Version ist hier zu finden.
/*
	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.

Welche Farbe darf es sein?

Die Uhrzeit einstellen

Update
In der neuen Version des Programms bleibt die Echtzeituhr auf Winterzeit eingestellt und es wird automatisch auf Sommerzeit umgerechnet. Es reicht also, einmalig die Winterzeit wie unten beschrieben einzustellen.

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.