3.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.

3.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.

3.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.

3.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: