Kinetic object clock with NeoPixel-Ring

A hardware and software project for a friend who loves clocks in all variants.

undefined

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 can see a short VIDEO of the object.


 

 

The schematic is quite simple and looks like this:

E26CAA0A-B1EB-459B-9909-2E4872B4AEC6

 


An ESP8266 (Wemos D1 mini) controls  86 NeoPixel LEDs at ring1, ring2 and two LEDS for the center.  These LEDs are all serially organized in one chain, only one data(in) is connected to GPIO02/D4  which can not(!)  be changed in ANNEX. D(out) of Ring1 is connected to D(in) of Ring2;  L1 and L2 are hanging at the end of that chain.

A 5V mini motor (M1) turns the wooden outer ring controlled by a PWM-signal at GPIO13/D7.
A second mini-motor (M2) permanently slowly rotates the wooden gearwheel mechanism of a wooden weekday calendar, but actually only to make it look like something more. For this part I modified the laser cut self assembly set of UGears which I received as a present from my son. It had six  tooth wheels in a planet gear – now there are only five left and #6 drives the outer ring via the second motor.

 

More to rotate: The outer wooden ring rotates once a minute for a few seconds around the object driven by the mini-motor mounted at the top of the inner ring. The rings are made of bamboo embroidery hoop rings.

The pictures reveal the mystery, why the outer ring returns properly to a fine position after having been turned around for some seconds: Two neomdyn magnets on the ring and one magnet behind the centric shaft  do attract each other.

All this reminded me a bit of the TV series “Stargate” – hence the name in the web interface.

DCC15F4E-98D1-4F4F-BB98-D331E8F03088

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 three additional buttons to display some light effects on the neopixel rings.

 

The  code for this is currently running with ANNEX_WIFI RDS_, a  BASIC-Interpreter and  programming environment  running completly in  ESP8266 and ESP32-modules.

This slideshow requires JavaScript.

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$ & "

S T A R G A T E -V”& VERSION$ & “

"
a$ = a$  & textbox$(t$)
a$ = a$ & "
" a$ = a$ & "Colour of the hour and minute hands:" a$ = a$ & "
" a$ = a$ & SLIDER$(R,0,100) a$ = a$ & "R:" a$ = a$ & textbox$(R) a$ = a$ & "
" a$ = a$ & slider$(G,0,100) a$ = a$ & "G:" a$ = a$ & textbox$(G) a$ = a$ & "
" a$ = a$ & slider$(B,0,100) a$ = a$ & "B:" a$ = a$ & textbox$(B) a$ = a$ & "
" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "

" a$ = a$ & "Colour of the second hand:" a$ = a$ & "
" a$ = a$ & SLIDER$(Sec_R,0,100) a$ = a$ & "R:" a$ = a$ & textbox$(Sec_R) a$ = a$ & "
" a$ = a$ & slider$(Sec_G,0,100) a$ = a$ & "G:" a$ = a$ & textbox$(Sec_G) a$ = a$ & "
" a$ = a$ & slider$(Sec_B,0,100) a$ = a$ & "B:" a$ = a$ & textbox$(Sec_B) a$ = a$ & "
" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "

" a$ = a$ & "Colour of the hour and 5-minute markers:" a$ = a$ & "
" a$ = a$ & SLIDER$(Mark_R,0,10) a$ = a$ & "R:" a$ = a$ & textbox$(Mark_R) a$ = a$ & "
" a$ = a$ & slider$(Mark_G,0,10) a$ = a$ & "G:" a$ = a$ & textbox$(Mark_G) a$ = a$ & "
" a$ = a$ & slider$(Mark_B,0,10) a$ = a$ & "B:" a$ = a$ & textbox$(Mark_B) a$ = a$ & "
" a$ = a$ & button$("Save RGB", SAVE_RGB_DATA) a$ = a$ & "

" a$ = a$ & "PWM-Signal for motor at D7:" a$ = a$ & "
" a$ = a$ & slider$(VAL_PWM,0,1023) a$ = a$ & "
VAL_PWM:" a$ = a$ & textbox$(VAL_PWM) a$ = a$ & "

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

 

2 comments

  1. Can I ask how the WiFi details are included in the code please?

    1. The Wifi details are primarily a part of the config-page of ANNEX and are thereby stored permanently, but they can as well be set at runtime via a BASIC command. See online help by pressing F2-Key in the editor.

%d bloggers like this: