gwordal

Lesson 2 of 5 · 18 min

GPIO with gpiozero

The 40-pin header on a Raspberry Pi is where Python meets the physical world. It looks like an Arduino header, but it behaves differently in one critical way: the pins run at 3.3 V, and they have no tolerance for anything higher. This lesson covers how to read the pinout, drive an LED, read a button, and avoid the one mistake that quietly destroys Pi boards.

3.3 V logic and the pinout

On an Arduino Uno, a HIGH is 5 V. On a Pi, a HIGH is 3.3 V and a LOW is 0 V. The processor is built from tiny transistors whose insulating layers are too thin to survive 5 V, which is why the chip runs at lower voltages to begin with.

The header has 40 pins, but only about 26 are general purpose. The rest are fixed:

  • Power: 3V3 (physical pins 1 and 17), 5V (pins 2 and 4)
  • Ground: eight pins, for example physical pins 6, 9, 14, 20, 25, 30, 34, 39
  • Special functions: GPIO2 and GPIO3 are I2C, GPIO14 and GPIO15 are the serial port, GPIO10, 9, 11 are SPI

There are two numbering schemes, and mixing them up is the most common beginner error. Physical numbers count pins along the header from 1 to 40. BCM numbers (also called GPIO numbers) are the names the chip uses internally. GPIO17 is the pin at physical position 11. gpiozero always uses BCM numbers.

Drag to orbit

Blue pins: analog inputs. Orange pins: digital I/O.

A board with a pin header. On a Pi, every signal pin is 3.3 V: treat the header as fragile.

Current limits

Each pin is a weak driver. Treat roughly 8 mA per pin as comfortable, 16 mA as the absolute upper limit, and about 50 mA across all pins together. A motor, a relay coil, or a strip of LEDs must never hang directly off a GPIO. Drive them through a transistor or a driver board that has its own supply.

An LED with gpiozero

gpiozero is a library that gives each component a name, so your code reads like a description of the robot instead of a list of register writes. It comes pre-installed on Raspberry Pi OS.

Wire GPIO17 (physical pin 11) through a 220 Ω resistor to the LED anode, and the LED cathode to ground. The resistor value follows Ohm's law, assuming a red LED dropping about 2 V:

I = (3.3 V - 2 V) / 220 Ω = 5.9 mA

That is bright enough and well inside the pin limit.

from gpiozero import LED
from time import sleep

led = LED(17)          # BCM number 17, physical pin 11

while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

You can also write led.blink(on_time=0.5, off_time=0.5). gpiozero runs the blinking in a background thread, so your main program is free to do other work. That is a big difference from the Arduino delay(), which blocks everything.

A button and the pull-up resistor

A button is just a switch. When it is open, the GPIO pin is connected to nothing, which is called floating. A floating pin picks up electrical noise and reads random values, so the pin needs a defined state when the button is not pressed.

A pull-up resistor solves this by connecting the pin to 3.3 V through a high resistance, typically tens of kilohms. With the button open, the pin reads HIGH. When pressed, the button connects the pin to ground, which overpowers the weak pull-up, and the pin reads LOW. Current through the pull-up while pressed is tiny: 3.3 V / 50 kΩ = 66 µA.

The Pi has built-in pull-up resistors you can switch on in software, so you need no external parts. Wire one leg of the button to GPIO27 (physical pin 13) and the other leg to ground.

from gpiozero import LED, Button
from signal import pause

led = LED(17)
button = Button(27)          # internal pull-up is enabled by default

button.when_pressed = led.on
button.when_released = led.off

pause()                      # keep the program alive, waiting for events

Notice that gpiozero hides the inverted logic. The pin reads LOW when pressed, but when_pressed fires anyway. Callbacks like these mean you do not need to poll the pin in a tight loop, which saves CPU time.

Mechanical contacts also bounce: for a few milliseconds the signal flickers between HIGH and LOW as the metal settles. If you count presses, pass bounce_time=0.05 to ignore events within 50 ms of the first edge.

Level shifting and why 5 V kills a pin

Many popular parts speak 5 V. The classic HC-SR04 ultrasonic sensor drives its echo pin to 5 V. If you connect that directly to a Pi pin, the voltage is above the supply the input circuitry expects. Inside the chip, protection diodes conduct and push current into the 3.3 V rail, and the thin gate oxide of the input transistor can break down. Damage can be instant, or the pin can degrade and fail weeks later. The Pi has no protection against this: its GPIO pins are not 5 V tolerant.

For a one-way 5 V to 3.3 V signal, a resistor divider is enough. With 1 kΩ from the sensor to the Pi pin and 2 kΩ from the Pi pin to ground:

Vpin = 5 V x 2 kΩ / (1 kΩ + 2 kΩ) = 3.33 V

For two-way signals such as I2C to a 5 V device, use a bidirectional level shifter module, which costs very little. Going the other way is usually fine: a 3.3 V output often counts as HIGH to a 5 V device, but verify the minimum input-high voltage in the datasheet, since some parts want 0.7 times their supply, which is 3.5 V at 5 V.

Check yourself

A sensor outputs a 5 V signal. You want to read it on a Pi pin using a divider with 1 kΩ on the signal side and 2 kΩ to ground. What voltage does the Pi see?

Check yourself

A button wired between GPIO27 and ground is read with gpiozero using the default settings. What keeps the pin from floating when the button is open?