from machine import Pin, PWM, SPI, SoftI2C
import neopixel
import time
import _thread
#import cst816
#import gc9a01
# Configuration
BUTTON_PIN = 14 # Button pin, make sure it's suitable for input
LED_PIN = 33 # LED strip pin, ensure this pin supports digital output
LED_COUNT = 16 # Number of LEDs in the strip
BUZZER_PIN = 1 # Buzzer pin, adjust if necessary and ensure it's suitable for output
PLAYERS_CLK_PIN = 23 # Players clockwise rotation encoder pin
PLAYERS_DT_PIN = 22 # Players counterclockwise rotation encoder pin
SPEED_CLK_PIN = 19 # Speed clockwise rotation encoder pin
SPEED_DT_PIN = 18 # Speed counterclockwise rotation encoder pin
class PatchedI2C(SoftI2C):
def try_lock(self):
return True
def unlock(self):
pass
def writeto_then_readfrom(self, address, write_buffer, read_buffer, out_start=0, out_end=None, in_start=0, in_end=None):
if out_end is None:
out_end = len(write_buffer)
if in_end is None:
in_end = len(read_buffer)
self.writeto(address, write_buffer[out_start:out_end])
read_data = self.readfrom(address, in_end - in_start)
read_buffer[in_start:in_end] = read_data
def read(self, nbytes):
return self.readfrom(self.addr, nbytes, stop=True)
# State definitions
ANIMATION, IDLE, SHOW_COLOR, RUNNING, PAUSED, FINISHED, PULSING = range(7)
state = SHOW_COLOR # Initial state
previous_millis = 0 # For timing
interval = 1000 # Interval for actions, in milliseconds
speeds = [625, 1250, 2500, 12500] # 15s, 30s, 60s, 300s
current_speed = 1 # Current intervals index, startup with 30s
current_player = 0 # Current player (if applicable)
led_index = LED_COUNT - 1 # For iterating LEDs, start from the last LED for anti-clockwise countdown
player_colors = [(255, 0, 255), (255, 255, 0), (0, 255, 255), (192, 0, 0), (255, 255, 255), (0, 255, 0)] # Player colors
pulse_count = 0 # For pulsing effect
pulse_is_on = False
last_pulse_time = 0
amount_of_players = 6
min_players = 1
brightness = 1
button_pressed = False
button_press_start_time = 0
skip_player_pressed = False
animation_running = False
music_running = False
breathing_running = False
players_clk_value = 1 # 1 = not changed, 0 = changed
speed_clk_value = 1 # 1 = not changed, 0 = changed
# Initialize components
np = neopixel.NeoPixel(Pin(LED_PIN), LED_COUNT)
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)
buzzer = PWM(Pin(BUZZER_PIN))
players_clk = Pin(PLAYERS_CLK_PIN, Pin.IN, Pin.PULL_UP)
players_dt = Pin(PLAYERS_DT_PIN, Pin.IN, Pin.PULL_UP)
speed_clk = Pin(SPEED_CLK_PIN, Pin.IN, Pin.PULL_UP)
speed_dt = Pin(SPEED_DT_PIN, Pin.IN, Pin.PULL_UP)
# Initialize I2C for touch sensor
#i2c = PatchedI2C(scl=Pin(7), sda=Pin(6))
#touch_sensor = cst816.CST816(i2c)
# Initialize SPI for the display
#spi = SPI(2, baudrate=60000000, sck=Pin(10), mosi=Pin(11))
#display = gc9a01.GC9A01(
# spi,
# cs=Pin(9),
# dc=Pin(8),
# reset=Pin(14),
# width=240,
# height=240
#)
notes = {
'E3': 82 * 4, # E2 4 times higher
'B3': 123 * 4, # B2 4 times higher
'D4': 147 * 4, # D3 4 times higher
'E4': 165 * 4, # E3 4 times higher
'G4': 196 * 4, # G3 4 times higher
'A4': 220 * 4, # A3 4 times higher
}
melody = [
('E3', 200), ('B3', 200), ('D4', 200), ('E4', 200),
('G4', 400),
('E3', 200), ('B3', 200), ('D4', 200), ('E4', 200),
('A4', 400),
]
# Function definitions for game logic, sound, LED control, etc.
def set_all_leds_color(color):
adjusted_color = (int(color[0] * brightness), int(color[1] * brightness), int(color[2] * brightness))
for i in range(LED_COUNT):
np[i] = adjusted_color
np.write()
def show_current_player_color():
global state, led_index
set_all_leds_color(player_colors[current_player])
led_index = LED_COUNT - 1
state = SHOW_COLOR
def start_timer():
global led_index, previous_millis, state
set_all_leds_color((0, 255, 0)) # Set all LEDs to green for the countdown
led_index = LED_COUNT - 1
previous_millis = time.ticks_ms()
state = RUNNING
def pause_timer():
global state
state = PAUSED
def resume_timer():
global state, previous_millis
state = RUNNING
previous_millis = time.ticks_ms() # Reset timer to maintain correct timing
def check_touch():
global state, current_player, button_press_start_time, skip_player_pressed
touch_detected = touch_sensor.get_touch()
if touch_detected:
if button_press_start_time == 0:
button_press_start_time = time.ticks_ms() # Record the time when touch starts
skip_player_pressed = False
# Check if the touch has been held for more than 1.5 seconds
if not skip_player_pressed and time.ticks_ms() - button_press_start_time >= 1500:
skip_player_pressed = True
next_player()
#set_display_color(player_colors[current_player])
else:
if button_press_start_time > 0 and not skip_player_pressed:
touch_duration = time.ticks_ms() - button_press_start_time
# Short touch detected, mimic the button functionality for Start/Pause/Resume
if touch_duration >= 50:
if state == SHOW_COLOR:
start_timer()
elif state == RUNNING:
pause_timer()
elif state == PAUSED:
resume_timer()
elif state == IDLE:
show_current_player_color()
# Reset the touch timer after processing the touch
button_press_start_time = 0
def beep():
play_tone('E4', 100)
def beepOn():
buzzer.freq(660) # Set frequency for the note
buzzer.duty(512) # 50% duty cycle to generate sound
time.sleep(0.05)
def beepOff():
buzzer.duty(0)
def play_tone(note, duration):
if note in notes:
buzzer.freq(notes[note]) # Set frequency for the note
buzzer.duty(512) # 50% duty cycle to generate sound
else:
buzzer.duty(0) # No sound for rests
time.sleep_ms(duration)
buzzer.duty(0) # Turn off between notes
def play_melody():
start_time = time.ticks_ms()
#while time.ticks_diff(time.ticks_ms(), start_time) < 7000: # Loop for 7 seconds
while animation_running:
for note, duration in melody:
if animation_running:
play_tone(note, duration)
# Adjust the timing based on the song's rhythm
time.sleep_ms(30)
def music_thread():
global music_running
music_running = True
# Play the boot-up sound
play_melody()
music_running = False
def breathing_effect():
global breathing_running
breathing_running = True
width, height = 240, 240 # Assuming a square display for simplicity
center_x, center_y = width // 2, height // 2
max_radius = min(center_x, center_y)
selected_colors = [(255, 0, 255), (255, 255, 0), (0, 255, 255), (255, 255, 255)] # The colors used in the animation
for color in selected_colors:
for t in range(0, 100, 5): # Gradual intensity change for breathing effect
intensity = abs(100 - t * 2) / 100.0 # Create a breathing effect
adjusted_color = tuple(int(c * intensity) for c in color)
color_16_bit = ((adjusted_color[0] & 0xF8) << 8) | ((adjusted_color[1] & 0xFC) << 3) | (adjusted_color[2] >> 3)
radius = int(max_radius * (t / 100.0))
display.circle(center_x, center_y, radius, color_16_bit) # Draw a filled circle from center
time.sleep(0.05)
breathing_running = False
def start_animation():
global music_running, animation_running, breathing_running
animation_running = True
thread2 = _thread.start_new_thread(music_thread, ())
#thread3 = _thread.start_new_thread(breathing_effect, ())
selected_colors = [(255, 0, 255), (255, 255, 0), (0, 255, 255), (255, 255, 255)] # The colors used in the animation
for color in selected_colors:
# Light up LEDs one by one
for j in range(LED_COUNT):
adjusted_color = (int(color[0] * brightness), int(color[1] * brightness), int(color[2] * brightness))
np[j] = adjusted_color
np.write()
time.sleep(0.03) # Delay between lighting up each LED
time.sleep(0.3) # Pause when all LEDs are lit
# Turn off LEDs one by one
for j in range(LED_COUNT):
np[j] = (0, 0, 0) # Turn off the LED at position j
np.write()
time.sleep(0.03) # Delay between turning off each LED
animation_running = False
while music_running or breathing_running:
time.sleep(0.1) # Wait until startup music is finished
show_current_player_color() # Initialize the first player's color after the animation
def update_timer():
global led_index, state, pulse_count, previous_millis
if led_index > 0:
np[led_index] = (0, 0, 0) # Turn off the current LED
led_index -= 1 # Move to the next LED for the next update
previous_millis = time.ticks_ms() # Reset the timer for the next update
for i in range(led_index + 1):
if led_index > LED_COUNT / 2:
np[i] = (int(0 * brightness), int(255 * brightness), int(0 * brightness)) # Green at 5% brightness
elif led_index > LED_COUNT / 4:
np[i] = (int(255 * brightness), int(140 * brightness), int(0 * brightness)) # Orange at 5% brightness
else:
np[i] = (int(255 * brightness), int(0 * brightness), int(0 * brightness)) # Red at 5% brightness
np.write()
if (led_index < 3):
beep()
else:
state = PULSING
previous_millis = time.ticks_ms()
def next_player():
global current_player
current_player = (current_player + 1) % amount_of_players # Cycle to the next player
show_current_player_color() # Initialize the next player turn
#set_display_color(player_colors[current_player]) # Update the display to show the current player color
def pulse_last_led():
global pulse_count, state, pulse_is_on, last_pulse_time
if time.ticks_ms() - last_pulse_time > 100 and pulse_count < 20: # Faster pulse rate for the last LED, limit to 20 pulses
pulse_is_on = not pulse_is_on
# Adjust color for 5% brightness
adjusted_color = (int(255 * brightness), int(0 * brightness), int(0 * brightness))
np[0] = adjusted_color if pulse_is_on else (0, 0, 0) # Toggle the last LED
np.write()
last_pulse_time = time.ticks_ms()
pulse_count += 1
beepOn()
if pulse_count == 20:
np[0] = (0, 0, 0) # Ensure the last LED is off after pulsing
np.write()
beepOff()
pulse_count = 0 # Reset pulse count for new cycle
pulse_is_on = False
last_pulse_time = 0
state = FINISHED # Mark the cycle as finished
def increase_speed():
global current_speed, interval
total_speeds = len(speeds) - 1
if current_speed < total_speeds:
current_speed += 1
interval = speeds[current_speed]
current_player = 0
if state != IDLE and state != ANIMATION:
show_current_player_color()
def decrease_speed():
global current_speed, interval
total_speeds = len(speeds) - 1
if current_speed > 0:
current_speed -= 1
interval = speeds[current_speed]
current_player = 0
if state != IDLE and state != ANIMATION:
show_current_player_color()
def update_speed():
global interval, speed_clk_value
new_speed_clk_value = speed_clk.value()
if (speed_clk_value != new_speed_clk_value):
speed_clk_value = new_speed_clk_value
speed_dt_value = speed_dt.value()
if not speed_clk_value and speed_dt_value: #Clockwise rotation
time.sleep(0.5)
increase_speed()
elif not speed_clk_value and not speed_dt_value: #Counterclockwise rotation
time.sleep(0.5)
decrease_speed()
def increase_players():
global current_player, amount_of_players
max_players = len(player_colors)
if amount_of_players < max_players:
amount_of_players += 1
current_player = 0
if state != IDLE and state != ANIMATION:
show_current_player_color()
def decrease_players():
global current_player, amount_of_players
if min_players < amount_of_players:
amount_of_players -= 1
current_player = 0
if state != IDLE and state != ANIMATION:
show_current_player_color()
def update_number_of_players():
global players_clk_value
new_players_clk_value = players_clk.value()
if (players_clk_value != new_players_clk_value):
players_clk_value = new_players_clk_value
dt_value = players_dt.value()
if not players_clk_value and dt_value: #Clockwise rotation
time.sleep(0.5)
increase_players()
elif not players_clk_value and not dt_value: #Counterclockwise rotation
time.sleep(0.5)
decrease_players()
def set_display_color(rgb):
# Unpack the RGB tuple into individual components
r, g, b = rgb
# Convert the RGB values to a 16-bit format for the display
color_16_bit = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
# Set the display color using the 16-bit color value
# Assuming 'display' is an instance of your display driver
display.fill(color_16_bit)
def check_touch():
global state, current_player, button_press_start_time, skip_player_pressed
touch_detected = touch_sensor.get_touch()
if touch_detected:
if button_press_start_time == 0:
button_press_start_time = time.ticks_ms() # Record the time when touch starts
skip_player_pressed = False
# Check if the touch has been held for more than 1.5 seconds
if not skip_player_pressed and time.ticks_ms() - button_press_start_time >= 1500:
skip_player_pressed = True
next_player()
#set_display_color(player_colors[current_player])
else:
if button_press_start_time > 0 and not skip_player_pressed:
touch_duration = time.ticks_ms() - button_press_start_time
# Short touch detected, mimic the button functionality for Start/Pause/Resume
if touch_duration >= 50:
if state == SHOW_COLOR:
start_timer()
elif state == RUNNING:
pause_timer()
elif state == PAUSED:
resume_timer()
elif state == IDLE:
show_current_player_color()
# Reset the touch timer after processing the touch
button_press_start_time = 0
def check_button():
global button_pressed, button_press_start_time, skip_player_pressed
current_button_state = not button.value()
if current_button_state and not button_pressed: # Detect button pressed
time.sleep(0.05) # Debounce
button_press_start_time = time.ticks_ms()
button_pressed = True
skip_player_pressed = False
elif not current_button_state and button_pressed: # Detect button released
time.sleep(0.05) # Debounce
button_pressed = False
if not skip_player_pressed:
if state == SHOW_COLOR:
start_timer()
elif state == RUNNING:
pause_timer()
elif state == PAUSED:
resume_timer()
elif state == IDLE:
show_current_player_color()
elif current_button_state and button_pressed and not skip_player_pressed: # Button is still being pressed
time.sleep(0.05) # Debounce
if time.ticks_ms() - button_press_start_time >= 1500: # Button held for 2 seconds
skip_player_pressed = True # Mark that we've triggered a skip
next_player() # Skip to the next player
def setup_display():
global display, backlight
spi = SPI(2, baudrate=60000000, polarity=0, phase=0, sck=Pin(10), mosi=Pin(11))
reset_pin = Pin(14, Pin.OUT)
cs_pin = Pin(9, Pin.OUT)
dc_pin = Pin(8, Pin.OUT)
backlight_pin = Pin(2, Pin.OUT)
reset_pin.value(0)
time.sleep(0.1)
reset_pin.value(1)
time.sleep(0.1)
display = gc9a01.GC9A01(
spi,
reset=reset_pin,
cs=cs_pin,
dc=dc_pin,
backlight=backlight_pin,
width=240,
height=240
)
display.init()
display.fill(gc9a01.BLACK)
backlight_pin.value(1)
def setup():
update_number_of_players()
update_speed()
#setup_display()
start_animation()
#set_display_color(player_colors[current_player])
def loop():
#check_touch()
check_button()
update_number_of_players()
update_speed()
if state == RUNNING and time.ticks_ms() - previous_millis >= interval:
update_timer()
if state == PULSING:
pulse_last_led()
if state == FINISHED:
next_player()
def main():
setup()
while True:
loop()
if __name__ == "__main__":
main()