5566

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

This project might have a bit of local flavor
elektor


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:
elektor

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

img-8911.jpeg


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

img-8914.jpeg


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 '!!!!!!!!!!!!!!!!!!