2023-04-27 19:51:44 +00:00
|
|
|
import math
|
|
|
|
import time
|
|
|
|
from pimoroni_i2c import PimoroniI2C
|
2023-05-04 13:10:12 +00:00
|
|
|
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
|
2023-04-27 19:51:44 +00:00
|
|
|
from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, NUM_LEDS
|
|
|
|
|
|
|
|
"""
|
|
|
|
Display a circular stop-watch on the Encoder Wheel's LED ring.
|
|
|
|
|
|
|
|
Press the centre button to start the stopwatch, then again to pause and resume.
|
|
|
|
|
|
|
|
Press Ctrl+C to stop the program.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Constants
|
|
|
|
BRIGHTNESS = 1.0 # The brightness of the LEDs when the stopwatch is running
|
|
|
|
UPDATES = 50 # How many times the LEDs will be updated per second
|
|
|
|
MINUTE_UPDATES = UPDATES * 60 # How many times the LEDs will be updated per minute
|
|
|
|
HOUR_UPDATES = MINUTE_UPDATES * 60 # How many times the LEDs will be updated per hour
|
2023-05-02 16:31:11 +00:00
|
|
|
UPDATE_RATE_US = 1000000 // UPDATES
|
2023-04-27 19:51:44 +00:00
|
|
|
|
|
|
|
IDLE_PULSE_MIN = 0.2 # The brightness (between 0.0 and 1.0) that the idle pulse will go down to
|
|
|
|
IDLE_PULSE_MAX = 0.5 # The brightness (between 0.0 and 1.0) that the idle pulse will go up to
|
|
|
|
IDLE_PULSE_TIME = 2 # The time (in seconds) to perform a complete idle pulse
|
|
|
|
UPDATES_PER_PULSE = IDLE_PULSE_TIME * UPDATES
|
|
|
|
|
|
|
|
IDLE, COUNTING, PAUSED = range(3) # The state constants used for program flow
|
|
|
|
|
2023-05-04 13:10:12 +00:00
|
|
|
# Create a new BreakoutEncoderWheel
|
|
|
|
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
|
2023-04-27 19:51:44 +00:00
|
|
|
wheel = BreakoutEncoderWheel(i2c)
|
|
|
|
|
|
|
|
# Variables
|
|
|
|
state = IDLE
|
|
|
|
idle_update = 0
|
|
|
|
second_update = 0
|
|
|
|
minute_update = 0
|
|
|
|
hour_update = 0
|
|
|
|
last_centre_pressed = False
|
|
|
|
|
|
|
|
|
|
|
|
# Simple function to clamp a value between a minimum and maximum
|
|
|
|
def clamp(n, smallest, largest):
|
|
|
|
return max(smallest, min(n, largest))
|
|
|
|
|
|
|
|
|
|
|
|
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
|
|
|
|
# inconsistent timings when dealing with complex operations or external communication
|
|
|
|
def sleep_until(end_time):
|
2023-05-02 16:31:11 +00:00
|
|
|
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
|
|
|
|
if time_to_sleep > 0:
|
|
|
|
time.sleep_us(time_to_sleep)
|
2023-04-27 19:51:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Record the current time
|
2023-05-02 16:31:11 +00:00
|
|
|
current_time = time.ticks_us()
|
2023-04-27 19:51:44 +00:00
|
|
|
|
|
|
|
# Run the update loop forever
|
|
|
|
while True:
|
|
|
|
|
|
|
|
# Read whether or not the wheen centre has been pressed
|
|
|
|
centre_pressed = wheel.pressed(CENTRE)
|
|
|
|
if centre_pressed and centre_pressed != last_centre_pressed:
|
|
|
|
if state == IDLE: # If we're currently idle, switch to counting
|
|
|
|
second_update = 0
|
|
|
|
minute_update = 0
|
|
|
|
hour_update = 0
|
|
|
|
state = COUNTING
|
|
|
|
|
|
|
|
elif state == COUNTING: # If we're counting, switch to paused
|
|
|
|
state = PAUSED
|
|
|
|
|
|
|
|
elif state == PAUSED: # If we're paused, switch back to counting
|
|
|
|
state = COUNTING
|
|
|
|
last_centre_pressed = centre_pressed
|
|
|
|
|
|
|
|
# If we're idle, perform a pulsing animation to show the stopwatch is ready to go
|
|
|
|
if state == IDLE:
|
|
|
|
percent_along = min(idle_update / UPDATES_PER_PULSE, 1.0)
|
|
|
|
brightness = ((math.cos(percent_along * math.pi * 2) + 1.0) / 2.0) * ((IDLE_PULSE_MAX - IDLE_PULSE_MIN)) + IDLE_PULSE_MIN
|
|
|
|
# Update all the LEDs
|
|
|
|
for i in range(NUM_LEDS):
|
|
|
|
wheel.set_hsv(i, 0.0, 0.0, brightness)
|
|
|
|
wheel.show()
|
|
|
|
|
|
|
|
# Advance to the next update, wrapping around to zero if at the end
|
|
|
|
idle_update += 1
|
|
|
|
if idle_update >= UPDATES_PER_PULSE:
|
|
|
|
idle_update = 0
|
|
|
|
|
|
|
|
# If we're counting, perform the stopwatch animation
|
|
|
|
elif state == COUNTING:
|
|
|
|
# Calculate how many LED channels should light, as a proportion of a second, minute, and hour
|
|
|
|
r_to_light = min(second_update / UPDATES, 1.0) * 24
|
|
|
|
g_to_light = min(minute_update / MINUTE_UPDATES, 1.0) * 24
|
|
|
|
b_to_light = min(hour_update / HOUR_UPDATES, 1.0) * 24
|
|
|
|
|
|
|
|
# Set each LED, such that ones below the current time are fully lit, ones after
|
|
|
|
# are off, and the one at the transition is at a percentage of the brightness
|
|
|
|
for i in range(NUM_LEDS):
|
2023-05-02 16:31:11 +00:00
|
|
|
r = int(clamp(r_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
|
|
|
|
g = int(clamp(g_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
|
|
|
|
b = int(clamp(b_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
|
2023-04-27 19:51:44 +00:00
|
|
|
wheel.set_rgb(i, r, g, b)
|
|
|
|
wheel.show()
|
|
|
|
|
|
|
|
# Advance the second updates count, wrapping around to zero if at the end
|
|
|
|
second_update += 1
|
|
|
|
if second_update >= UPDATES:
|
|
|
|
second_update = 0
|
|
|
|
|
|
|
|
# Advance the minute updates count, wrapping around to zero if at the end
|
|
|
|
minute_update += 1
|
|
|
|
if minute_update >= MINUTE_UPDATES:
|
|
|
|
minute_update = 0
|
|
|
|
|
|
|
|
# Advance the hour updates count, wrapping around to zero if at the end
|
|
|
|
hour_update += 1
|
|
|
|
if hour_update >= HOUR_UPDATES:
|
|
|
|
hour_update = 0
|
|
|
|
|
|
|
|
# Sleep until the next update, accounting for how long the above operations took to perform
|
2023-05-02 16:31:11 +00:00
|
|
|
current_time = time.ticks_add(current_time, UPDATE_RATE_US)
|
2023-04-27 19:51:44 +00:00
|
|
|
sleep_until(current_time)
|