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.
| Call | What 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.
Blinking the Onboard LED
# blink.py - onboard LED blink for Pico, Pico W, Pico 2 and Pico 2 W
from machine import Pin
import time
# 'LED' maps to GP25 on Pico/Pico 2 and to the wireless chip GPIO on W boards
led = Pin('LED', Pin.OUT)
while True:
led.toggle() # flip between on and off
time.sleep(0.5) # 0.5 s per state -> 1 Hz blink
Save it as main.py on the board if you want it to run at power-up. time.sleep() accepts seconds as a float; for finer control use time.sleep_ms() or time.sleep_us().
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
| LED | Pico GPIO | Physical pin | Resistor |
|---|---|---|---|
| Red | GP15 | 20 | 330 ohm |
| Yellow | GP14 | 19 | 330 ohm |
| Green | GP13 | 17 | 330 ohm |
| All cathodes | GND | 18 | - |
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, 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
STATESlist 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 callson_tickevery 500 ms. On the rp2 port,Timer()with no ID uses a virtual timer, so you can create several.global state, ticks_leftis required because the callback modifies variables defined at module level.- The
try/except KeyboardInterruptblock catches Thonny's Stop button so the timer is cancelled and the LEDs are left off. Withouttick.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: usePin('LED').ValueError: invalid pinwith'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.pyon 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.