C Forde
Published © CC BY-NC-SA

Lissajous Clock

Using Lissajous figures to indicate the time in the style of a carriage clock in wood, acrylic & brass using a Raspberry Pi Pico

AdvancedFull instructions provided2 days206
Lissajous Clock

Things used in this project

Hardware components

Raspberry Pi Pico Lipo
×1
Pico Display Pack 2.0
×1
Pico Omnibus (Dual Expander)
×1
Real Time Clock RV3028
×1
RGB Encoder Breakout
×1
Momentary push button switch
×1
Rotary Switch
×1

Software apps and online services

Thonny

Story

Read more

Schematics

Lissajous Clock Schematic

Connection diagram for main circuit elements

Code

Lissajous Clock code

Python
# lissajous clock

# raspberry pi pico
# pico display pack 2.0 - (320 x 240)
# pico omnibus dual expander
# rv3028 rtc
# rgb rotary encoder

import math
import utime
import picodisplay2 as disp
from breakout_rtc import BreakoutRTC
from pimoroni_i2c import PimoroniI2C
from breakout_encoder import BreakoutEncoder
from machine import Pin

# encoder setup
PINS_BREAKOUT_GARDEN = {"sda": 2, "scl": 3}
i2c_0 = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
enc_0 = BreakoutEncoder(i2c_0, 0x0F)
enc_0.set_brightness(1.0)
# enc.set_direction(BreakoutEncoder.DIRECTION_CCW) # Uncomment to flip the direction
steps_per_rev = 61

# display setup
width = disp.get_width()
height = disp.get_height()
disp_buffer = bytearray(width * height * 2)
disp.init(disp_buffer)
disp.set_backlight(0.5)  # backlight - 50%
disp.set_pen(0, 255, 0)  # pen colour RGB - green

# 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(4, Pin.IN, Pin.PULL_DOWN)
button = Pin(5, Pin.IN, Pin.PULL_DOWN)

# global variables
sum_hrs = 0
sum_min = 0
tim = 0
ttim = 0
mim = 0
mtim = 0
rtc_seconds = 0
hours = 0

# functions
def tgraph_hrs():
    global tim, ttim
    tgraph(ttim, 0.25, 0.25)  # tens
    tgraph(tim, 0.75, 0.25)  # units
    return


def tgraph_min():
    global mim, mtim
    tgraph(mtim, 0.25, 0.75)  # tens
    tgraph(mim, 0.75, 0.75)  # units
    return


def tgraph(intime, woff, hoff):
    # tgraph: converts a value into a lissajous pattern with complete lobes
    # intime - the time value to be converted
    # woff - width offset
    # hoff - horizontal offset
    global width, height
    tval = int(intime) % 2
    aspect = 4.3
    for i in range(722):
        sidx = int(math.sin(i) * (width / aspect) + (width * woff))
        g = i * int(intime)
        hsize = height / aspect
        hpos = height * hoff
        if tval != 0:
            nidx = int(math.cos(g) * hsize + hpos)
        else:
            nidx = int(math.sin(g) * hsize + hpos)
        disp.pixel(sidx, nidx)
    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_hrs()
    utime.sleep(2)
    # print (sum_hrs, ttim, tim)
    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_min()
    utime.sleep(2)
    # print (sum_min, mtim, mim)
    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 unit, tens


def clean_disp():
    disp.set_pen(0, 0, 0)
    disp.clear()
    disp.set_pen(0, 255, 0)
    return


def read_rtc():
    global tim, ttim, mim, mtim, rtc_seconds
    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)

            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
    rtc.set_time(0, sum_min, sum_hrs, 0, 1, 1, 2022)
    utime.sleep(2)
    return


def cycle_led():
    # cycle_led: represents 0 to 60 seconds as a varying horizontal line
    disp.set_clip(0, 120, 320, 5)
    tick = int((rtc_seconds / 60) * 320) # displays horizontal line as a percentage of screen width
    disp.pixel_span(0, 120, tick)
    disp.update()
    disp.remove_clip()
    return


def count_changed():
    global tim, ttim, mim, mtim, hours, sum_min, sum_hrs
    if enc_0.get_interrupt_flag():
        count = enc_0.read()
        old = count
        count = count % 61
        enc_0.clear_interrupt_flag()
 
        while count < 0:
            count += steps_per_rev
          
        minutes = count
        if old < 0:
           if count == 60:
              hours -= 1
              minutes = 0            
        else:
           if count == 60:
              hours += 1
              minutes = 0
        if hours < 0:
            hours = 23
            minutes = 59
        if hours > 23:
            hours = 0
            minutes = 0
#        if count != 60:
#            print(count, hours, minutes)
        
        sum_hrs = hours
        sum_min = minutes
        tim, ttim = split_time(hours)
        mim, mtim = split_time(minutes)

    return


# initialization
read_rtc()
count_changed()
enc_0.clear_interrupt_flag()
rtc.enable_periodic_update_interrupt(True)
clean_disp()
tgraph_hrs()
tgraph_min()
disp.update()

while True:
    if switch.value():
        enc_0.set_led(0, 0, 255)
        if disp.is_pressed(disp.BUTTON_A):  # enter hours
            clean_disp()
            hrs_adj()
            disp.update()
        elif disp.is_pressed(disp.BUTTON_X):  # enter minutes
            clean_disp()
            mins_adj()
            disp.update()
        elif disp.is_pressed(disp.BUTTON_B):  # set time
            clean_disp()
            set_time()
            disp.update()
        else:
            if button.value():
                enc_0.set_led(0, 0, 255)
                clean_disp()
                set_time()
                disp.update() 
            else:    
                clean_disp()
                count_changed()
                tgraph_hrs()
                tgraph_min()
                disp.update()
    else:
        enc_0.set_led(0, 0, 0) 
        read_rtc()
        clean_disp()
        tgraph_hrs()
        tgraph_min()
        disp.update()
        cycle_led()

Credits

C Forde
11 projects • 3 followers
Contact

Comments

Please log in or sign up to comment.