# imports
import machine
import math
import time

#######################################
# Pin and constant definitions
#######################################
SEVEN_SEGMENT_START_PIN = 0
ANALOGUE_INPUT_PIN = 26
BUTTON_PIN = 16
DISPLAY_COUNT = 4
MEASUREMENT_COUNT = 1  # Reduced for quicker reading
DECIMAL_PRECISION = 3
ADC_RANGE = float((math.pow(2, 16) - 1))
DEBOUNCE_TIME = 50  # 50 ms debounce time

digit_list_hex = [
    0x40,  # 0
    0x79,  # 1
    0x24,  # 2
    0x30,  # 3
    0x19,  # 4
    0x12,  # 5
    0x02,  # 6
    0x78,  # 7
    0x00,  # 8
    0x10,  # 9
    0x08,  # A
    0x03,  # B
    0x46,  # C
    0x21,  # D
    0x06,  # E
    0x0E,  # F
    0x7F   # Empty
]

#######################################
# Global variables
#######################################
display_value = 0
segment_pins = []
display_select_pins = []
analogue_voltage_pin = None
button_pin = None
current_display_index = DISPLAY_COUNT - 1
display_timer = None
prev_analogue_voltage = -1
last_button_press = 0

#######################################
# Function definitions
#######################################

# Function to read the ADC pin and
# to convert the digital value to a voltage level in the 0-3.3V range
# This function updates the value of the display_value global variable
def read_analogue_voltage(pin):
    global display_value, prev_analogue_voltage

    total_value = sum(pin.read_u16() for _ in range(MEASUREMENT_COUNT))
    average_value = total_value // MEASUREMENT_COUNT
    analogue_voltage = round((average_value / ADC_RANGE) * 3.3, DECIMAL_PRECISION)

    if analogue_voltage != prev_analogue_voltage:
        prev_analogue_voltage = analogue_voltage
        new_display_value = int(analogue_voltage * math.pow(10, DECIMAL_PRECISION))
        max_val = math.pow(10, DISPLAY_COUNT) - 1

        if new_display_value > max_val:
            print(f'Warning: {new_display_value} exceeds {max_val}, clipping')
            new_display_value = max_val

        if display_value != new_display_value:
            display_value = new_display_value
            reset_display()

        return analogue_voltage

# Function to disable timer that triggers scanning 7 segment displays
def disable_display_timer():
    global display_timer
    display_timer.deinit()

# Function to enable timer that triggers scanning 7 segment displays
def enable_display_timer():
    global display_timer
    display_timer.init(period=4, mode=machine.Timer.PERIODIC, callback=scan_display)

# Function to handle scanning 7 segment displays
# Display the value stored in the display_value global variable
# on available 7-segment displays
def scan_display(timer_int):
    global current_display_index, display_value

    digit = int(abs(display_value) // math.pow(10, current_display_index)) % 10
    display_digit(digit, current_display_index, current_display_index == DECIMAL_PRECISION and DECIMAL_PRECISION != 0)
    current_display_index = (current_display_index - 1) % DISPLAY_COUNT

# Function to display the given value on the display with the specified index
# dp_enable specifies if the decimal point should be on or off
def display_digit(digit_value, digit_index, dp_enable=False):
    if digit_value < 0 or digit_value > len(digit_list_hex):
        return

    for pin in display_select_pins:
        pin.value(0)

    mask = digit_list_hex[digit_value]
    for i in range(7):
        segment_pins[i].value((mask >> i) & 1)

    segment_pins[7].value(not dp_enable)

    if digit_index == -1:
        for pin in display_select_pins:
            pin.value(1)
    elif 0 <= digit_index < DISPLAY_COUNT:
        display_select_pins[digit_index].value(1)

# Function to reset the display
def reset_display():
    global current_display_index

    disable_display_timer()
    current_display_index = DISPLAY_COUNT - 1
    display_digit(16, -1)
    time.sleep(0.1)
    enable_display_timer()

# Function to test available 7-segment displays
def display_value_test():
    for i in range(16):
        display_digit(i, 0)
        time.sleep(0.5)

# Function to setup GPIO/ADC pins, timers and interrupts
def setup():
    global segment_pins, display_select_pins, analogue_voltage_pin, button_pin, display_timer

    display_select_pins = [machine.Pin(i, machine.Pin.OUT) for i in range(SEVEN_SEGMENT_START_PIN + 8, SEVEN_SEGMENT_START_PIN + 8 + DISPLAY_COUNT)]
    for pin in display_select_pins:
        pin.value(0)

    segment_pins = [machine.Pin(i, machine.Pin.OUT) for i in range(SEVEN_SEGMENT_START_PIN, SEVEN_SEGMENT_START_PIN + 8)]
    for pin in segment_pins:
        pin.value(1)

    analogue_voltage_pin = machine.ADC(ANALOGUE_INPUT_PIN)
    button_pin = machine.Pin(BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)

    display_timer = machine.Timer()
    enable_display_timer()

if __name__ == '__main__':
    setup()
    # display_value_test()

    while True:
        if button_pin.value() == 0:
            current_time = time.ticks_ms()
            if current_time - last_button_press > DEBOUNCE_TIME:
                last_button_press = current_time
                voltage = read_analogue_voltage(analogue_voltage_pin)
                if voltage is not None:
                    print(f'Voltage: {voltage} V')
        time.sleep(0.1)
$abcdeabcde151015202530354045505560fghijfghij
$abcdeabcde151015202530fghijfghij
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT