Add interrupt handling and debouncing

This commit is contained in:
2026-03-31 21:46:44 +02:00
parent 0bd11e7f3b
commit 3197ae1933

View File

@@ -14,6 +14,12 @@ BUTTON_SEND = None
RATE_BUTTONS = [] RATE_BUTTONS = []
DISPLAY = None DISPLAY = None
# Variables for Interrupt (ISR) handling and Debouncing
pending_rate_button = -1
pending_send_button = False
last_interrupt_time = 0
DEBOUNCE_MS = 250 # Ignore button presses within 250ms of each other
# Helper to update display based on state # Helper to update display based on state
def refresh_display(message=None): def refresh_display(message=None):
if not DISPLAY: if not DISPLAY:
@@ -26,10 +32,9 @@ def refresh_display(message=None):
DISPLAY.text(message, 10, 30) DISPLAY.text(message, 10, 30)
elif CURRENT_STATE == STATE_USER_ID: elif CURRENT_STATE == STATE_USER_ID:
# 1. User ID Mode # 1. User ID Mode
DISPLAY.text("Inchometer", 25, 0) DISPLAY.text("User ID Mode", 15, 0)
# Display binary representation: _ _ _ _ _ # Display binary representation: _ _ _ _ _
# Bit 4 is leftmost button, Bit 0 is rightmost
binary_str = "" binary_str = ""
for i in range(5): for i in range(5):
bit_pos = 4 - i bit_pos = 4 - i
@@ -49,8 +54,6 @@ def refresh_display(message=None):
else: else:
rating_text = str(RATING) rating_text = str(RATING)
# Draw "big" text (simulated with scale if supported, or just centered)
# Standard ssd1306 doesn't have big fonts, so we just center it
DISPLAY.text(rating_text, 60, 30) DISPLAY.text(rating_text, 60, 30)
DISPLAY.show() DISPLAY.show()
@@ -60,30 +63,35 @@ def initialize_pin(pin_number:int):
button = Pin(pin_number, Pin.IN, Pin.PULL_DOWN) button = Pin(pin_number, Pin.IN, Pin.PULL_DOWN)
return button return button
# activates send pin # Hardware Interrupt: rate button is pressed
def activate_sendPin() -> None:
BUTTON_SEND.irq(trigger=Pin.IRQ_RISING, handler=sendButton_pressed)
# deactivates send pin
def deactivate_sendPin() -> None:
BUTTON_SEND.irq(handler=None)
# rate button is pressed
def rate_button_pressed(pin:Pin) -> None: def rate_button_pressed(pin:Pin) -> None:
global USER_ID, RATING, CURRENT_STATE, RATE_BUTTONS global pending_rate_button, last_interrupt_time
# Find which button index was pressed current_time = time.ticks_ms()
button_index = -1 # Simple software debounce
for i, button in enumerate(RATE_BUTTONS): if time.ticks_diff(current_time, last_interrupt_time) > DEBOUNCE_MS:
if button == pin: # Find which button index was pressed
button_index = i for i, button in enumerate(RATE_BUTTONS):
break if button == pin:
pending_rate_button = i
break
last_interrupt_time = current_time
if button_index == -1: # Hardware Interrupt: Send/Confirm button is pressed
return def sendButton_pressed(pin:Pin) -> None:
global pending_send_button, last_interrupt_time
current_time = time.ticks_ms()
if time.ticks_diff(current_time, last_interrupt_time) > DEBOUNCE_MS:
pending_send_button = True
last_interrupt_time = current_time
# Process the rate button press (Called from the main loop)
def process_rate_button(button_index: int):
global USER_ID, RATING, CURRENT_STATE
if CURRENT_STATE == STATE_USER_ID: if CURRENT_STATE == STATE_USER_ID:
bit_pos = len(RATE_BUTTONS) - 1 - button_index bit_pos = 4 - button_index
USER_ID ^= (1 << bit_pos) USER_ID ^= (1 << bit_pos)
print(f"Current User ID: {USER_ID}") print(f"Current User ID: {USER_ID}")
@@ -92,12 +100,10 @@ def rate_button_pressed(pin:Pin) -> None:
print(f"Current Rating selected: {RATING}") print(f"Current Rating selected: {RATING}")
refresh_display() refresh_display()
activate_sendPin()
# Send/Confirm button is pressed # Process the send button press (Called from the main loop)
def sendButton_pressed(pin) -> None: def process_send_button():
global CURRENT_STATE, USER_ID, RATING global CURRENT_STATE, USER_ID, RATING
deactivate_sendPin()
if CURRENT_STATE == STATE_USER_ID: if CURRENT_STATE == STATE_USER_ID:
# 2. Confirmed ID display # 2. Confirmed ID display
@@ -109,8 +115,7 @@ def sendButton_pressed(pin) -> None:
elif CURRENT_STATE == STATE_RATING: elif CURRENT_STATE == STATE_RATING:
if RATING == 0: if RATING == 0:
activate_sendPin() return # Don't send if no rating is selected yet
return
# 4. Sending / Success / Error # 4. Sending / Success / Error
refresh_display("Sending...") refresh_display("Sending...")
@@ -122,7 +127,7 @@ def sendButton_pressed(pin) -> None:
else: else:
refresh_display("Error") refresh_display("Error")
time.sleep(5) # Display status for 5 seconds time.sleep(3) # Display status for 3 seconds
# Reset for next rating # Reset for next rating
USER_ID = 0 USER_ID = 0
@@ -133,21 +138,32 @@ def sendButton_pressed(pin) -> None:
# start the rating program # start the rating program
def start_rating(rate_button_pins:list, button_send_pin:int, oled_display=None) -> None: def start_rating(rate_button_pins:list, button_send_pin:int, oled_display=None) -> None:
global RATE_BUTTONS, BUTTON_SEND, DISPLAY global RATE_BUTTONS, BUTTON_SEND, DISPLAY
global pending_rate_button, pending_send_button
DISPLAY = oled_display DISPLAY = oled_display
# initialize all pins as buttons and add them to the list # initialize all pins as buttons and attach interrupts
RATE_BUTTONS = [] RATE_BUTTONS = []
for pin_num in rate_button_pins: for pin_num in rate_button_pins:
button = initialize_pin(pin_num) button = initialize_pin(pin_num)
button.irq(trigger=Pin.IRQ_RISING, handler=rate_button_pressed) button.irq(trigger=Pin.IRQ_RISING, handler=rate_button_pressed)
RATE_BUTTONS.append(button) RATE_BUTTONS.append(button)
# initialize send button # initialize send button and attach interrupt
BUTTON_SEND = initialize_pin(button_send_pin) BUTTON_SEND = initialize_pin(button_send_pin)
BUTTON_SEND.irq(trigger=Pin.IRQ_RISING, handler=sendButton_pressed)
print("System started.") print("System started.")
refresh_display() refresh_display()
# The Main Event Loop - This safely handles the screen updates
while True: while True:
time.sleep(1) if pending_rate_button != -1:
process_rate_button(pending_rate_button)
pending_rate_button = -1 # Reset the flag
if pending_send_button:
process_send_button()
pending_send_button = False # Reset the flag
time.sleep_ms(50) # Short delay to save power and avoid choking the CPU