# Spiral clock
# software v1.20.6
# raspberry pi pico LiPo 4MB
# pico display pack 240 x 135 SPI LCD
# Waveshare GPIO Expander For Raspberry Pi Pico
# rv3028 rtc
import math
import utime
from pimoroni import Button, RGBLED
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY, PEN_P4
disp = PicoGraphics(display=DISPLAY_PICO_DISPLAY, pen_type=PEN_P4, rotate=0)
from breakout_rtc import BreakoutRTC
from pimoroni_i2c import PimoroniI2C
from machine import Pin, PWM
# display setup
width, height = disp.get_bounds()
disp_buffer = bytearray(width * height * 2)
#disp.init(disp_buffer)
disp.set_backlight(0.5) # backlight - 50%
disp.set_font("bitmap8")
green = disp.create_pen(0, 255, 0)
black = disp.create_pen(0, 0, 0)
blue = disp.create_pen(0, 0, 255)
red = disp.create_pen(255, 0, 0)
white = disp.create_pen(255, 255, 255)
button_a = Button(12)
button_b = Button(13)
button_x = Button(14)
button_y = Button(15)
led_rgb = RGBLED(6, 7, 8)
# rtc setup
PINS_BREAKOUT_GARDEN = {"sda": 0, "scl": 1}
i2c_3 = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
rtc = BreakoutRTC(i2c_3)
rtc.set_backup_switchover_mode(3)
rtc.set_24_hour()
rtc.update_time()
switch = Pin(11, Pin.IN, Pin.PULL_DOWN) # set enable
buzzer = PWM(Pin(21))
beatlist = [[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,1,1],[0,0,0,1],[1,0,0,1],[0,0,0,0]] # stepper motor pattern
motor_pins = [2, 3, 4, 5, 9, 10] # Select [9, 10], motor [2, 3, 4, 5]
sense_pins = [22, 26, 27, 28]
for pin in motor_pins:
machine.Pin(pin, machine.Pin.OUT)
for pin in sense_pins:
machine.Pin(pin, machine.Pin.IN, Pin.PULL_UP)
# global variables
sum_hrs = 0
sum_min = 0
tim = 0
ttim = 0
mim = 0
mtim = 0
rtc_seconds = 0
rtc_minutes = 0
zero_mins = 0
zero_hrs = 0
hours = 0
old_hrs = -1
old_mins = -1
old_secs = -1
diff_hrs = 0
diff_mins = 0
diff_secs = 0
home = False
spiral_hgt = 4.5 # height of spiral in circular rotations
deg_hr = (spiral_hgt*360)/12
deg_min = (spiral_hgt*360)/60
blank = False
# functions
def clkwise(sel):
# motor direction of rotation
for index in range(0, 8):
set_bit(index, sel)
return
def anti_clkwise(sel):
# motor direction of rotation
for index in range(7, -1, -1):
set_bit(index, sel)
return
def set_bit(num, sel):
# num - motor pattern, sel - motor selector
global beatlist, motor_pins
# motor select
if sel == 0:
machine.Pin(motor_pins[4]).value(0) # motor1 off
machine.Pin(motor_pins[5]).value(0) # motor2 off
elif sel == 1:
machine.Pin(motor_pins[4]).value(1) # motor1 on
machine.Pin(motor_pins[5]).value(0) # motor2 off
elif sel == 2:
machine.Pin(motor_pins[4]).value(0) # motor1 off
machine.Pin(motor_pins[5]).value(1) # motor2 on
utime.sleep_us(1000)
# motor pattern
motor_bits = beatlist[num]
# print (motor_bits[0], motor_bits[1], motor_bits[2], motor_bits[3])
for index in range(0, 4):
machine.Pin(motor_pins[index]).value(motor_bits[index])
utime.sleep_us(1000)
return
def motor_off():
set_bit(8, 1) # motor1
set_bit(8, 2) # motor2
return
def pos_min(mins, secs):
# rotation for minute intervals
step_min = int(((mins+(secs/60))*deg_min)*1.42222)
if step_min > 0:
# print (step_min)
for index in range(step_min + 1):
anti_clkwise(2)
set_bit(8, 2) # motor2 off
return
def pos_hrs(hrs, mins):
# rotation for hour intervals
# step_hrs = int(((hrs+(mins/60))*deg_hr)*1.42222)
step_hrs = int(((hrs+(0))*deg_hr)*1.42222)
if step_hrs > 0:
# print (step_hrs)
for index in range(step_hrs + 1):
anti_clkwise(1)
set_bit(8, 1) # motor1 off
return
def spiral_time():
# update spiral positions
global diff_hrs, diff_mins, diff_secs
spiral_hrs = diff_hrs
if diff_hrs >= 12:
spiral_hrs = diff_hrs - 12
pos_hrs(spiral_hrs, diff_mins)
pos_min(diff_mins, diff_secs)
return
def homing():
# home all motors
global old_hrs, old_mins, old_secs
old_hrs = 0
old_mins = 0
old_secs = 0
seeking(1, 0) # motor1
seeking(2, 2) # motor2
return
def home_hrs():
# home hours motor
old_hrs = 0
old_mins = 0
old_secs = 0
seeking(1, 0)
return
def home_mins():
# home minute motor
old_mins = 0
old_secs = 0
seeking(2, 2)
return
def seeking(motor, sensor):
# ensure spirals are aligned at start up
global home, sense_pins
# print ("homing", motor)
home = False
timer = utime.ticks_ms()
index = 0
# rotate spiral to home position before timeout
while (machine.Pin(sense_pins[sensor]).value() == 0 and (utime.ticks_ms() - timer) < 60000):
anti_clkwise(motor)
index += 1
if index > 512:
index = 0
utime.sleep_ms(100)
if machine.Pin(sense_pins[sensor]).value() == 1:
home = True
motor_off()
return
def tgraph():
# display digital time
global tim, ttim, mim, mtim
intime = str(tim) + str(ttim) + ":" + str(mim) + str(mtim)
disp.text(intime, int(width/6), int(height/4), scale=8)
return
def hrs_adj():
global tim, ttim, sum_hrs
sum_hrs += 1
if sum_hrs > 24:
sum_hrs = 0
tim, ttim = split_time(sum_hrs)
tgraph()
disp.update()
return
def mins_adj():
global mim, mtim, sum_min
sum_min += 1
if sum_min > 59:
sum_min = 0
mim, mtim = split_time(sum_min)
tgraph()
disp.update()
return
def split_time(value):
if value > 0:
new_value = str(value)
if value < 10:
tens = 0
unit = new_value[0]
else:
tens = new_value[0]
unit = new_value[1]
else:
tens = unit = 0
return tens, unit
def clean_disp():
disp.set_pen(black)
disp.clear()
disp.update()
return
def read_rtc():
global tim, ttim, mim, mtim, rtc_seconds, rtc_minutes, old_hrs, old_mins, old_secs, diff_hrs, diff_mins, zero_mins, zero_hrs
if rtc.read_periodic_update_interrupt_flag():
rtc.clear_periodic_update_interrupt_flag()
if rtc.update_time():
# rtc_date = rtc.string_date()
# rtc_time = rtc.string_time()
rtc_hours = rtc.get_hours()
rtc_minutes = rtc.get_minutes()
rtc_seconds = rtc.get_seconds() # used in cycle_led
# print (rtc_time, rtc_hours, rtc_minutes, rtc_seconds)
diff_hrs = rtc_hours - old_hrs
zero_hrs = diff_hrs
if diff_hrs < 0:
diff_hrs = 0
if old_hrs != rtc_hours:
old_hrs = rtc_hours
diff_mins = rtc_minutes - old_mins
zero_mins = diff_mins
if diff_mins <= 0:
diff_mins = 0
if old_mins != rtc_minutes:
old_mins = rtc_minutes
if zero_mins == -59:
if rtc_hours == 0 or rtc_hours == 12:
zero_hrs = -23
diff_secs = rtc_seconds - old_secs
if diff_secs < 0:
diff_secs = 0
if old_secs != rtc_seconds:
old_secs = rtc_seconds
# print (diff_hrs, zero_hrs)
if zero_mins == -59:
chime(4) # tone on the hour
tim, ttim = split_time(rtc_hours)
mim, mtim = split_time(rtc_minutes)
return
def set_time():
# seconds, minutes, hours, weekday, day, month, year
# rtc.set_time(55,59,22,0,14,2,2022)
global sum_min, sum_hrs, old_hrs, old_mins
rtc.set_time(0, sum_min, sum_hrs, 0, 1, 1, 2023)
utime.sleep(1)
# reset old time values
old_hrs = -1
old_mins = -1
homing()
return
def cycle_led(sec):
global width, height
# cycle_led: represents 0 to 60 seconds as a varying horizontal line
disp.set_clip(0, 120, width, 5)
tick = int((sec / 60) * width) # horizontal line as a %age of screen width
disp.pixel_span(0, 120, tick)
disp.update()
disp.remove_clip()
utime.sleep(1)
return
def chime(count):
for b in range(1,count):
for a in range(500, 800):
buzzer.duty_u16(1000)
buzzer.freq(a)
utime.sleep(0.005)
buzzer.duty_u16(0)
return
def beep():
buzzer.freq(1000)
buzzer.duty_u16(1000)
utime.sleep(0.25)
buzzer.duty_u16(0)
return
def menu():
# button labels
disp.text("H", 10, 20, 24, 2) # hours
disp.text("M", 220, 20, 24, 2) # minutes
disp.text("U", 220, 100, 24, 2) # update time
disp.text("B", 10, 100, 24, 2) # display mode (blank or time)
return
# initialization
read_rtc()
rtc.enable_periodic_update_interrupt(True)
clean_disp()
disp.set_pen(white)
# menu()
# tgraph()
disp.text("Spiral Time",10,25,220,3)
disp.update()
utime.sleep(2)
clean_disp()
disp.set_pen(green)
disp.text("Please Wait" + " Homing....",10,25,220,3)
disp.update()
homing()
clean_disp()
if home == False:
disp.set_pen(red)
disp.text("Homing Failed!",10,50,220,3)
beep()
else:
disp.set_pen(green)
disp.text("Homing Passed",10,50,220,3)
disp.update()
utime.sleep(2)
while True:
if switch.value(): # enable set time
led_rgb.set_rgb(0, 0, 4) # set mode
disp.set_pen(blue)
menu()
tgraph()
if button_a.read(): # enter hours
beep()
clean_disp()
hrs_adj()
elif button_b.read():
beep()
blank = not(blank)
utime.sleep(1)
elif button_x.read(): # enter minutes
beep()
clean_disp()
mins_adj()
elif button_y.read(): # set time
beep()
set_time()
disp.update()
else:
read_rtc()
clean_disp()
if home == True:
led_rgb.set_rgb(0, 4, 0) # homing good
if blank == False:
disp.set_pen(green)
else:
led_rgb.set_rgb(4, 0, 0) # homing error
if blank == False:
disp.set_pen(red)
menu()
tgraph()
disp.update()
if home == True:
spiral_time()
# if zero_hrs == -23:
# home_hrs() # home hrs at 12 & 24
if zero_mins == -59:
# home_hrs()
# home_mins() # home mins at 60
homing()
cycle_led(rtc_seconds)
Comments