gpiozero framework backed by the lgpio C library.
This guide provides the exact hardware stack, pin mappings, and production-ready Python code to build a reliable industrial-style I/O monitor, along with a debugging matrix for the specific errors you will encounter on the Pi 5 architecture.
The Raspberry Pi Input Output Decision Matrix
Before wiring a single jumper, you must choose the correct interface topology. Connecting raw wires directly to the Pi 5 GPIO header is acceptable for a breadboard LED, but it will destroy your SoC in an electrically noisy environment or when switching inductive loads.
| Scenario / Signal Type | Interface Choice | Concrete Pick (Part Number) |
|---|---|---|
| Clean 3.3V logic, same-board sensors | Direct GPIO Header | N/A (Direct wire) |
| 12V/24V DC industrial sensors, noisy environments | Optocoupler Input Isolation | PC817 4-Channel Isolation Module |
| Switching 120V/240V AC or high-current DC loads | Opto-isolated Relay HAT | Waveshare RPi Relay HAT (B) |
| Need 16+ I/O lines without using standard GPIO | I2C GPIO Expander | MCP23017 I2C Port Expander |
Hardware Spec Sheet & Pin Mapping
The Raspberry Pi 5 introduced a new power architecture and a dedicated RP1 southbridge chip for I/O. This means older 3.3V tolerances and pin behaviors have shifted slightly, making exact part selection critical.
Bill of Materials
- Compute Board: Raspberry Pi 5 (8GB variant) — ~$80. (The 8GB model is recommended to prevent OS-level OOM kills when running background MQTT or camera tasks alongside GPIO polling).
- OS: Raspberry Pi OS Bookworm (64-bit). Note: Bookworm dropped
RPi.GPIOsupport in favor oflgpio. - Output HAT: Waveshare RPi Relay HAT (B) — ~$18. Features 4x 10A/250VAC relays with onboard optocouplers and flyback diodes.
- Input Module: PC817 4-Channel Optocoupler Isolation Module — ~$6. Accepts 3.6V to 30V trigger signals and outputs a clean 3.3V logic low.
- Power Supply: Official Raspberry Pi 27W USB-C PD Power Supply — ~$12. (Required to prevent brownouts when all 4 relays engage simultaneously, drawing ~300mA from the 5V rail).
Pin Mapping Table (BCM Numbering)
The Waveshare HAT hardwires its relays to specific pins. We will map the PC817 inputs to adjacent, uninterrupted GPIO pins to keep the physical wiring clean.
| Function | Module Pin | Pi 5 BCM GPIO | Physical Pin | Electrical Behavior |
|---|---|---|---|---|
| Relay 1 (Output) | HAT CH1 | GPIO 26 | Pin 37 | Active HIGH (3.3V triggers relay) |
| Relay 2 (Output) | HAT CH2 | GPIO 20 | Pin 38 | Active HIGH (3.3V triggers relay) |
| Sensor 1 (Input) | Opto OUT1 | GPIO 17 | Pin 11 | Active LOW (Pulls to GND when triggered) |
| Sensor 2 (Input) | Opto OUT2 | GPIO 27 | Pin 13 | Active LOW (Pulls to GND when triggered) |
| Opto VCC | Opto VCC | 3V3 Power | Pin 1 | 3.3V supply for output side |
| Opto GND | Opto GND | Ground | Pin 9 | Common ground reference |
Wiring the Opto-Isolated Input and Relay Output
Follow these steps precisely. The most common cause of erratic raspberry pi input output behavior is floating grounds or incorrect pull-up resistor configurations on the optocoupler module.
- De-energize and Stack: Ensure the Pi 5 is powered off. Stack the Waveshare Relay HAT (B) directly onto the 40-pin GPIO header. Secure it with the included M2.5 standoffs to prevent mechanical stress on the header.
- Wire the Output Load: Connect your external load (e.g., a 120V AC fan or 12V DC solenoid) to the Relay HAT's screw terminals. Use the
COM(Common) andNO(Normally Open) terminals. Safety Note: If wiring mains AC, ensure the AC neutral and ground are bonded at your service panel, and never switch the neutral line with the relay. - Wire the Optocoupler Power: Connect the PC817 module's
VCCto Pi Pin 1 (3.3V) andGNDto Pi Pin 9. Do not use the 5V pin for the optocoupler output side; feeding 5V into GPIO 17 will destroy the RP1 southbridge pin. - Wire the Input Signals: Connect your external sensor (e.g., a 24V industrial limit switch) to the
IN1terminal on the PC817 module. Connect the sensor's ground to the module'sDC-orGNDterminal. - Route Logic Outputs: Connect a jumper wire from the PC817
OUT1terminal to Pi Pin 11 (GPIO 17). - Verify with Multimeter: Before applying Pi power, use a multimeter in continuity mode. Check that there is no short between the 3.3V rail and Ground. Trigger your external sensor manually and verify the voltage at
OUT1drops from 3.3V to ~0.1V.
Compilable Python Code for Pi 5 (Bookworm OS)
With the release of Bookworm, the Raspberry Pi Foundation officially deprecated RPi.GPIO. The standard is now gpiozero, which uses the lgpio backend on the Pi 5. The code below includes explicit pin definitions, software debouncing, and graceful signal handling to prevent GPIO lockups.
import signal
import sys
import logging
from gpiozero import Button, OutputDevice
# Configure logging for production visibility
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
# --- PIN DEFINITIONS (BCM Numbering) ---
# Input: PC817 Optocoupler OUT1 connected to GPIO 17
INPUT_PIN_SENSOR_1 = 17
# Output: Waveshare Relay HAT CH1 connected to GPIO 26
OUTPUT_PIN_RELAY_1 = 26
def graceful_exit(signum, frame):
"""Handles Ctrl+C or system kill signals to ensure GPIO cleanup."""
logging.info("Shutdown signal received. Cleaning up GPIO states...")
# gpiozero automatically cleans up on exit, but explicit closure prevents edge-case locks
try:
sensor_1.close()
relay_1.close()
except NameError:
pass
sys.exit(0)
# Register signal handlers for SIGINT (Ctrl+C) and SIGTERM
signal.signal(signal.SIGINT, graceful_exit)
signal.signal(signal.SIGTERM, graceful_exit)
def main():
global sensor_1, relay_1
try:
# RELAY SETUP
# Waveshare HAT (B) requires a HIGH signal to energize the relay coil
relay_1 = OutputDevice(OUTPUT_PIN_RELAY_1, active_high=True, initial_value=False)
# INPUT SETUP
# CRITICAL: The PC817 module has onboard pull-up resistors.
# When the optocoupler triggers, it pulls the line to GND (Active LOW).
# We set pull_up=None to avoid conflicting with the hardware resistor,
# and active_state=False to tell gpiozero that 0V means 'pressed'.
sensor_1 = Button(
INPUT_PIN_SENSOR_1,
pull_up=None,
active_state=False,
bounce_time=0.05 # 50ms software debounce for noisy industrial contacts
)
def handle_sensor_activate():
logging.info("Sensor 1 TRIGGERED: Engaging Relay 1.")
relay_1.on()
def handle_sensor_deactivate():
logging.info("Sensor 1 CLEARED: Disengaging Relay 1.")
relay_1.off()
# Bind callbacks to edge detection events
sensor_1.when_pressed = handle_sensor_activate
sensor_1.when_released = handle_sensor_deactivate
logging.info(f"Raspberry Pi Input Output monitor active. Monitoring GPIO {INPUT_PIN_SENSOR_1}.")
# pause() yields execution to the background gpiozero event threads
signal.pause()
except Exception as e:
logging.critical(f"Fatal GPIO initialization error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Debugging: First 3 Checks and Exact Error Strings
When migrating to the Pi 5 or dealing with isolated I/O, you will encounter specific errors. Do not guess; follow this ranked troubleshooting path.
1. The "Pin In Use" or "Busy" Error
Exact Error String: lgpio.error: 'GPIO busy' OR gpiozero.exc.GPIOPinInUse: pin 17 is already in use
- Cause A (Most Likely): A previous instance of your Python script crashed or was killed without running the
finallycleanup block, leaving thelgpiodaemon holding the file lock on/dev/gpiochip4. - Fix: Run
sudo killall python3in the terminal. If using systemd, restart your service withsudo systemctl restart your-script.service. - Cause B: You have the
pigpioddaemon running in the background, which claims all GPIO pins on boot. - Fix: Disable it via
sudo systemctl disable pigpiodand reboot.
2. The Floating Input / Ghost Triggering
Symptom: The relay clicks randomly every few seconds, or the log shows rapid TRIGGERED / CLEARED events without touching the sensor.
- Cause A (Most Likely): You configured
Button()withpull_up=True(the default). The Pi's internal 50kΩ pull-up is fighting the PC817 module's external 10kΩ pull-up, creating an RC oscillation effect when long wires act as antennas. - Fix: Explicitly set
pull_up=Noneandactive_state=Falsein thegpiozeroButton initialization, exactly as shown in the code block above. - Cause B: The external sensor wiring is unshielded and running parallel to AC mains cables, inducing 50/60Hz noise.
- Fix: Increase the
bounce_timeparameter from0.05to0.2(200ms) to filter out AC ripple noise.
3. The Legacy Library Collision
Exact Error String: RuntimeError: Cannot determine SOC peripheral base address
- Cause: You are trying to use the legacy
import RPi.GPIO as GPIOlibrary on a Raspberry Pi 5. The Pi 5 uses the RP1 southbridge, which moved the GPIO memory addresses.RPi.GPIOhardcodes the old BCM2835/2711 addresses and will fundamentally fail on Pi 5 hardware. - Fix: Uninstall the legacy library (
sudo apt remove python3-rpi.gpio) and rewrite your script usinggpiozeroor the rawlgpioPython bindings. See the official gpiozero migration guide for syntax translation.
Extending or Simplifying the Build
Once the base raspberry pi input output circuit is proven on the bench, you can scale the architecture to fit your exact deployment constraints.
If you are only switching a 5V PC fan or a low-current LED strip, drop the Waveshare Relay HAT. Replace it with a single IRLZ44N Logic-Level MOSFET. Connect the gate to GPIO 26 via a 1kΩ resistor, the source to ground, and the drain to your load's ground return. This eliminates the mechanical clicking of the relay, reduces power draw to near zero, and allows for PWM speed control via
gpiozero.PWMOutputDevice.
How to Extend (The Industrial Route):
If you need to monitor 16 different 24V industrial proximity sensors, the Pi's native GPIO header will run out of pins, and wiring 16 individual PC817 modules becomes a rats' nest.
Instead, transition to an I2C architecture. Use a 16-Channel Optocoupler Isolation Board with a PCA9555 I2C Expander. This reduces your Pi wiring to just four pins (3.3V, GND, SDA, SCL) and allows you to read all 16 isolated inputs using the gpiozero.MCP23017 or smbus2 libraries, freeing up the physical GPIO header for SPI displays or UART serial sensors.
By strictly isolating your inputs with optocouplers and utilizing the modern lgpio backend on the Pi 5, you transform a fragile development board into a reliable edge-computing node capable of surviving real-world electrical environments.






