The Raspberry Pi 3 40-pin header is a powerhouse for embedded projects, but it is also a trap for the unprepared. The physical pin numbers stamped on the board do not match the Broadcom (BCM) GPIO numbers used in Python. Confusing Physical Pin 11 with BCM GPIO 11 is the fastest way to send 5V into a 3.3V logic line and permanently brick your SoC. This guide provides a definitive raspberry pi 3 gpio pin diagram mapping, followed by a complete, fail-safe environmental control build using an I2C sensor and a 5V relay.

The Raspberry Pi 3 GPIO Pin Diagram: Physical vs. BCM Mapping

The Raspberry Pi 3 Model B and B+ share the identical 40-pin J8 header layout. While there are 40 physical pins, only 26 are general-purpose I/O (GPIO). The rest are dedicated power rails, grounds, and hardware communication buses (I2C, SPI, UART). Below is the critical subset of pins you will use in 95% of embedded projects, mapped across the three common numbering schemes.

Physical Pin BCM GPIO WiringPi (Legacy) Name / Function Bench Notes & Warnings
1 - - 3V3 Power 3.3V output. Max draw is ~50mA total across all 3.3V pins. Do not use for 5V relays.
2 - - 5V Power Tied directly to the USB input. Use for 5V sensors, relay VCC, and opto-isolators.
3 2 8 SDA1 (I2C) Hardware I2C data. Has 1.8kΩ onboard pull-ups to 3.3V. Do not attach 5V I2C devices without a level shifter.
5 3 9 SCL1 (I2C) Hardware I2C clock. Also features 1.8kΩ pull-ups. Can be used to wake the Pi from halt state.
6 - - Ground Primary ground reference. Use for low-current sensor returns.
11 17 0 GPIO 17 Standard GPIO. Excellent for relay control. Defaults to input on boot.
12 18 1 GPIO 18 (PWM0) Hardware PWM capable. Use for dimming LEDs or driving servo motors.
14 - - Ground Located next to the UART TX pin. Essential for serial console debugging.
16 23 4 GPIO 23 Standard GPIO. Good for secondary digital inputs or button reads.
39 - - Ground Bottom-right corner. Convenient for high-current ground returns.

For the full 40-pin visual layout, the community standard reference is Pinout.xyz, which correctly distinguishes between the Pi 3 and the slightly altered power routing on the Pi 4/5. Always verify your physical board revision before applying power.

Project Build: BME280 Environmental Exhaust Controller

We are building a thermal exhaust controller. A BME280 sensor reads ambient temperature and humidity over I2C. If the temperature exceeds a threshold, the Pi triggers an opto-isolated 5V relay to switch on a 12V DC exhaust fan. This build targets the Raspberry Pi 3 Model B+ running Raspberry Pi OS (Bookworm or Bullseye).

Parts List

  • Microcontroller: Raspberry Pi 3 Model B+ (1GB RAM)
  • Sensor: BME280 I2C Breakout Board (Adafruit 2652 or generic 3.3V variant) - ~$10
  • Actuator: 5V Relay Module (1-channel, opto-isolated, active LOW trigger) - ~$4
  • Wiring: Female-to-Female jumper wires (20cm, 28 AWG)
  • Load: 12V DC PC case fan (used here for safe low-voltage demonstration)
⚠️ Mains Voltage Warning: This tutorial switches a 12V DC load. If you adapt this to switch 120V/240V AC mains via the relay, you must use an appropriately rated relay (e.g., 10A 250VAC), ensure the AC wiring is fully enclosed in a non-conductive junction box, and comply with local electrical codes. Never leave exposed mains terminals on a workbench.

Pin Mapping & Wiring Table

Component Component Pin Pi 3 Physical Pin Pi 3 BCM GPIO Wire Color (Suggested)
BME280 VIN / VCC 1 - (3.3V) Red
BME280 GND 6 - (GND) Black
BME280 SCL 5 3 Yellow
BME280 SDA 3 2 Blue
Relay Module VCC 2 - (5V) Red
Relay Module GND 9 - (GND) Black
Relay Module IN (Signal) 11 17 Green

Numbered Wiring Steps

  1. De-energize the Pi: Unplug the micro-USB power cable. Never hot-plug I2C sensors on the Pi 3; the BCM2837 chip is sensitive to voltage spikes on the SDA/SCL lines.
  2. Wire the BME280: Connect VIN to Pin 1 (3.3V), GND to Pin 6, SCL to Pin 5, and SDA to Pin 3. Double-check that SDA and SCL are not swapped.
  3. Wire the Relay Module: Connect VCC to Pin 2 (5V) and GND to Pin 9. The relay coil requires 5V and ~70mA, which the Pi's 5V rail can handle, provided your power supply is rated for at least 2.5A.
  4. Connect the Relay Signal: Connect the IN pin to Physical Pin 11 (BCM 17).
  5. Wire the Load: Connect your 12V fan's positive wire to the relay's COM (Common) terminal, and the 12V power supply positive to the NO (Normally Open) terminal. Connect the fan's negative wire directly to the 12V power supply negative.
  6. Verify: Use a multimeter in continuity mode to verify no shorts exist between the 3.3V and 5V rails before applying power.

Python Control Code with Error Handling

This script uses gpiozero for robust relay control and the smbus2 / RPi.bme280 libraries for I2C communication. It includes explicit error handling for the two most common hardware faults: I2C bus lockups and GPIO permission errors.

Prerequisites: Run sudo apt install python3-gpiozero python3-smbus i2c-tools and pip3 install RPi.bme280. Ensure I2C is enabled via sudo raspi-config (Interface Options > I2C).

import time
import smbus2
import bme280
from gpiozero import OutputDevice
from signal import pause

# --- PIN DEFINITIONS ---
# Using BCM numbering for gpiozero
RELAY_BCM_PIN = 17 

# I2C Configuration
I2C_BUS = 1
# BME280 address is usually 0x76 or 0x77 depending on the breakout board
BME280_ADDRESS = 0x76 

# Initialize Relay (Active LOW relay modules require active_high=False)
exhaust_fan = OutputDevice(RELAY_BCM_PIN, active_high=False, initial_value=False)

def read_sensor():
    """Reads BME280 data over I2C with calibration."""
    bus = smbus2.SMBus(I2C_BUS)
    calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
    data = bme280.sample(bus, BME280_ADDRESS, calibration_params)
    return data.temperature, data.humidity

def main():
    TEMP_THRESHOLD = 28.0  # Celsius
    HYSTERESIS = 1.5       # Prevents relay flutter at the threshold
    
    print(f"Starting Environmental Controller on BCM GPIO {RELAY_BCM_PIN}...")
    
    try:
        while True:
            try:
                temp, humidity = read_sensor()
                print(f"Temp: {temp:.1f}C | Humidity: {humidity:.1f}%")
                
                # Control logic with hysteresis
                if temp > TEMP_THRESHOLD and not exhaust_fan.is_active:
                    print("[ACTION] Temp high. Engaging exhaust fan.")
                    exhaust_fan.on()
                elif temp < (TEMP_THRESHOLD - HYSTERESIS) and exhaust_fan.is_active:
                    print("[ACTION] Temp normalized. Disengaging exhaust fan.")
                    exhaust_fan.off()
                    
            except OSError as e:
                # Catches I2C NACK and bus errors
                print(f"[ERROR] I2C Communication Fault: {e}")
                print("Check SDA/SCL wiring and ensure I2C is enabled in raspi-config.")
                # Failsafe: Turn off relay if we lose sensor data to prevent runaway heating
                exhaust_fan.off() 
                
            time.sleep(5)
            
    except KeyboardInterrupt:
        print("\n[SYSTEM] Manual interrupt received. Shutting down safely.")
    finally:
        # gpiozero handles cleanup automatically on exit, but explicit is better
        exhaust_fan.off()
        exhaust_fan.close()
        print("[SYSTEM] Relay secured. GPIO resources released.")

if __name__ == "__main__":
    main()

Debugging: I2C NACKs and GPIO Permission Errors

When working with the Pi 3's GPIO and I2C buses, you will inevitably hit hardware-level exceptions. Here is how to diagnose the exact error strings Python throws at you.

Error 1: OSError: [Errno 121] Remote I/O error

This is an I2C NACK (Not Acknowledged). The Pi sent a clock pulse on SCL, but the BME280 did not pull the SDA line low to acknowledge. Ranked Causes:

  1. Wrong I2C Address: Some BME280 breakouts default to 0x77 instead of 0x76. Run i2cdetect -y 1 in the terminal. If you see 77 in the grid, update the BME280_ADDRESS variable in the code.
  2. Swapped SDA/SCL: Physical Pin 3 is SDA, Pin 5 is SCL. They are not interchangeable. Swap them and reboot.
  3. Missing Pull-ups: The Pi 3 has 1.8kΩ internal pull-ups on Pins 3 and 5. If you are using a raw BME280 chip (not a breakout board) or running long wires (>30cm), the signal integrity degrades. Add external 4.7kΩ pull-up resistors to the 3.3V rail.

Error 2: RuntimeError: No access to /dev/mem. Try running as root!

This occurs when using the legacy RPi.GPIO library or older versions of gpiozero without proper user permissions. The script is trying to map the physical memory addresses of the BCM chip but is blocked by the OS. Fix: If you are on an older OS (Buster), run the script with sudo python3 script.py. If you are on Bookworm, ensure your user is in the gpio and i2c groups by running sudo usermod -aG gpio,i2c $USER and then logging out and back in.

The First Three Things to Check When It Fails

Before rewriting code or blaming the Pi, execute this bench checklist:

  1. Run i2cdetect -y 1: If the grid is entirely empty (only dashes), your I2C interface is disabled in raspi-config, or your ground wire is disconnected.
  2. Verify Logic Levels: Put a multimeter on the BME280 VIN pin. It must read 3.3V. If you accidentally wired it to Pin 2 (5V), you have likely fried the sensor's internal voltage regulator, and it will no longer respond to I2C polling.
  3. Check Relay Trigger Logic: Many cheap relay modules are "Active LOW". This means they turn ON when the signal pin is pulled to Ground (0V), and turn OFF when the signal is High (3.3V). The code above handles this via active_high=False in gpiozero. If your relay clicks inversely, flip this boolean.

Extending and Simplifying the Build

How to Extend

  • Add MQTT for Home Assistant: Install paho-mqtt and publish the temp and humidity variables to a broker topic like homeassistant/sensor/workshop_bme280/state. This allows you to graph the thermal data over time without running a local database.
  • Add an OLED Display: Wire an SSD1306 128x64 I2C OLED to the same SDA/SCL bus (Pins 3 and 5). Because I2C is a bus topology, you can daisy-chain devices as long as their addresses don't conflict (the SSD1306 uses 0x3C, so it won't clash with the BME280).

How to Simplify

  • Drop the I2C Sensor: If you just want to test the relay logic, replace the BME280 with a simple physical pushbutton wired between BCM GPIO 17 and Ground, using gpiozero.Button with internal pull-ups. This eliminates all I2C debugging and lets you verify the 5V relay switching mechanics in five minutes.
  • Use a DHT11: If I2C is giving you grief, swap to a DHT11 sensor using a single-wire protocol on BCM GPIO 4. It is slower and less accurate than the BME280, but requires only one data pin and a 10kΩ pull-up resistor.

For deeper reading on the gpiozero API and its hardware abstraction, refer to the official gpiozero documentation. Understanding the physical reality behind the raspberry pi 3 gpio pin diagram is what separates a fragile prototype from a reliable embedded system.