Pi Zero GPIO Basics: LED and Push Button in Python with gpiozero

The 40-pin header is what turns a Pi Zero from a tiny Linux computer into a controller for real hardware. Before moving to sensors, displays and motors, it is worth getting comfortable with the two simplest devices: an LED as an output and a push button as an input. Along the way you will learn the pin numbering scheme, why resistors are needed, how pull-up resistors work, and how to write event-driven code instead of busy loops.

We use gpiozero, the high-level GPIO library that ships with Raspberry Pi OS. On Bookworm it uses the lgpio backend underneath, and the same code runs unchanged on every Raspberry Pi model.

GPIO Concepts You Need

Each GPIO pin on the Zero works at 3.3 V logic. An output pin drives either 0 V or 3.3 V; an input pin reads whether the voltage is near 0 V or near 3.3 V. The pins are connected directly to the processor with no protection, so never connect 5 V to a GPIO pin and never draw large currents from it. A GPIO can light a small LED through a resistor, but motors, relays and buzzers need a transistor or driver.

There are two numbering systems. Physical numbers count the header pins 1 to 40. BCM (Broadcom) numbers are the GPIO names the processor uses, such as GPIO17. gpiozero uses BCM numbers by default. Run pinout in a terminal to see the map for your board.

A push button connected between a GPIO and ground needs a pull-up resistor so the pin reads high when the button is not pressed; otherwise the pin floats and reads random values. The processor has internal pull-ups, and gpiozero's Button enables one by default, so no external resistor is needed.

Hardware and Wiring

If your Zero has no header soldered, solder a 2x20 male header first, or buy the WH version. Push-fit hammer headers also work but soldering is more reliable.

ComponentZero pin (physical)BCM nameNotes
LED anode (long leg)Pin 11GPIO17Through a 330 ohm resistor
LED cathode (short leg)Pin 9GNDAny ground pin works
Button, one sidePin 13GPIO27Internal pull-up enabled by gpiozero
Button, other sidePin 14GNDPressing pulls GPIO27 low
GPIO17 (pin 11) ---[330R]---|>|--- GND (pin 9)
                            LED

GPIO27 (pin 13) ---o  o--- GND (pin 14)
                  button

Why 330 ohms? A typical red LED drops about 2 V, leaving roughly 1.3 V across the resistor at 3.3 V. By Ohm's law, I = 1.3 V / 330 ohm, which is about 4 mA. That is bright enough to see clearly and well within a comfortable load for a GPIO pin.

The Code

gpiozero is normally preinstalled. If python3 -c "import gpiozero" fails, install it with sudo apt install python3-gpiozero python3-lgpio. Save the first program as blink_button.py and run it with python3 blink_button.py.

blink_button.py: toggle an LED with a button, blink on long press
#!/usr/bin/env python3
"""LED and button demo for Raspberry Pi Zero using gpiozero.

Short press : toggle the LED on/off
Hold 2 s    : blink the LED rapidly
"""
from signal import pause
from gpiozero import LED, Button

led = LED(17)                      # BCM GPIO17, physical pin 11
button = Button(27, hold_time=2,   # BCM GPIO27, physical pin 13
                bounce_time=0.05)  # 50 ms software debounce

held = False  # remembers whether the last press was a long press

def on_press():
    global held
    held = False  # new press, not held yet

def on_release():
    # Only toggle on a short press; a long press is handled in on_hold
    if not held:
        led.toggle()
        print('LED is now', 'ON' if led.is_lit else 'OFF')

def on_hold():
    global held
    held = True
    print('Long press: blinking')
    # Runs in a background thread; returns immediately
    led.blink(on_time=0.1, off_time=0.1, n=10)

button.when_pressed = on_press
button.when_released = on_release
button.when_held = on_hold

print('Ready. Press the button (Ctrl+C to quit).')
pause()  # keep the script alive; callbacks run in the background

The second program uses pulse-width modulation to fade the LED. The Zero has two hardware PWM channels, but gpiozero's PWMLED uses software PWM through lgpio, which is fine for LEDs on any pin.

fade.py: breathe the LED with PWM while the button is held down
#!/usr/bin/env python3
"""Fade an LED using PWM. The LED breathes only while the button is held."""
from signal import pause
from gpiozero import PWMLED, Button

led = PWMLED(17, frequency=200)  # 200 Hz avoids visible flicker
button = Button(27)

def start_breathing():
    # fade_in_time/fade_out_time in seconds; background=True returns at once
    led.pulse(fade_in_time=1, fade_out_time=1, background=True)

def stop_breathing():
    led.off()

button.when_pressed = start_breathing
button.when_released = stop_breathing

# You can also set brightness directly from 0.0 to 1.0, e.g. led.value = 0.3
pause()

How the Code Works

Instead of checking the button in a loop, the programs register callback functions. gpiozero watches the pin in a background thread and calls when_pressed, when_released or when_held when the state changes. The main thread simply calls pause(), so the CPU stays almost idle, which matters on a single-core Zero W.

Mechanical buttons bounce: the contacts open and close several times within a few milliseconds of a press. The bounce_time argument ignores further edges for 50 ms after the first one. The held flag stops the release callback from toggling the LED after a long press, so a single physical action produces a single logical action.

Common Mistakes

  • LED never lights: it is in backwards. The longer leg (anode) goes towards the GPIO.
  • Button reads randomly: it is wired to 3.3 V instead of GND. With the default pull-up, the button must connect the pin to ground.
  • GPIO busy error: another script still holds the pin. Stop the other program, then run yours again.
  • Wrong pin: you used physical numbers in code. LED(17) means GPIO17, which is physical pin 11.
  • Intermittent behaviour: unsoldered or loose header pins. Solder the header properly.

Summary

You wired an LED with a current-limiting resistor and a button using the internal pull-up, then controlled them with gpiozero's LED, PWMLED and Button classes using callbacks rather than polling. The same pattern of creating a device object and attaching event handlers carries over to buzzers, motion sensors and relays, and it is the base for the systemd and web dashboard tutorials in this series.