Kinetic object clock with NeoPixel-Ring
A kinetic object that shows the time at two concentric NeoPixel-rings
A hardware and software project for a friend who loves clocks in all variants.
It has two concentric NeoPixel rings and some moving parts to form a kinetic object that can even display time in an old-fashioned "analog" way :-). Hours are displayed on the inner ring, minutes and seconds on the outer ring.
The electronics are located in the base of the object
Here you will find a short VIDEO of the object. and here a more detailed Blog-post https://peterneufeld.wordpress.com/2020/06/09/kinetic-object-clock-with-neopixel-ring/
An ESP8266 (Wemos D1 mini) controls 86 neopixel LEDs and a 3V mini motor to turn a wooden ring.
A second mini-motor permanently rotates the wooden gearwheel mechanism of a weekday calendar, but actually only to make it look like something more.. This is a nice laser cut model from UGears..
The wooden ring rotates once a minute for a few seconds around the object.
All this reminded me a bit of the TV series "Stargate" - hence the name in the web interface.
The web interface allows to set the color of the "hands" for the hours, minutes and seconds of the clock and the hour markers. The RGB values can be saved permanently for the next program start.
There are several buttons to display some light effects on the neopixel rings.
The code for this is currently running with ANNEX_WIFI RDS_1.40, a BASIC-Interpreter and programming environment running completly in ESP8266 and ESP32-modules.
The BASIC-listing for ESP8266 and ANNEX-WIFI RDS:
' ############################################################################################################ VERSION$ = "1.1" ' Uhr mit zwei NeoPixel-Ringen an D4(!!!) ' und PWM-Signal an D7 für einen gesteuerten Motor ' Jan 2020: Erweiterung von 84 auf 85 LEDs zur ' Mittenbeleuchtung der zusätzlichen Mechanik der Uhr cls 'für drehzahlgesteuerten Motor mit PWM-Signal an D7 D7 = 13 ' D7 = GPIO13 VAL_PWM = 0 VAL_PWM_alt = 1 OPTION.PWMFREQ 10000 '100Hz ist in 1.37beta die kleinste mögliche Frequenz für PWM. Default ist 1000Hz pwm(D7)=VAL_PWM x = 0: y = 0: z = 0 mm = 0: hh = 0: ss = 0 R = 5: G = 25 : B = 0 R_alt = R + 1 G_alt = G B_alt = B Mark_R = 0 : Mark_G = 1 : Mark_B = 1 Sec_R = 1 : Sec_G = 1 : Sec_B = 1 t$ = "TIME" 'gosub SHOW_IP Gosub READ_RGB_DATA gosub STARTUP OnHtmlReload HTML_PAGE gosub HTML_PAGE timer0 1000, SHOW_TIME wait ' ################################### ' ################################### ' ################################### Mode_CLOCK: timer0 0 mm = 0 hh = 0 gosub STARTUP timer0 1000, SHOW_TIME wait ' ' ################################### SHOW_TIME: mm_alt = mm hh_alt = hh ss_alt = ss t$ = time$ 'dw = mid(bla,1,3) 'dow 'mh = mid(bla,5,3) 'month 'dt = mid(bla,9,2) 'date hh = val(mid$(t$,1,2)) 'hour mm = val(mid$(t$,4,2)) 'min ss = val(mid$(t$,7,2)) 'sec 'Motor in den letzten 5 Sekunden einer Minute kurz anwerfen VAL_PWM = 0 if ss >54 then VAL_PWM = 800 else ' Motor zu Sicherheit ausschalten; ' Dazwischen geht auch manuelle Bedienung mit dem Slider if ss = 1 then VAL_PWM= 0 end if if hh > 11 then hh = hh -12 hh = (hh * 2) + 60 if mm > 31 then hh = hh + 1 'neo.strip 0,84,1,1,1,1 neo.strip 0,84,0,0,0,1 ' ''neo.pixel ss_alt,0,0,0,1 ''neo.pixel mm_alt,0,0,0,1 ''neo.pixel hh_alt,0,0,0,1 'Stundenmarkierungen aussen for i = 0 to 58 step 5 neo.pixel i,Mark_R,Mark_G,Mark_B,1 next i 'Stundenmarkierungen innen 'for i = 60 to 84 step 6 ' neo.pixel i,Mark_R,Mark_G,Mark_B,1 'next i 'die Zeiger setzten neo.pixel ss,Sec_R,Sec_G,Sec_B,1 neo.pixel mm,R,G,B,1 neo.pixel hh,R,G,B,1 'Mittelbeleuchtung neo.pixel 85,R*3 mod 100,G*3 mod 100,B*3 mod 100,1 'erst jetzt den Neopixel-puffer senden neo.pixel 84,R,G,B,0 ' 'PWM an D7 aendern, wenn neuer Wert if VAL_PWM <> VAL_PWM_alt then pwm(D7) = VAL_PWM mod 1024 VAL_PWM_alt = VAL_PWM end if refresh return ' ################################### LIGHTSHOW1: timer0 0 t$="LIGHTSHOW1" neo.strip 0,84,0,0,0 'do for xx = 1 to 5 for p = 0 to 59 neo.pixel p,55,0,0 neo.pixel 59 - p,55,0,0 neo.pixel p,0,0,0 neo.pixel 59-p,0,0,0 next p for p = 0 to 59 neo.pixel p,0,55,0 neo.pixel 59-p,0,55,0 neo.pixel p,0,0,0 neo.pixel 59-p,0,0,0 next p for p = 0 to 59 neo.pixel p,0,0,55 neo.pixel 59-p,0,0,55 neo.pixel p,0,0,0 neo.pixel 59-p,0,0,0 next p next xx timer0 1000, SHOW_TIME return ' ################################### LIGHTSHOW2: 'pacman timer0 0 t$="PACMAN" neo.strip 60,84,0,10,10 schritt_x = 1 schritt_y = 1.8 schritt_z = 1.2 do x_alt = x x = x + schritt_x if (x > 59) or (x < 1) then schritt_x = schritt_x * -1 end if y_alt = y y = y + schritt_y if (y > 59) or (y < 1) then schritt_y = schritt_y * -1 end if z_alt = z z = z + schritt_z if (z > 59) or (z < 1) then schritt_z = schritt_z * -1 end if neo.pixel x,84,0,0,1 neo.pixel x_alt,0,0,0,1 neo.pixel y,0,60,0,1 'neo.pixel y_alt,0,0,0,1 neo.pixel z,0,0,60,1 neo.pixel z_alt,0,0,0,0 pause 40 loop until 0 wait ' ################################### LIGHTSHOW3: timer0 0 t$="LIGHTSHOW 3" neo.strip 0,93,0,0,0 neo.strip 0,84,0,0,0 for i = 0 to 59 if i < 29 then neo.pixel 29-i,5,5,0,0 neo.pixel 29+i,5,5,0,0 neo.pixel (59 + 12 - i / 2),5,5,0,0 neo.pixel (59 + 12 + i / 2),5,5,0,0 end if neo.pixel i,1,1,5,0 neo.pixel (60 -i),1,5,1,0 neo.pixel (60 + 12 - i / 2),1,1,5,0 neo.pixel (60 + 12 + i / 2),1,5,1,0 pause 20 next i neo.strip 0,84,0,0,0 for i = 1 to 30000 ii = i MOD 60 iii= (ii/5)*2 'if ii = 1 then gg= i mod 50 rr= i+15 mod 50 bb= i+30 mod 50 'end if neo.strip 0,59+24+1,0,0,0,1 neo.pixel ii,rr,gg,bb,1 neo.pixel (ii+20)mod 60,gg,bb,rr,1 neo.pixel (ii+40)mod 60,bb,rr,gg,1 ' iii=iii/1.5 ' neo.pixel 83-iii,rr,gg,bb,1 ' neo.pixel 83 -abs((iii-8)mod 23),gg,bb,rr,1 ' neo.pixel 83 -abs((iii-16)mod 23),bb,rr,gg,1 neo.pixel 60+iii,rr,gg,bb,1 neo.pixel 60+abs((iii-8)mod 23),gg,bb,rr,1 neo.pixel 60+abs((iii-16)mod 23),bb,rr,gg,1 neo.pixel 84,bb,rr,gg,0 pause 25 next i return ' ################################### STARTUP: neo.setup 86 neo.strip 0,86, 0,0,0 for i = 0 to 29 neo.pixel (30-i),30-i,2+i,0,1 neo.pixel (30+i),30-i,2+i,0,1 neo.pixel (72+((i/5)*2)),1,i,0,1 neo.pixel (72-((i/5)*2)),1,i,0,1 neo.pixel (84),1,i,0,0 pause 120-i*3 next i neo.strip 0,86,11,211,11 pause 10 neo.strip 0,86,0,0,0 return ' ################################### ' ################################### HTML_PAGE: cls autorefresh 750 a$ = "" a$ = a$ & "<H1> S T A R G A T E -V"& VERSION$ & "</H1>" 'a$ = a$ & "<br>" a$ = a$ & textbox$(t$) a$ = a$ & "<br>" a$ = a$ & "Colour of the hour and minute hands:" a$ = a$ & "<br>" a$ = a$ & SLIDER$(R,0,100) a$ = a$ & "R:" a$ = a$ & textbox$(R) a$ = a$ & "<br>" a$ = a$ & slider$(G,0,100) a$ = a$ & "G:" a$ = a$ & textbox$(G) a$ = a$ & "<br>" a$ = a$ & slider$(B,0,100) a$ = a$ & "B:" a$ = a$ & textbox$(B) a$ = a$ & "<br>" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "<br><br>" a$ = a$ & "Colour of the second hand:" a$ = a$ & "<br>" a$ = a$ & SLIDER$(Sec_R,0,100) a$ = a$ & "R:" a$ = a$ & textbox$(Sec_R) a$ = a$ & "<br>" a$ = a$ & slider$(Sec_G,0,100) a$ = a$ & "G:" a$ = a$ & textbox$(Sec_G) a$ = a$ & "<br>" a$ = a$ & slider$(Sec_B,0,100) a$ = a$ & "B:" a$ = a$ & textbox$(Sec_B) a$ = a$ & "<br>" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "<br><br>" a$ = a$ & "Colour of the hour and 5-minute markers:" a$ = a$ & "<br>" a$ = a$ & SLIDER$(Mark_R,0,10) a$ = a$ & "R:" a$ = a$ & textbox$(Mark_R) a$ = a$ & "<br>" a$ = a$ & slider$(Mark_G,0,10) a$ = a$ & "G:" a$ = a$ & textbox$(Mark_G) a$ = a$ & "<br>" a$ = a$ & slider$(Mark_B,0,10) a$ = a$ & "B:" a$ = a$ & textbox$(Mark_B) a$ = a$ & "<br>" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "<br><br>" a$ = a$ & "PWM-Signal for motor at D7:" a$ = a$ & "<br>" a$ = a$ & slider$(VAL_PWM,0,1023) a$ = a$ & "<br>VAL_PWM:" a$ = a$ & textbox$(VAL_PWM) a$ = a$ & "<br><br>MODUS: " a$ = a$ & button$("LIGHTSHOW", LIGHTSHOW1) a$ = a$ & button$("PacMan LIGHTSHOW2", LIGHTSHOW2) a$ = a$ & button$("LIGHTSHOW3", LIGHTSHOW3) a$ = a$ & button$("CLOCK", Mode_CLOCK) html a$ a$="" return ' ################################### Exit: end ' ################################### SHOW_IP: IPADR$= word$(word$(IP$,1),4,".") return ' ################################### TestExit: timer0 0 neo.strip 0,84,0,8,0 pause 500 neo.strip 0,84,0,5,0 pause 500 neo.strip 0,84,0,2,0 pause 500 neo.strip 0,84,0,1,0 pause 500 for i = 0 to 29 neo.pixel i,0,0,0,1 neo.pixel 60-i,0,0,0,1 neo.pixel i/2+60,0,0,0,1 neo.pixel 84-i/2,0,0,0,0 pause 50 next i neo.pixel 30,0,10,0,1 neo.pixel 72,0,10,0,0 '###### end '###### ' ############################################################## SAVE_RGB_DATA: ' schreibt die Einstellungen in einzelne Dateien im Flash t$ = "DATA ..." xxx$ = "RGB-DATA" file.save "/clock/settings",xxx$ file.save "/clock/R", str$(R) file.save "/clock/G", str$(G) file.save "/clock/B", str$(B) file.save "/clock/Sec_R", str$(Sec_R) file.save "/clock/Sec_G", str$(Sec_G) file.save "/clock/Sec_B", str$(Sec_B) file.save "/clock/Mark_R", str$(Mark_R) file.save "/clock/Mark_G", str$(Mark_G) file.save "/clock/Mark_B", str$(Mark_B) t$ = "DATA SAVED" return ' ############################################################## READ_RGB_DATA: if file.exists("/clock/settings") then SETTINGS$ = file.read$("/clock/settings") if SETTINGS$ <> "" then R = val(file.read$("/clock/R")) G = val(file.read$("/clock/G")) B = val(file.read$("/clock/B")) Sec_R = val(file.read$("/clock/Sec_R")) Sec_G = val(file.read$("/clock/Sec_G")) Sec_B = val(file.read$("/clock/Sec_B")) Mark_R = val(file.read$("/clock/Mark_R")) Mark_G = val(file.read$("/clock/Mark_G")) Mark_B = val(file.read$("/clock/Mark_B")) endif return ' ##############################################################
Updates from the author