Rheinturm-Clock
The World‘s largest Digital Clock at Düsseldorf, now in a living room.

In Düsseldorf, Germany, there is the world’s largest decimal clock on the local TV tower.
https://en.wikipedia.org/wiki/Rheinturm
A friend of mine, who is an experienced maker, has now built a “small” replica for his living room using a 3D printer and a NeoPixel strip. I supported this with an ANNEX32 scrict code for an ESP32 controller.
The result is now located in a living room and looks like this:

This is the Web interface to control the colors and brightnes of the NEoPixels in the tower.

As usual, I chose an Annex32 script to quickly get a prototype up and running, and to adjust the colors and correct distribution of the LEDs.
To adapt it to our needs and try it out, I first tested the script in the WOKWI’s Annex32 emulator, and so my friend was able to access it quickly.
https://wokwi.com/projects/429294505945321473

The final result, now in the living room, is certainly something my friend can be proud of!
' KOPFZEILE1$ = "- R H E I N T U R M - Z E I T P E G E L -"
KOPFZEILE2$ = " mit 60er-NEOPIXEL-Strip"
KOPFZEILE3$ = " - V2.1 -"
' Peter.Neufeld@gmx.de 03.2017 // 04.2025
S1_pos = 0 'Position der ersten Sekunden-LED im Streifen (Anfang bei 0)
LED_PIN = 27 'GPIO Daten-Pin des ESP32 für die NeoPixel-data
LED_TOP_PIN = 26 'EINZELNE klassische 3mm-LED an Turmspitze, blinkt 1 pro Sekunde
LED_NUM = 70 'Anzahl der NeoPixel-LEDs im Streifen
LED_SEC_E = 1 'Anzahl der Trenn-LEDs nach den Sekunden-Einern
LED_SEC_Z = 2 'Anzahl der Trenn-LEDs nach den Sekunden-Zehnern
LED_MIN_E = 1 'Anzahl der Trenn-LEDs nach den MINUTEN-Einern
LED_MIN_Z = 2 'Anzahl der Trenn-LEDs nach den MINUTEN-Zehnern
LED_H_E = 1 'Anzahl der Trenn-LEDs nach den STUNDEN-Einern
LED_H_Z = 1 'Anzahl der Trenn-LEDs nach den STUNDEN-Zehnern
LED_BLINK_NUM = 4 'Anzahl der blinkenden LEDs in der Spitze(=letzte LEDs am Streifenende)
LED_BLINK_POS = LED_NUM - LED_BLINK_NUM 'Blinkende LED an der Turmspitze LED_DIMM = 200 'Helligkeit der Trenn-LEDs
LED_blink_DIMM = LED_DIMM 'Helligkeit der blinkenden LEDs
STATUS$ = "Anfang" 'Variable zum Testen
R = 200 'R G B -Werte der UHR-LEDs falls bisher noch nicht im EEPROM gespeichert
G = 200
B = 200
ZEIT$ = "00:00:00"
TOGGLE = 0
pin.mode LED_TOP_PIN,output
gosub einstellungen_lesen
' Startposition der jeweils ersten LED fuer die
' einzelnen Stellen der Sekunden, Minuten und Stunden
' pos + LED-Anzahl + Trenner-LEDs
s2_pos = s1_pos + 9 + LED_SEC_E 'Sekunden-Zehner
m1_pos = s2_pos + 5 + LED_SEC_Z 'Minuten-Einer
m2_pos = m1_pos + 9 + LED_MIN_E 'Minuten-Zehner
h1_pos = m2_pos + 5 + LED_MIN_Z 'Stunden-Einer
h2_pos = h1_pos + 9 + LED_H_E 'Stunden-Zehner
LED_BLINK_NUN = 3
R_alt = R + 1 ' zum Erkennen neuer R G B Werte aus der GUI
G_alt = G
B_alt = B
save_stat$ = "" 'Status fuer Werte in EEProm gesichert
gosub NEOPIXEL_start
' --Hauptseite mit 1s_timer aus WEBPAGE----
STATUS$ = "Hauptschleife"
m1_alt = 99
h1_alt = 99
gosub Zeit_zerlegen
onHTMLreload WEBPAGE
onHTMLchange WEBPAGE
gosub WEBPAGE
gosub TIMER_AN
'####
WAIT
'####
end
' ############################################################## ' ## JETZT KOMMEN DIE UNTERPROGRAMME ########################### ' ##############################################################
TIMER_AN:
'Timer aktualisiert einmal pro Sekunde die NEOPIXEL-LEDs
STATUS$ = "TIMER_AN"
timer0 1000, AKTUELLE_ZEIT_ANZEIGEN
return
' ##############################################################
AKTUELLE_ZEIT_ANZEIGEN:
'####### Wird vom Timer einmal pro Sekunde aufgerufen
STATUS$ = "AKTUELLE_ZEIT_ANZEIGEN"
gosub Zeit_zerlegen
WLOG ZEIT$
print ZEIT$
gosub LEDs_setzen
STATUS$ = "AKTUELLE_ZEIT_ANZEIGEN_WAIT"
return
' ##############################################################
Zeit_zerlegen:
STATUS$ = "Zeit_zerlegen"
ZEIT$ = time$
h2 = val(mid$(ZEIT$,1,1))
h1 = val(mid$(ZEIT$,2,1))
m2 = val(mid$(ZEIT$,4,1))
m1 = val(mid$(ZEIT$,5,1))
s2 = val(mid$(ZEIT$,7,1))
s1 = val(mid$(ZEIT$,8,1))
STATUS$ = "Zeit_zerlegt"
return
' ############################################################## LEDs_setzen:
STATUS$ = "LEDs_setzen_A"
'neo.strip 0,LED_NUM,130,130,130,1
if ( R <> R_alt ) or ( G <> G_alt ) or ( B <> B_alt ) then
m1_alt = 99
h1_alt = 99
R_alt = R
G_alt = G
B_alt = B
end if
'sekunden
'--------
if s1 = 0 then
neo.strip s1_pos,s1_pos + 8,0,0,0,1
else
neo.strip s1_pos,s1_pos + s1 -1,R,G,B,1
end if
if s2 = 0 then
neo.strip s2_pos,s2_pos + 4,0,0,0,1
else neo.strip s2_pos,s2_pos + s2 -1,R,G,B,1
end if
'minuten
'-------
if m1_alt <> m1 then m1_alt = m1
if m1 = 0 then
neo.strip m1_pos,m1_pos + 8,0,0,0,1
else
neo.strip m1_pos,m1_pos + m1 -1,R,G,B,1
end if
if m2 = 0 then
neo.strip m2_pos,m2_pos + 4,0,0,0,1
else
neo.strip m2_pos,m2_pos + m2 -1,R,G,B,1
end if
end if
'Stunden
'-------
if h1_alt <> h1 then h1_alt = h1
if h1 = 0 then neo.strip h1_pos,h1_pos + 8,0,0,0,1 else neo.strip h1_pos,h1_pos + h1 -1,R,G,B,1 end if if h2 = 0 then neo.strip h2_pos,h2_pos + 4,0,0,0,1 else neo.strip h2_pos,h2_pos + h2 -1,R,G,B,1 end if end if
'#### Trenner-LEDs und Turmspitze ###### 'Trenner-LED innerhalb Sekunde (rot) neo.strip s2_pos - LED_SEC_E,s2_pos-1,LED_DIMM,0,0,1
'Trenner Sekunde//Minute (rot) neo.strip m1_pos - LED_SEC_Z, m1_pos - 1,LED_DIMM,0,0,1
'Trenner-LED innerhalb Minute (rot) neo.strip m2_pos - LED_MIN_E, m2_pos -1,LED_DIMM,0,0,1
'Trenner-LED Minute//Stunde (rot) neo.strip h1_pos - LED_MIN_Z, h1_pos -1,LED_DIMM,0,0,1
'Trenner-LED innerhalb Stunde (rot) neo.strip h2_pos - LED_H_E, h2_pos -1,LED_DIMM,0,0,1
'Trenner-LED oberhalb Stunden (rot) neo.strip h2_pos +2, h2_pos + 1 + LED_H_Z ,LED_DIMM,0,0,1
'fixe blaue LEDs ganz oben neo.strip h2_pos + 1 + LED_H_Z, LED_NUM,0,0,LED_DIMM,1
'rot blinkende Turmspitze pin(LED_TOP_PIN) = 1 - pin(LED_TOP_PIN) 'einzelne klassische 3mm-LED an Turmspitze TOGGLE=1-TOGGLE if (TOGGLE =0) then 'Helligkeit der blinkenden LEDs = AN LED_blink_DIMM = LED_DIMM save_stat$ = "" 'Loeschen des "OK" 1s nach erfolgreichem [einstellungen_schreiben] 'save_stat = "ramfree:" & ramfree() else 'Helligkeit der blinkenden LEDs = AUS LED_blink_DIMM = LED_DIMM/2 end if
'!!!Erst hier schreibt neo den bisher aufgebauten Puffer!!! neo.strip LED_BLINK_pos , LED_BLINK_POS + LED_BLINK_NUM -1,LED_BLINK_DIMM,0,0,0 '!!!ERST HIER WIRD DER NeoPixel-STREIFEN ANGESTEUERT
STATUS$ = "LEDs_setzen_E"
return
' ############################################################## ' ##############################################################
NEOPIXEL_start: '--------------- ' Initialisieren und Test der Neopixel ' !!!!!!!!!!! MIT NEOPIXEL an GPIO2 !!!!!!!!!!!!!!! STATUS$ = "NEOPIXEL_start_A" neo.setup LED_PIN,LED_NUM for i = 0 to LED_NUM neo.pixel i,200,200,200,0 pause 20 next i pause 1000 neo.strip 0,LED_NUM,0,0,0 STATUS$ = "NEOPIXEL_start_E" return
' ############################################################## WEBPAGE: STATUS$ = "WEBPAGE_A" cls autorefresh 1000 A$ = "" A$ = A$ + |<table><tbody><tr>| A$ = A$ + "<b><center>" & KOPFZEILE1$ & |</b><br>| A$ = A$ + KOPFZEILE3$ + |</center><th>| 'A$ = A$ + "<br><br>Internet-Zeit:>" + textbox$(ZEIT$,"cssTB") A$ = A$ + "<br>" + textbox$(ZEIT$,"cssTB") A$ = A$ + "<br><br><small>" A$ = A$ + "Helligkeit der Trenner-LEDs:<br>"
A$ = A$ + "->"+ slider$(LED_DIMM,0,155,"cssSL") + textbox$(LED_DIMM,"cssTB1") A$ = A$ + "<br><br>" A$ = A$ + "Farbe der Uhr-LEDs <br>" A$ = A$ + "R:" + slider$(R,0,240,"cssSL") + textbox$(R,"cssTB1") A$ = A$ + "<br>" A$ = A$ + "G:" + slider$(G,0,240,"cssSL") + textbox$(G,"cssTB1") A$ = A$ + "<br>" A$ = A$ + "B:" + slider$(B,0,240,"cssSL") + textbox$(B,"cssTB1") A$ = A$ + "<br><br>" + button$("RGB-Werte sichern", einstellungen_schreiben,"cssBT") A$ = A$ + "<br><br>" + textbox$(save_stat$,"cssTB") A$ = A$ + |</small>| A$ = A$ + |</th>| A$ = A$ + |<th width = "50%">| A$ = A$ + |<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/D%C3%BCsseldorf_Rheinturm_-_Lichtzeitpegel.jpg/500px-D%C3%BCsseldorf_Rheinturm_-_Lichtzeitpegel.jpg" alt="Bild" width=100%>| A$ = A$ + |</th>| A$ = A$ + |</tr></tbody></table>| a$ = a$ + cssid$("cssSL"," width:110px;height:2em; font-size:0.8em; ") a$ = a$ + cssid$("cssTB1"," width:40px;height:2em; text-align:center;font-size:0.8em; ") a$ = a$ + cssid$("cssTB"," width:100px;height:2em; text-align:center;font-size:1.0em; ") a$ = a$ + cssid$("cssBT"," width:180px;height:2em; text-align:center;font-size:1.0em; border-radius:1.1em; padding:.5em")
HTML a$ return
' ############################################################## ' ############################################################## ' ############################################################## ' einstellungen_lesen: STATUS$ = "Einstellungen_lesen" IF file.exists("/Rheinturm_settings.txt") = 1 then settings$ = file.read$("/Rheinturm_settings.txt") if settings$ <> "" then R = val(file.read$("/Rheinturm_R.txt")) G = val(file.read$("/Rheinturm_G.txt")) B = val(file.read$("/Rheinturm_B.txt")) LED_DIMM = val(file.read$("/Rheinturm_L.txt")) endif endif STATUS$ = "einstellungen_lesen_E" return
' ##############################################################
einstellungen_schreiben: ' schreibt die Einstellungen in einzelne Dateien im Flash STATUS$ = "Einstellungen_schreiben"
timer0 0 xxx$ = "Rheinturmuhr-Daten" file.save "/Rheinturm_settings.txt",xxx$ file.save "/Rheinturm_R.txt", str$(R) file.write "/Rheinturm_G.txt", str$(G) file.write"/Rheinturm_B.txt", str$(B) 'file.write "Rheinturm_T", str$(T) file.write "/Rheinturm_L.txt",str$(LED_DIMM) save_stat$ = "RGB gesichert" STATUS$ = "Einstellungen_geschrieben" gosub TIMER_AN
return
' ############################################################## ' ##############################################################
TestExit: timer0 0 STATUS$ = "--- E N D E ---" A$ = A$ + "<br>" & STATUS$ save_stat$ = " E N D E "
neo.strip 0,59,10,0,0 for i = 59 to 2 step -1 neo.pixel i,0,0,0,0 next i
neo.strip 0,1,100,100,100 PAUSE 500 neo.strip 0,1,0,0,5 '!!!!!!!!!!!!!!!!!! END '!!!!!!!!!!!!!!!!!!

Discussion (0 comments)