Stage 5 of 6 · Lab, kit and Pi · about 40 minutes
First light
Everything converges here: the terminal from Week 2, the SSH key from Week 3, the environment from an hour ago, and the circuit rules from the last page. Fifteen lines of Python, one LED, one button, and the semester's first complete system.
Wire the circuit
Power down or stop outputs
Wire before running anything: either the Pi is off, or no GPIO script is running. Rule 5 from the previous page.
LED loop
Jumper from physical 11 (GPIO17) to a breadboard row. Resistor from that row to a second row. LED from the second row (long leg here) across to a third row. Jumper from the third row back to physical 6 (GND).
Button loop
Seat the button across the centre gap so its legs cannot short each other. Jumper from physical 13 (GPIO27) to one leg's row; jumper from the diagonally opposite leg's row to physical 14 (GND).
Trace it aloud
Pin by pin against the diagram, both of you: operator points, verifier reads. Count the physical pins from the corner with the square pad; do not trust memory.
Blink: your first output
Over SSH, in the activated environment ((.venv) in the prompt), create blink.py with nano:
from gpiozero import LED
from time import sleep
led = LED(17) # BCM number: GPIO17, the wire on physical pin 11
for n in range(10):
led.on()
sleep(0.5)
led.off()
sleep(0.5)
print(f"blink {n}")
(.venv) $ python blink.py
Ten blinks, then the script ends and the pin releases. If nothing lights, resist the urge to rewire at random: the troubleshoot table has the LED cases in order (backwards LED first, wrong pin second). Then experiment, one variable at a time: pull the delay into delay = 0.2 and feel the code-to-circuit loop close.
What gpiozero just did for you
gpiozero is the Raspberry Pi Foundation's own library, and the three lines hide honest work worth knowing about:
LED(17)claimed GPIO17 from the OS (through a low-level pin driver), configured it as an output, and registered a cleanup so the pin is released and switched off when the script exits, which is why a crashed script does not leave the LED stuck on..on()/.off()drive the pin high and low. There is also.toggle(), and.blink()which blinks in the background without your loop.- The same pattern extends to every component this semester:
Button,Buzzer,MotionSensor,DistanceSensorand dozens more, each documented with a wiring diagram in the gpiozero recipes. Learning one class teaches the shape of all of them.
Read the button
New file, press.py, the polling version, a scan cycle you write yourself:
from gpiozero import Button
from time import sleep
button = Button(27) # internal pull-up enabled; button wired to GND
while True:
if button.is_pressed:
print("pressed")
else:
print("released")
sleep(0.2)
Run it, hold and release the button, watch the truth arrive five times a second, stop with Ctrl+C. This is exactly how a PLC sees an input, and its cost is visible too: the loop burns CPU asking a question whose answer rarely changes, and a press shorter than 0.2 s can slip between polls.
Events: when_pressed
The alternative: tell the library what to do and let it watch the pin. events.py:
from gpiozero import Button
from signal import pause
def hello():
print("pressed!")
button = Button(27, bounce_time=0.05)
button.when_pressed = hello # the function itself, no parentheses
pause() # sleep forever; events do the work
when_pressed = hellohands gpiozero a function to call on each press. Note the absence of(): you are giving it the recipe, not the result of running it once.bounce_time=0.05is the software debounce from the inputs section: contact chatter within 50 ms counts as one press.pause()keeps the script alive doing nothing, while presses fire your function. No polling loop, no missed short presses, no wasted CPU. Event-driven code is how the MQTT programs of Week 9 will be shaped, so this small script is a preview of the course's second half.
Put them together
The week's finale, toggle.py: the button drives the LED, and the wiring does not change at all.
from gpiozero import LED, Button
from signal import pause
led = LED(17)
button = Button(27, bounce_time=0.05)
button.when_pressed = led.toggle # each press flips the LED
pause()
Five meaningful lines: a light switch, implemented in software. Change the behaviour without touching a wire, led.toggle to led.on, add button.when_released = led.off for a momentary switch, and you have demonstrated the entire point of putting a computer between the input and the output. Commit all four scripts and push; the hand-in collects them.
Why this is the whole course in miniature
Sense (button) → decide (your Python) → act (LED). The term project is this triangle with better senses (temperature, light, soil moisture), richer decisions (thresholds, PID), stronger actions (relays, pumps) and a dashboard watching, but the triangle never changes.
Checklist for this stage
Check yourself
Why LED(17) and not LED(11) for a wire on pin 11?
pinout or pinout.xyz settles any doubt.