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:
- 28 GPIO pins: Software-configurable as inputs or outputs
- Power pins: 2 × 5V, 2 × 3.3V
- Ground pins: 8 × GND
- Special interfaces: 2 × I2C, 2 × SPI, 2 × UART
- EEPROM pins: ID_SD and ID_SC (HAT detection — do not use)
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, instabilityThe 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-configNavigate 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.GPIOgpiozero (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
- GPIO 17 (Pin 11) → 330Ω resistor → LED anode (long leg)
- LED cathode (short leg) → GND (Pin 14)
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 exitSave and Run
nano blink.py # Paste code, save with Ctrl+O, exit with Ctrl+X python3 blink.pyPress 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 GPIOInput: 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 runningThe 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 pulsesCommon 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 clockwiseI2C: 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 addressesBME280 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
- Always use current-limiting resistors with LEDs: (3.3V - LED voltage) / desired current. For a red LED (2V forward voltage) at 10mA: (3.3-2)/0.01 = 130Ω. 220Ω is a safe standard value.
- Use a level shifter for 5V devices: Bidirectional level shifters ($1-2 for 4 channels) protect your Pi when interfacing with Arduino, 5V sensors, or legacy logic.
- Power external devices separately: Motors, relays, and multiple sensors should have their own power supply with a shared ground. Don't power motors from the Pi's 5V pin.
- Use a breadboard for prototyping: Solderless breadboards ($3-5) make it easy to experiment without permanent connections.
- Label your wires: Use colored Dupont jumper wires (female-to-male, male-to-male, female-to-female assortments, $5-8).
- Always call GPIO.cleanup(): Or use gpiozero which handles this automatically via context managers.