Raspberry Pi Pico LED Blink and GPIO Output Basics in MicroPython

Every GPIO (general-purpose input/output) pin on the Pico can be switched between 0 V and 3.3 V under program control. That single ability lets you light LEDs, switch transistors and relays, and send signals to other chips. In this tutorial you will blink the onboard LED, connect external LEDs with correctly calculated resistors, and build a traffic-light sequence. You will also see the difference between blocking delays and a hardware timer callback.

The machine.Pin Class

In MicroPython all pin work goes through machine.Pin. You create a Pin object with the GPIO number and a mode, then call methods on it. The table lists the calls you will use most.

CallWhat it does
Pin(15, Pin.OUT)Configure GP15 as a push-pull output
led.value(1) or led.on()Drive the pin high (3.3 V)
led.value(0) or led.off()Drive the pin low (0 V)
led.toggle()Invert the current output state
led.value()Read back the current level
Pin("LED", Pin.OUT)The onboard LED on any Pico board in recent firmware

On the original Pico and Pico 2 the green LED is connected to GP25, so Pin(25, Pin.OUT) works. On the Pico W and Pico 2 W the LED is wired to a GPIO on the CYW43439 wireless chip instead, and MicroPython exposes it under the name "LED". Using Pin("LED") everywhere keeps your code portable across all four boards.

Wiring External LEDs

An LED needs a series resistor to limit current. The resistor value comes from Ohm's law using the supply voltage, the LED forward voltage and the current you want. RP2040 GPIO pins default to a 4 mA drive strength, so aim for a few milliamps; modern LEDs are clearly visible at 2 to 5 mA.

R = (Vgpio - Vf) / I

Red LED:   R = (3.3 V - 2.0 V) / 0.004 A = 325 ohm  -> use 330 ohm
Green LED: R = (3.3 V - 2.2 V) / 0.004 A = 275 ohm  -> use 330 ohm (slightly dimmer)
Blue LED:  Vf about 3.0 V leaves only 0.3 V, so use 100 ohm or a lower value
LEDPico GPIOPhysical pinResistor
RedGP1520330 ohm
YellowGP1419330 ohm
GreenGP1317330 ohm
All cathodesGND18-

Connect each GPIO to one end of its resistor, the other end of the resistor to the LED anode (the longer leg), and every LED cathode (the shorter leg, flat side of the body) to a ground pin. Physical pin 18 is a GND pin right next to GP13 and GP14, which keeps the breadboard wiring short.

Traffic Light Sequence with a Hardware Timer

A loop with sleep() is simple, but while the CPU sleeps it cannot do anything else. machine.Timer calls a function periodically in the background, leaving the main loop free. The program below runs a traffic-light state machine from a timer and keeps the onboard LED blinking in the main loop to prove both run at once.

traffic_light.py - timer-driven traffic light on GP13-GP15 with an independent heartbeat LED
# traffic_light.py - timer-driven traffic light on GP13, GP14, GP15
from machine import Pin, Timer
import time

red = Pin(15, Pin.OUT)
yellow = Pin(14, Pin.OUT)
green = Pin(13, Pin.OUT)
heartbeat = Pin('LED', Pin.OUT)

# Each state: (red, yellow, green, duration in timer ticks of 0.5 s)
STATES = [
    (1, 0, 0, 10),   # red for 5 s
    (1, 1, 0, 2),    # red + yellow for 1 s (get ready)
    (0, 0, 1, 8),    # green for 4 s
    (0, 1, 0, 3),    # yellow for 1.5 s
]

state = 0
ticks_left = STATES[0][3]

def apply(index):
    r, y, g, _ = STATES[index]
    red.value(r)
    yellow.value(y)
    green.value(g)

def on_tick(timer):
    # Runs every 500 ms; keep it short and do not use sleep() here
    global state, ticks_left
    ticks_left -= 1
    if ticks_left <= 0:
        state = (state + 1) % len(STATES)
        ticks_left = STATES[state][3]
        apply(state)

apply(state)
tick = Timer(period=500, mode=Timer.PERIODIC, callback=on_tick)

try:
    while True:
        heartbeat.toggle()       # main loop is free for other work
        time.sleep(0.1)
except KeyboardInterrupt:
    tick.deinit()                # stop the timer when Stop is pressed
    for p in (red, yellow, green, heartbeat):
        p.off()

How the Code Works

  • The STATES list describes the sequence as data, so changing timings or adding a pedestrian phase means editing the table rather than the logic.
  • Timer(period=500, mode=Timer.PERIODIC, callback=on_tick) creates a software-scheduled timer that calls on_tick every 500 ms. On the rp2 port, Timer() with no ID uses a virtual timer, so you can create several.
  • global state, ticks_left is required because the callback modifies variables defined at module level.
  • The try/except KeyboardInterrupt block catches Thonny's Stop button so the timer is cancelled and the LEDs are left off. Without tick.deinit() the timer would keep running after your script ends.

Common Mistakes

  • LED never lights: it is reversed. Swap the legs; the longer leg goes towards the GPIO.
  • Pin(25) does nothing on a Pico W: use Pin('LED').
  • ValueError: invalid pin with 'LED': the firmware is old or for the wrong board. Update MicroPython.
  • LED is very dim: the wire is on a different GPIO than the code uses. Count physical pins carefully, remembering that every fifth pin is ground.
  • Program runs once from Thonny but not on power-up: save it as main.py on the Pico.

Summary

You configured outputs with machine.Pin, blinked the onboard LED in a way that works on every Pico variant, calculated LED resistors from Ohm's law, and drove a three-LED traffic light from a periodic timer while the main loop stayed free. These output skills are the base for reading inputs, which is the topic of the next tutorial on push buttons and interrupts.