6.10. GPIO input

Reading a switch (or any digital signal) from a GPIO pin goes through machine.Pin again, this time configured as an input. The interface is symmetric with output: configure the mode, then either poll the pin’s value in the main loop or register an interrupt handler that fires when the value changes.

6.10.1. Configuring an input

Pass Pin.IN to the constructor, and optionally a pull resistor:

from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)

That configures P0 as an input with the MCU’s internal pull-up resistor enabled. With nothing connected to the pin, the pull-up holds it high; closing a switch from the pin to ground pulls it low.

6.10.2. Reading the value

value() with no arguments returns the current state – 0 for low, 1 for high:

if button.value() == 0:
    print("button pressed")
else:
    print("button released")

In the main-loop pattern, polling looks like this:

while True:
    if button.value() == 0:
        do_action()
    time.sleep_ms(50)

The 50 ms sleep keeps the loop from running at full CPU.

6.10.3. Interrupt-driven input

Polling works, but every iteration of the main loop costs CPU time. For inputs that change rarely – a button press once a minute, an alarm signal – irq() registers a handler that runs only when the pin changes.

The handler runs in interrupt context, which constrains what it can do:

  • No memory allocation. Creating new objects – lists, strings, exceptions, formatted strings – can fail inside an ISR because the heap is locked. Pre-allocate any buffers the handler needs at module scope.

  • No long-running work. The handler should hand off and return. Spending real time inside an ISR delays everything else (other interrupts, the main loop, USB traffic).

  • No printing in a tight ISR. print() allocates, blocks on the UART, and is one of the most expensive things a handler can do.

The standard pattern is for the ISR to schedule the real work via micropython.schedule(), which queues a function to run back in main context at the next safe point:

import micropython

def handle_press(pin):
    print("button pressed")

def on_press(pin):
    micropython.schedule(handle_press, pin)

button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)

The ISR is one line: queue the callback and return. handle_press then runs in normal context, where allocation, print(), and slow I/O are all safe again.

The trigger argument selects which edge fires: