← How-Tos
raspberry-pi May 7, 2026 ◯ 8 min read

Raspberry Pi GPIO Programming: Python, Sensors, Motors, and Hardware Projects

What Is GPIO?

GPIO (General Purpose Input/Output) is the interface that lets your Raspberry Pi physically interact with the world. Through these pins, you can read button presses, measure temperature, control LEDs, drive motors, communicate with sensors, and interface with thousands of electronic components. The Pi's GPIO runs at 3.3V logic levels and provides direct software control from Python, C, or any language with GPIO library support.

GPIO Pinout and Key Specifications

The Raspberry Pi 40-pin header (all models since Pi 2B) contains:

Critical Electrical Limits

ParameterMaximumExceeding This Will Pin output current (single)16mADamage the SoC's GPIO driver Total 3.3V rail current50mA (Pi 3) / ~300mA (Pi 4)Overload internal regulator, crash Pi Input voltage tolerance3.3VPermanently destroy the GPIO pin (5V kills it instantly) 5V pin current (from PSU)Limited by your power supplyUndervoltage warnings, instability

The golden rule: Never connect 5V to a GPIO pin. Never draw more than 16mA from a single pin. Always use current-limiting resistors with LEDs.

Setting Up GPIO on Raspberry Pi OS

Enable GPIO Interface

sudo raspi-config

Navigate to Interface Options โ†’ GPIO โ†’ Enable. Reboot if prompted.

Install GPIO Libraries

sudo apt update sudo apt install python3-pip python3-venv -y pip3 install gpiozero RPi.GPIO

gpiozero (recommended for beginners) is a high-level Python library with clean syntax and built-in device classes. RPi.GPIO is lower-level and gives more control but requires more code.

Your First GPIO Program: Blink an LED

Circuit

Using gpiozero (Recommended)

from gpiozero import LED from time import sleep led = LED(17) # GPIO 17 (BCM numbering) while True: led.on() sleep(1) led.off() sleep(1)

Using RPi.GPIO (Lower Level)

import RPi.GPIO as GPIO from time import sleep GPIO.setmode(GPIO.BCM) # Use BCM (GPIO) numbering, not BOARD (pin) GPIO.setup(17, GPIO.OUT) try: while True: GPIO.output(17, GPIO.HIGH) sleep(1) GPIO.output(17, GPIO.LOW) sleep(1) except KeyboardInterrupt: GPIO.cleanup() # Always clean up on exit

Save and Run

nano blink.py # Paste code, save with Ctrl+O, exit with Ctrl+X python3 blink.py

Press Ctrl+C to stop. The LED should blink once per second.

GPIO Numbering Systems

There are two ways to reference pins โ€” this confuses every beginner:

BCM (Broadcom SOC Channel)

Numbers match the Broadcom chip's GPIO channel numbers. These are the "GPIO XX" labels. Most documentation and libraries use BCM. This is the recommended system.

BOARD (Physical Pin Number)

Numbers match the physical position on the header (1-40). Pin 1 is closest to the SD card with 3.3V.

Quick Reference โ€” Most Useful Pins

BCMPhysicalNameCommon Uses GPIO2Pin 3SDA (I2C)I2C data line, general GPIO GPIO3Pin 5SCL (I2C)I2C clock line, general GPIO GPIO4Pin 7GPCLK0General purpose, 1-Wire GPIO14Pin 8TXD (UART)Serial transmit, general GPIO GPIO15Pin 10RXD (UART)Serial receive, general GPIO GPIO17Pin 11SPI1 CE1General purpose GPIO GPIO18Pin 12PWM0Hardware PWM, PCM clock GPIO27Pin 13โ€”General purpose GPIO GPIO22Pin 15โ€”General purpose GPIO GPIO23Pin 16โ€”General purpose GPIO GPIO24Pin 18โ€”General purpose GPIO GPIO10Pin 19MOSI (SPI)SPI data out, general GPIO GPIO9Pin 21MISO (SPI)SPI data in, general GPIO GPIO25Pin 22โ€”General purpose GPIO GPIO11Pin 23SCLK (SPI)SPI clock, general GPIO GPIO8Pin 24CE0 (SPI)SPI chip select, general GPIO GPIO7Pin 26CE1 (SPI)SPI chip select, general GPIO GPIO5Pin 29โ€”General purpose GPIO GPIO6Pin 31โ€”General purpose GPIO GPIO13Pin 33PWM1Hardware PWM GPIO19Pin 35PWM1PCM frame sync, hardware PWM GPIO26Pin 37โ€”General purpose GPIO GPIO20Pin 38PCM DINGeneral purpose GPIO GPIO21Pin 40PCM DOUTGeneral purpose GPIO

Input: Reading Buttons and Sensors

Reading a Button

Circuit: GPIO 27 (Pin 13) โ†’ Button โ†’ GND

from gpiozero import Button from signal import pause button = Button(27, pull_up=True) def on_press(): print("Button pressed!") def on_release(): print("Button released!") button.when_pressed = on_press button.when_released = on_release pause() # Keeps program running

The pull_up=True enables the Pi's internal pull-up resistor. The pin reads HIGH when open and LOW when the button connects it to ground.

Debouncing

Mechanical buttons bounce (multiple on/off transitions) for 5-20ms when pressed. gpiozero handles this automatically with a 100ms bounce time. For RPi.GPIO:

GPIO.add_event_detect(27, GPIO.FALLING, callback=my_callback, bouncetime=200)

PWM (Pulse Width Modulation)

PWM lets you control brightness, motor speed, and servo position by rapidly pulsing a pin on and off.

LED Dimming with PWM

from gpiozero import PWMLED from time import sleep led = PWMLED(18) # Use GPIO 18 (hardware PWM pin) # Fade in and out while True: for brightness in range(0, 101): led.value = brightness / 100 sleep(0.02) for brightness in range(100, -1, -1): led.value = brightness / 100 sleep(0.02)

Servo Control

from gpiozero import Servo from time import sleep servo = Servo(18) servo.min() # Full left (-90 degrees) sleep(1) servo.mid() # Center (0 degrees) sleep(1) servo.max() # Full right (+90 degrees) sleep(1) servo.detach() # Stop sending pulses

Common Sensor Projects

DHT22 Temperature and Humidity Sensor

Wiring: VCC โ†’ 3.3V (Pin 1), DATA โ†’ GPIO4 (Pin 7), GND โ†’ GND (Pin 6). Add a 10kฮฉ pull-up resistor between DATA and VCC.

pip3 install adafruit-circuitpython-dht sudo apt install libgpiod2 import adafruit_dht import board dht = adafruit_dht.DHT22(board.D4) try: temperature = dht.temperature humidity = dht.humidity print(f"Temp: {temperature}ยฐC, Humidity: {humidity}%") except RuntimeError as e: print(f"Reading failed: {e}")

HC-SR04 Ultrasonic Distance Sensor

Requires a voltage divider on the Echo pin (sensor outputs 5V, Pi accepts 3.3V max).

Wiring: VCC โ†’ 5V (Pin 2), Trig โ†’ GPIO23 (Pin 16), Echo โ†’ 1kฮฉ+2kฮฉ voltage divider โ†’ GPIO24 (Pin 18), GND โ†’ GND.

from gpiozero import DistanceSensor from time import sleep sensor = DistanceSensor(echo=24, trigger=23) while True: print(f"Distance: {sensor.distance * 100:.1f} cm") sleep(1)

PIR Motion Sensor

Wiring: VCC โ†’ 5V, OUT โ†’ GPIO17, GND โ†’ GND. Most PIR modules work at 3.3V logic but are powered by 5V.

from gpiozero import MotionSensor pir = MotionSensor(17) pir.when_motion = lambda: print("Motion detected!") pir.when_no_motion = lambda: print("No motion")

Motor Control

DC Motor with L298N H-Bridge

The L298N can drive two DC motors up to 2A each. It accepts 5-35V motor power and 5V logic (which it can supply to the Pi).

Wiring: IN1 โ†’ GPIO5, IN2 โ†’ GPIO6, ENA โ†’ GPIO13 (PWM for speed), motor power โ†’ 12V battery, GND โ†’ shared ground.

from gpiozero import Motor from time import sleep motor = Motor(forward=5, backward=6, enable=13) motor.forward(speed=0.5) # Half speed sleep(2) motor.backward(speed=0.8) # 80% speed reverse sleep(2) motor.stop()

Stepper Motor (28BYJ-48 with ULN2003)

from gpiozero import OutputDevice from time import sleep pins = [OutputDevice(17), OutputDevice(18), OutputDevice(27), OutputDevice(22)] sequence = [[1,0,0,1],[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,1,1],[0,0,0,1]] def step(direction=1, steps=512, delay=0.001): for _ in range(steps): for halfstep in range(8): for pin, val in zip(pins, sequence[halfstep if direction==1 else 7-halfstep]): pin.value = val sleep(delay) step(direction=1, steps=512) # One full rotation clockwise

I2C: Connecting Multiple Devices with Two Wires

I2C lets you connect up to 127 devices using just SDA (GPIO2) and SCL (GPIO3) with shared power and ground.

Enabling I2C

sudo raspi-config # Interface Options โ†’ I2C โ†’ Enable sudo reboot i2cdetect -y 1 # Should show connected device addresses

BME280 Environmental Sensor (I2C)

pip3 install adafruit-circuitpython-bme280 import board import adafruit_bme280 i2c = board.I2C() bme = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76) print(f"Temperature: {bme.temperature:.1f}ยฐC") print(f"Humidity: {bme.relative_humidity:.1f}%") print(f"Pressure: {bme.pressure:.1f} hPa")

SPI: High-Speed Communication

SPI is faster than I2C and uses MOSI (GPIO10), MISO (GPIO9), SCLK (GPIO11), and chip select pins (GPIO8, GPIO7). Enable via raspi-config.

MCP3008 ADC (Analog Input)

The Pi has no analog inputs. The MCP3008 adds 8 channels of 10-bit analog input via SPI.

from gpiozero import MCP3008 from time import sleep adc = MCP3008(channel=0) # Read channel 0 while True: voltage = adc.value * 3.3 # Convert 0-1 reading to 0-3.3V print(f"Voltage: {voltage:.2f}V") sleep(0.5)

UART: Serial Communication

UART is used for GPS modules, Arduino communication, and serial consoles. The Pi's primary UART is on GPIO14 (TX) and GPIO15 (RX).

Reading GPS Data

import serial gps = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1) while True: line = gps.readline().decode('ascii', errors='replace') if line.startswith('$GPGGA'): print(line.strip())

Best Practices and Safety

Troubleshooting

ProblemSolution Permission denied on GPIOAdd user to gpio group: sudo usermod -aG gpio $USER then reboot LED doesn't lightCheck polarity (long leg is anode), verify resistor value, check pin numbering (BCM vs BOARD) Button reads randomlyEnable pull-up/down resistor, add debounce, check wiring I2C device not detectedCheck wiring (SDA and SCL not swapped), verify I2C is enabled, check device address Servo jittersUse external 5V power for servo, add capacitor across power, use hardware PWM pin Pi crashes when motor runsMotor draws too much current. Use separate power supply for motor, add flyback diodes "This channel is already in use" warningPrevious program didn't call GPIO.cleanup(). Reboot or run GPIO.cleanup() manually