03. Python GPIO Control
03. Python GPIO Control¶
This lesson covers Python-based GPIO control on Raspberry Pi. We'll learn two main libraries (RPi.GPIO and gpiozero), implement digital output (LED), digital input (button), PWM control, and sensor integration (DHT11, PIR, ultrasonic).
1. GPIO Library Selection¶
1.1 RPi.GPIO vs gpiozero¶
| Feature | RPi.GPIO | gpiozero |
|---|---|---|
| Level | Low-level (direct GPIO control) | High-level (abstracted device interface) |
| Code Complexity | More code required | Concise, intuitive |
| Fine Control | Precise timing, interrupt control | Abstracted (limited fine control) |
| Learning Curve | Steeper | Gentle |
| Use Cases | Direct control, custom protocols | Rapid prototyping, standard devices |
Recommendation: - Beginners: Start with gpiozero (simple and intuitive) - Advanced Projects: Use RPi.GPIO for precise control
1.2 Library Installation¶
# RPi.GPIO (usually pre-installed)
sudo apt install python3-rpi.gpio
# gpiozero (usually pre-installed)
sudo apt install python3-gpiozero
# Or via pip
pip3 install RPi.GPIO
pip3 install gpiozero
2. Digital Output: LED Control¶
2.1 Basic LED Blink (RPi.GPIO)¶
Circuit: - LED anode (+) â GPIO17 - LED cathode (-) â 220ÎĐ resistor â GND
import RPi.GPIO as GPIO
import time
# Pin configuration
LED_PIN = 17 # GPIO17
# GPIO setup
GPIO.setmode(GPIO.BCM) # Use BCM numbering
GPIO.setup(LED_PIN, GPIO.OUT) # Set as output
try:
while True:
GPIO.output(LED_PIN, GPIO.HIGH) # LED ON
time.sleep(1)
GPIO.output(LED_PIN, GPIO.LOW) # LED OFF
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup() # Reset GPIO state
2.2 Basic LED Blink (gpiozero)¶
from gpiozero import LED
from time import sleep
led = LED(17) # GPIO17
try:
while True:
led.on()
sleep(1)
led.off()
sleep(1)
except KeyboardInterrupt:
pass
Even simpler with gpiozero:
from gpiozero import LED
led = LED(17)
led.blink() # Blink automatically with 1 second intervals
# Custom intervals
led.blink(on_time=0.5, off_time=0.2) # 0.5s ON, 0.2s OFF
2.3 PWM: LED Brightness Control¶
PWM (Pulse Width Modulation) controls LED brightness by varying the duty cycle.
RPi.GPIO version:
import RPi.GPIO as GPIO
import time
LED_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
# Create PWM object (frequency: 1000Hz)
pwm = GPIO.PWM(LED_PIN, 1000)
pwm.start(0) # Start with 0% duty cycle
try:
while True:
# Gradually brighten (0% â 100%)
for duty in range(0, 101, 5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
# Gradually dim (100% â 0%)
for duty in range(100, -1, -5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
except KeyboardInterrupt:
pass
finally:
pwm.stop()
GPIO.cleanup()
gpiozero version:
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
try:
while True:
# Gradually brighten (0 â 1)
for brightness in range(0, 11):
led.value = brightness / 10
sleep(0.1)
# Gradually dim (1 â 0)
for brightness in range(10, -1, -1):
led.value = brightness / 10
sleep(0.1)
except KeyboardInterrupt:
pass
finally:
led.close()
gpiozero built-in pulse effect:
from gpiozero import PWMLED
led = PWMLED(17)
led.pulse() # Automatic breathing effect
3. Digital Input: Button Control¶
3.1 Button Input (Polling Method)¶
Circuit: - Button one side â GPIO27 - Button other side â GND - Enable internal pull-up resistor (or add external 10kÎĐ pull-up resistor)
RPi.GPIO version:
import RPi.GPIO as GPIO
import time
BUTTON_PIN = 27 # GPIO27
GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Enable pull-up
try:
while True:
if GPIO.input(BUTTON_PIN) == GPIO.LOW: # Button pressed
print("Button Pressed!")
time.sleep(0.2) # Debounce delay
time.sleep(0.1)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
gpiozero version:
from gpiozero import Button
from time import sleep
button = Button(27, pull_up=True) # Enable pull-up
try:
while True:
if button.is_pressed:
print("Button Pressed!")
sleep(0.2) # Debounce delay
sleep(0.1)
except KeyboardInterrupt:
pass
3.2 Button Input (Interrupt Method)¶
Interrupt method is CPU-efficient as it responds to events rather than constant polling.
RPi.GPIO version:
import RPi.GPIO as GPIO
import time
BUTTON_PIN = 27
def button_callback(channel):
print(f"Button Pressed! (Pin: {channel})")
GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Register interrupt (trigger on falling edge = button press)
GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING,
callback=button_callback,
bouncetime=200) # 200ms debounce
try:
print("Waiting for button press... (Ctrl+C to exit)")
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
gpiozero version:
from gpiozero import Button
from signal import pause
button = Button(27, pull_up=True, bounce_time=0.2) # 0.2s debounce
def on_pressed():
print("Button Pressed!")
def on_released():
print("Button Released!")
button.when_pressed = on_pressed
button.when_released = on_released
print("Waiting for button press... (Ctrl+C to exit)")
pause() # Wait indefinitely
3.3 LED Toggle with Button¶
from gpiozero import LED, Button
from signal import pause
led = LED(17)
button = Button(27, pull_up=True, bounce_time=0.2)
def toggle_led():
led.toggle()
state = "ON" if led.is_lit else "OFF"
print(f"LED {state}")
button.when_pressed = toggle_led
print("Press button to toggle LED (Ctrl+C to exit)")
pause()
4. Sensor Integration¶
4.1 DHT11 Temperature/Humidity Sensor¶
Circuit: - DHT11 VCC â 3.3V - DHT11 DATA â GPIO4 - DHT11 GND â GND
Library Installation:
pip3 install adafruit-circuitpython-dht
sudo apt install libgpiod2 # Required dependency
Code:
import time
import board
import adafruit_dht
# Initialize DHT11 (GPIO4 = board.D4)
dht_device = adafruit_dht.DHT11(board.D4)
try:
while True:
try:
temperature = dht_device.temperature
humidity = dht_device.humidity
print(f"Temperature: {temperature:.1f}°C")
print(f"Humidity: {humidity:.1f}%")
print("-" * 30)
except RuntimeError as e:
# DHT sensors occasionally fail to read
print(f"Read error: {e}")
time.sleep(2) # DHT11 max sample rate: 1Hz
except KeyboardInterrupt:
pass
finally:
dht_device.exit()
Error Handling: - DHT sensors can fail intermittently â Use try/except - Wait at least 2 seconds between reads - If errors persist, check wiring
4.2 PIR Motion Sensor¶
PIR (Passive Infrared) sensors detect human movement.
Circuit: - PIR VCC â 5V - PIR OUT â GPIO23 - PIR GND â GND
Code (gpiozero):
from gpiozero import MotionSensor
from signal import pause
pir = MotionSensor(23) # GPIO23
def motion_detected():
print("Motion Detected!")
def no_motion():
print("No motion")
pir.when_motion = motion_detected
pir.when_no_motion = no_motion
print("PIR Sensor Ready (Ctrl+C to exit)")
pause()
Code with LED indicator:
from gpiozero import MotionSensor, LED
from signal import pause
pir = MotionSensor(23)
led = LED(17)
pir.when_motion = led.on
pir.when_no_motion = led.off
print("Motion detection with LED indicator")
pause()
4.3 HC-SR04 Ultrasonic Distance Sensor¶
Measures distance using ultrasonic waves (2cm - 400cm range).
Circuit: - HC-SR04 VCC â 5V - HC-SR04 TRIG â GPIO18 - HC-SR04 ECHO â GPIO24 (use voltage divider for 5Vâ3.3V or direct if 3.3V tolerant) - HC-SR04 GND â GND
Code (gpiozero):
from gpiozero import DistanceSensor
from time import sleep
# TRIG: GPIO18, ECHO: GPIO24
sensor = DistanceSensor(echo=24, trigger=18, max_distance=4)
try:
while True:
distance = sensor.distance * 100 # Convert to cm
print(f"Distance: {distance:.1f} cm")
sleep(0.5)
except KeyboardInterrupt:
pass
Manual calculation version (RPi.GPIO):
import RPi.GPIO as GPIO
import time
TRIG_PIN = 18
ECHO_PIN = 24
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIG_PIN, GPIO.OUT)
GPIO.setup(ECHO_PIN, GPIO.IN)
def measure_distance():
# Send 10Ξs pulse to TRIG
GPIO.output(TRIG_PIN, GPIO.HIGH)
time.sleep(0.00001) # 10Ξs
GPIO.output(TRIG_PIN, GPIO.LOW)
# Measure ECHO pulse duration
while GPIO.input(ECHO_PIN) == GPIO.LOW:
pulse_start = time.time()
while GPIO.input(ECHO_PIN) == GPIO.HIGH:
pulse_end = time.time()
pulse_duration = pulse_end - pulse_start
# Distance = (duration à speed of sound) / 2
# Speed of sound: 34300 cm/s
distance = (pulse_duration * 34300) / 2
return distance
try:
while True:
dist = measure_distance()
print(f"Distance: {dist:.1f} cm")
time.sleep(0.5)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
5. Practical Project: Integrated Sensor Monitoring System¶
Integrate multiple sensors and display real-time data.
Components: - DHT11 (Temperature/Humidity) â GPIO4 - PIR (Motion Sensor) â GPIO23 - HC-SR04 (Ultrasonic) â TRIG: GPIO18, ECHO: GPIO24 - LED (Status Indicator) â GPIO17
import time
import board
import adafruit_dht
from gpiozero import MotionSensor, DistanceSensor, LED
class SensorMonitor:
def __init__(self):
# Initialize sensors
self.dht = adafruit_dht.DHT11(board.D4)
self.pir = MotionSensor(23)
self.ultrasonic = DistanceSensor(echo=24, trigger=18, max_distance=4)
self.led = LED(17)
# Motion detection callbacks
self.pir.when_motion = self.on_motion
self.pir.when_no_motion = self.on_no_motion
self.motion_detected = False
def on_motion(self):
self.motion_detected = True
self.led.on()
print("[ALERT] Motion Detected!")
def on_no_motion(self):
self.motion_detected = False
self.led.off()
print("[INFO] No motion")
def read_sensors(self):
data = {}
# Read DHT11
try:
data['temperature'] = self.dht.temperature
data['humidity'] = self.dht.humidity
except RuntimeError as e:
print(f"DHT11 read error: {e}")
data['temperature'] = None
data['humidity'] = None
# Read ultrasonic distance
try:
data['distance'] = self.ultrasonic.distance * 100 # cm
except Exception as e:
print(f"Ultrasonic read error: {e}")
data['distance'] = None
# Motion status
data['motion'] = self.motion_detected
return data
def display_data(self, data):
print("\n" + "="*40)
print(f"Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("-"*40)
if data['temperature'] is not None:
print(f"Temperature: {data['temperature']:.1f}°C")
print(f"Humidity: {data['humidity']:.1f}%")
else:
print("Temperature: Read Failed")
if data['distance'] is not None:
print(f"Distance: {data['distance']:.1f} cm")
else:
print("Distance: Read Failed")
print(f"Motion: {'Detected' if data['motion'] else 'None'}")
print("="*40)
def run(self):
print("Sensor Monitoring System Started")
print("Press Ctrl+C to stop")
try:
while True:
data = self.read_sensors()
self.display_data(data)
time.sleep(3) # Update every 3 seconds
except KeyboardInterrupt:
print("\nProgram stopped by user")
finally:
self.cleanup()
def cleanup(self):
self.dht.exit()
self.led.close()
print("Cleanup complete")
if __name__ == "__main__":
monitor = SensorMonitor()
monitor.run()
Sample Output:
Sensor Monitoring System Started
Press Ctrl+C to stop
========================================
Timestamp: 2024-01-15 14:32:10
----------------------------------------
Temperature: 24.0°C
Humidity: 55.0%
Distance: 125.3 cm
Motion: None
========================================
[ALERT] Motion Detected!
========================================
Timestamp: 2024-01-15 14:32:13
----------------------------------------
Temperature: 24.1°C
Humidity: 55.2%
Distance: 45.7 cm
Motion: Detected
========================================
6. Best Practices¶
6.1 Proper GPIO Cleanup¶
Always call GPIO.cleanup() to reset GPIO state on exit:
try:
# Your code here
pass
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup() # Essential!
Or use context manager:
from gpiozero import LED
with LED(17) as led:
led.on()
# Automatically cleaned up when exiting with block
6.2 Debouncing¶
Mechanical buttons can produce multiple signals (bounce). Use debounce:
# RPi.GPIO
GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING,
callback=callback_func,
bouncetime=200) # 200ms debounce
# gpiozero
button = Button(27, bounce_time=0.2) # 0.2s debounce
6.3 Error Handling¶
Always handle sensor read errors:
try:
temperature = dht_device.temperature
except RuntimeError as e:
print(f"Sensor error: {e}")
temperature = None
6.4 Resource Management¶
Close resources properly:
class SensorSystem:
def __init__(self):
self.sensor = SomeDevice()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.sensor.close()
return False
# Usage
with SensorSystem() as system:
system.read_data()
# Automatically cleaned up
7. Summary¶
Completed Tasks¶
- â GPIO Libraries: Understand RPi.GPIO vs gpiozero differences and selection
- â Digital Output: LED control, PWM brightness control
- â Digital Input: Button input (polling and interrupt methods)
- â Sensor Integration: DHT11, PIR, HC-SR04 sensor interfacing
- â Practical Project: Integrated multi-sensor monitoring system
- â Best Practices: GPIO cleanup, debouncing, error handling
Next Steps¶
| Next Lesson | Topic | Content |
|---|---|---|
| 04. WiFi Networking | Network communication | Python socket programming, HTTP client, device communication |
| 05. BLE Connectivity | Bluetooth Low Energy | BLE protocol basics, sensor data collection via BLE |
| 06. MQTT Protocol | IoT messaging protocol | MQTT broker setup, pub/sub messaging patterns |
Hands-On Exercises¶
- Traffic Light System:
- Use 3 LEDs (Red, Yellow, Green)
- Implement traffic light sequence
-
Add pedestrian button to trigger sequence
-
Smart Night Light:
- Use PIR motion sensor
- Turn on LED when motion detected
-
Auto-off after 30 seconds of no motion
-
Parking Assistant:
- Use ultrasonic sensor
- Show distance on LED (PWM brightness: closer = brighter)
-
Add buzzer for proximity alert
-
Climate Monitor:
- Log DHT11 data every 5 minutes
- Save to CSV file with timestamps
- Display warning if temperature/humidity out of range
Common Issues¶
| Issue | Cause | Solution |
|---|---|---|
| GPIO warnings | Pins not cleaned up | Always call GPIO.cleanup() in finally block |
| Permission denied | Insufficient privileges | Run with sudo or add user to gpio group |
| DHT read errors | Normal intermittent failures | Add try/except, retry logic |
| Button multiple triggers | Contact bounce | Add debounce delay (200ms recommended) |
| Ultrasonic timeout | Out of range or wiring issue | Check max_distance setting, verify wiring |