Skip to content

Instantly share code, notes, and snippets.

@dglaude
Last active June 21, 2020 04:21
Show Gist options
  • Save dglaude/21d759f93904f578c27b18c5c494d0ed to your computer and use it in GitHub Desktop.
Save dglaude/21d759f93904f578c27b18c5c494d0ed to your computer and use it in GitHub Desktop.
microPython ESP8266 Rainbow TIX clock with DS1307 I2C RTC and Pimoroni Unicorn pHAT (32 Neo Pixels)

This "Rainbow TIX clock" runs in microPython on an ESP8266.

In addition to an ESP8266 dev board and its power source, it requires:

  • A 32 NeoPixel 4*8 display (I use
  • An accurate clock source (I use the 52PI RTC, an I2C RTC clock designed for Raspberry Pi and supported in microPython by the following Adafruit library https://github.com/adafruit/Adafruit-uRTC it should be possible to use internal RTC and initialise it from RTP over Wifi, but that is way more complicate and require connectivity)
  • An implementation for HSV_to_RGB, I use a version coming from colorsys, I use a simplified version of the full Python Library
  • An implementation of random library that contain the sample function, I use a simplified version of the full Python Library

Both the Pimoroni Unicorn pHAT and the 52Pi RTC clock are supposed to be used with a voltage of 5V, but they it seems to work OK with a 3V source. So if you lack 5V source, you can cheat and provide 3V on the 5V pin of both.

To have an accurate clock, the RTC must set to the right time (use local time) and you can do it one of the following ways:

ESP8266 pinout:

  • GPIO 4 is used for I2C SDA to talk to the DS1307
  • GPIO 5 is used for I2C SCL to talk to the DS1307
  • GPIO 12 is used for controlling the 32 NeoPixels of the Unicorn pHAT
  • 3V and GND are also used

Unicorn pHAT pinout link https://pinout.xyz/pinout/unicorn_phat)

  • Hardware Pin 2 (5V) is connected to 3V (it should be 5V but it works with 3V)
  • Hardware Pin 6 (Ground) is connected to Ground
  • Hardware Pin 12 (BMC 18) is connected to GPIO 12

52PI BAT RTC pinout link http://wiki.52pi.com/index.php/BAT_RTC(English)

  • Hardware Pin 3 (BMC 2 SDA) is connected to SDA (GPIO 4 of ESP8266)
  • Hardware Pin 3 (BMC 3 SCL) is connected to SCL (GPIO 5 of ESP8266)
  • Hardware Pin 4 (5V) is connected to 3V
  • Hardware Pin 6 (Ground) is connected to Ground

Clock reading explanation

Original TIX clock need more pixels than the 4*8 from the Unicorn pHAT, so information had to be squeezed.

  • 4*3 pixels are used to display the hour. All values from 0 to 12 are used. 0 is used for midnight and 12 for midday.
  • 4*2 pixels are used to display the number of 10 minutes in the current time (first digit)
  • 4*3 pixels are used to display the number of minutes remaining (second digit)

To clearly identify the separate blocks, 3 differents colors are used. Rather than a different fix color for each, a rainbow of color is used, but each pixel of the same block is always in the same color. The pixels turned ON are random, and this is modified every 10 seconds.

This combo make it both easy to read (as it is not moving too much) and easy to know the clock is not stop due to the rainbow effect.

f=open('main.py','w')
f.write('''import my_clock
my_clock.main()''')
f.close()
f=open('my_clock.py','w')
f.write('''
import random, colorsys
import sys, time
from machine import I2C, Pin
from neopixel import NeoPixel
import urtc
def setnset(set, n, val):
global test
for z in random.sample(set,n):
test[z]=val
def fill_pix():
global rtc
global test
datetime = rtc.datetime()
m1=int(datetime.minute/10)
m2=datetime.minute%10
# h1=int(datetime.hour/10)
# h2=datetime.hour%10
h=datetime.hour
if h>12:
h=h%12
test = [0 for i in range(32)]
# setnset([0,8,16],h1,2)
# setnset([1,2,9,10,17,18,24,25,26],h2,1)
setnset([0,1,2,8,9,10,16,17,18,24,25,26],h,1)
setnset([3,4,11,12,19,20,27,28],m1,2)
setnset([5,6,7,13,14,15,21,22,23,29,30,31],m2,3)
def main():
global test
global rtc
max=64
shift=[0,33,66]
pin = Pin(12, Pin.OUT)
np = NeoPixel(pin, 32)
i2c = I2C(Pin(5), Pin(4))
rtc = urtc.DS1307(i2c)
color=[(0,0,0),(255,0,0),(0,255,0),(0,0,255)]
spacing = 360.0 / 99.0
hue = 0
while True:
fill_pix()
for t in range(100):
hue = int(time.ticks_ms()/50 ) % 360
for j in range(3):
offset = (shift[j]) * spacing
h = ((hue + offset) % 360) / 360.0
r, g, b = [int(c*max) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0)]
color[j+1] = (r, g, b)
for i in range(32):
np[i] = color[test[i]]
np.write()
time.sleep_ms(100)
''')
f.close()
f=open("colorsys.py","w")
f.write('''
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h*6.0)
f = (h*6.0) - i
p = v*(1.0 - s)
q = v*(1.0 - s*f)
t = v*(1.0 - s*(1.0-f))
i = i%6
if i == 0:
return v, t, p
if i == 1:
return q, v, p
if i == 2:
return p, v, t
if i == 3:
return p, q, v
if i == 4:
return t, p, v
if i == 5:
return v, p, q
''')
f.close()
f=open("random.py","w")
f.write('''import os
def getrandbits(k):
numbytes = (k + 7) // 8
x = int.from_bytes(os.urandom(numbytes), 'big')
return x >> (numbytes * 8 - k)
def bit_length(n):
res = n
count = 1
while res>1:
res = res>>1
count = count+1
return count
def randbelow(n):
k = bit_length(n)
r = getrandbits(k)
while r >= n:
r = getrandbits(k)
return r
def randrange(start, stop=None, step=1):
istart = int(start)
if istart != start:
raise ValueError("non-integer arg 1 for randrange()")
if stop is None:
if istart > 0:
return randbelow(istart)
raise ValueError("empty range for randrange()")
# stop argument supplied.
istop = int(stop)
if istop != stop:
raise ValueError("non-integer stop for randrange()")
width = istop - istart
if step == 1 and width > 0:
return istart + randbelow(width)
if step == 1:
raise ValueError("empty range for randrange() (%d,%d, %d)" % (istart, istop, width))
# Non-unit step argument supplied.
istep = int(step)
if istep != step:
raise ValueError("non-integer step for randrange()")
if istep > 0:
n = (width + istep - 1) // istep
elif istep < 0:
n = (width + istep + 1) // istep
else:
raise ValueError("zero step for randrange()")
if n <= 0:
raise ValueError("empty range for randrange()")
return istart + istep*randbelow(n)
def randint(a, b):
return randrange(a, b+1)
def sample(population, k):
n = len(population)
if not 0 <= k <= n:
raise ValueError("Sample larger than population")
result = [None] * k
selected = set()
selected_add = selected.add
for i in range(k):
j = randbelow(n)
while j in selected:
j = randbelow(n)
selected_add(j)
result[i] = population[j]
return result
''')
f.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment