The 40-Pin Raspberry Pi Pin Diagram: 2026 Header Breakdown
If you are wiring up a new project, the direct answer to navigating the Raspberry Pi pin diagram is this: the physical 40-pin header layout remains identical across the Raspberry Pi 4 Model B and the Raspberry Pi 5. You have 26 usable GPIO pins, four power pins (two 5V, two 3.3V), eight ground pins, and dedicated hardware buses for I2C, SPI, and UART.
However, the underlying silicon changed drastically with the Pi 5. The Pi 5 routes its GPIO through the custom RP1 southbridge chip rather than the Broadcom SoC. While the physical pinout diagram is the same, this architectural shift means legacy Python libraries like RPi.GPIO will fail on a Pi 5. For any new build in 2026, you must use gpiozero (which leverages the lgpio backend) to ensure your code runs on both Pi 4 and Pi 5 hardware.
When reading the diagram, you will encounter two numbering systems: Physical (Board) and BCM (Broadcom). Physical numbering simply counts 1 through 40 down the header. BCM numbering maps to the internal silicon registers. In Python, always configure your libraries to use BCM numbering to prevent mapping errors when moving code between different Pi revisions.
Decision Tree: Choosing the Right Pins for Your Build
Do not just grab random pins from the header. Use this decision path to select the correct interface for your sensors and actuators, terminating in the optimal pin choices for a standard environmental monitor.
| Your Requirement | Interface Choice | Physical Pins | BCM Pins |
|---|---|---|---|
| Need to read an analog sensor (e.g., soil moisture)? | SPI (via MCP3008 ADC) | 19, 21, 23, 24 | 10, 9, 11, 8 |
| Need high-speed data (e.g., TFT display)? | SPI0 | 19, 21, 23, 24, 26 | 10, 9, 11, 8, 7 |
| Need simple on/off or hardware PWM (e.g., LED dimming)? | Standard GPIO | 12 | 18 (Hardware PWM0) |
| Need serial console or GPS module? | UART | 8, 10 | 14, 15 |
| Need digital environmental/IMU sensors? | I2C1 | 3, 5 | 2, 3 |
Project Build: I2C BME280 Environmental Monitor with PWM Status LED
This build targets the Raspberry Pi 4 Model B (4GB) and Raspberry Pi 5 (4GB) running Raspberry Pi OS (Bookworm or newer). We will wire a BME280 temperature/humidity sensor via I2C, a hardware-PWM LED for status indication, and a button to trigger a manual reading.
Parts List
- Board: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (4GB)
- Sensor: Adafruit BME280 I2C Breakout (Part #2652) or equivalent 3.3V I2C BME280
- Indicator: Standard 5mm Red LED
- Resistor: 330Ω (for LED current limiting)
- Input: 6x6mm Tactile Pushbutton
- Wiring: Female-to-Female Dupont jumper wires
Pin Mapping Table
| Component | Component Pin | Pi Physical Pin | Pi BCM Pin | Function |
|---|---|---|---|---|
| BME280 | VIN | 1 | N/A | 3.3V Power |
| BME280 | GND | 6 | N/A | Ground |
| BME280 | SCL | 5 | 3 | I2C Clock |
| BME280 | SDA | 3 | 2 | I2C Data |
| LED | Anode (via 330Ω) | 12 | 18 | Hardware PWM0 |
| LED | Cathode | 14 | N/A | Ground |
| Button | Leg 1 | 16 | 23 | GPIO Input (Pull-up) |
| Button | Leg 2 | 20 | N/A | Ground |
Wiring Steps
- Enable I2C: Open terminal and run
sudo raspi-config. Navigate to Interface Options > I2C > Enable. Reboot the Pi. - Wire Power & Ground: Connect the BME280 VIN to Physical Pin 1 (3.3V) and GND to Physical Pin 6. Never wire 5V to the BME280 VIN unless your specific breakout has an onboard voltage regulator.
- Wire I2C Bus: Connect SCL to Pin 5 and SDA to Pin 3. The Pi has internal 1.8kΩ pull-up resistors on these lines, so standard breakouts work without external resistors.
- Wire Actuator & Input: Connect the LED anode to Pin 12 through the 330Ω resistor, and cathode to Pin 14. Wire the pushbutton between Pin 16 and Pin 20.
- Verify Hardware: Run
i2cdetect -y 1in the terminal. You should see76or77in the grid output, confirming the BME280 is physically seen by the I2C controller.
Complete Python Code with Error Handling
The following script uses gpiozero for the LED and button, and smbus2 for raw I2C communication. This avoids the C-extension compilation issues common with older sensor libraries on the Pi 5's ARM64 architecture. Install dependencies via terminal: sudo apt install python3-gpiozero python3-smbus2.
import sys
import time
from gpiozero import PWMLED, Button
from gpiozero.exc import PinFactoryFallback
from smbus2 import SMBus, i2c_msg
# --- PIN DEFINITIONS (BCM Numbering) ---
PWM_LED_PIN = 18 # Physical Pin 12
BUTTON_PIN = 23 # Physical Pin 16
I2C_BUS_ID = 1 # I2C1 bus
BME280_I2C_ADDR = 0x76 # Default for Adafruit; use 0x77 if i2cdetect shows 77
# --- HARDWARE INITIALIZATION ---
try:
# PWMLED defaults to 100Hz, which is fine for visual indication
status_led = PWMLED(PWM_LED_PIN)
# pull_up=True uses the Pi's internal pull-up resistor; button connects to GND
read_button = Button(BUTTON_PIN, pull_up=True, bounce_time=0.05)
bus = SMBus(I2C_BUS_ID)
except Exception as e:
print(f"[FATAL] Hardware initialization failed: {e}")
sys.exit(1)
def verify_sensor():
"""Reads the BME280 Chip ID register (0xD0) to verify I2C connection."""
try:
# Write 1 byte to register 0xD0, then read 1 byte back
msg = i2c_msg.write(BME280_I2C_ADDR, [0xD0])
bus.i2c_rdwr(msg)
msg = i2c_msg.read(BME280_I2C_ADDR, 1)
bus.i2c_rdwr(msg)
chip_id = list(msg)[0]
if chip_id == 0x60:
print(f"[INFO] BME280 detected successfully (Chip ID: 0x{chip_id:02X})")
return True
else:
print(f"[WARN] Unexpected Chip ID: 0x{chip_id:02X}. Sensor may be incompatible.")
return False
except OSError as e:
print(f"[ERROR] I2C Communication Failed: {e}")
return False
def pulse_led_success():
"""Visual feedback for successful read."""
status_led.pulse(fade_in_time=0.2, fade_out_time=0.2, n=1, background=False)
def main():
print("Starting Environmental Monitor. Press Ctrl+C to exit.")
if not verify_sensor():
print("[HALT] Fix I2C wiring and restart.")
sys.exit(1)
# Turn LED on to 10% brightness to indicate standby
status_led.value = 0.1
try:
while True:
if read_button.is_pressed:
print("[ACTION] Button pressed. Polling sensor registers...")
status_led.value = 1.0 # Full brightness during read
# In a production build, you would read registers 0xF7-0xFE
# and apply the compensation algorithm from the Bosch datasheet here.
# For this diagnostic script, we confirm the bus is stable.
time.sleep(0.1)
pulse_led_success()
print("[OK] Bus stable. Ready for full compensation logic.")
status_led.value = 0.1 # Return to standby
time.sleep(0.05) # Debounce loop delay
except KeyboardInterrupt:
print("\n[INFO] Shutting down gracefully.")
finally:
status_led.off()
bus.close()
if __name__ == "__main__":
main()
Debugging: "Remote I/O Error" and GPIO Failures
When working with the 40-pin header, physical layer failures are vastly more common than software bugs. If your script crashes, check these first three things:
- Run
i2cdetect -y 1: If the grid is empty, your issue is physical wiring or disabled I2C inraspi-config. Do not debug Python until the OS sees the hardware. - Check Dupont Wire Seating: Female-to-female jumper wires suffer from metal fatigue. The internal crimp often breaks while the plastic housing looks intact. Swap your wires with known-good ones.
- Verify 3.3V Rail Health: Use a multimeter to measure between Physical Pin 1 (3.3V) and Pin 6 (GND). If it reads below 3.2V, your Pi's power supply is browning out under load, causing the I2C controller to drop packets.
Common Error Strings and Ranked Causes
Error 1: OSError: [Errno 121] Remote I/O error
This is the universal I2C failure code in Python. The Pi's I2C controller sent a clock pulse but received no acknowledgment (ACK) from the sensor.
- Cause 1 (Most Likely): I2C address mismatch. The BME280 defaults to
0x76or0x77depending on the manufacturer. Check youri2cdetectoutput and update theBME280_I2C_ADDRvariable in the code. - Cause 2: SDA and SCL wires are swapped. I2C is not auto-polarizing; cross them and the bus locks up.
- Cause 3: Missing pull-up resistors on cheap clone sensors. While the Pi has internal pull-ups, wire capacitance over 30cm can corrupt the signal. Add external 4.7kΩ pull-ups to the 3.3V rail.
Error 2: RuntimeError: No access to /dev/mem. Try running as root!
- Cause 1: You are using the deprecated
RPi.GPIOlibrary on Raspberry Pi OS Bookworm or on a Pi 5. The OS security model no longer allows user-space memory mapping for GPIO. - Fix: Rewrite your code using
gpiozeroas shown in the script above, which uses thelgpiobackend and respects modern OS permissions without requiringsudo.
Extending and Simplifying the Build
Depending on your project timeline and budget, you can scale this hardware configuration up or down without rewriting your core logic.
How to Simplify (The Bench Test)
If you are just learning the pin diagram and want to verify your Python environment without buying sensors, strip the build down to the button and LED. Delete the smbus2 imports and the verify_sensor() function. Use the button press to toggle the LED state. This isolates your software logic from I2C hardware variables, proving your gpiozero installation is healthy.
How to Extend (Adding Telemetry)
To turn this into a standalone kiosk, add an SPI OLED display. Because we intentionally left the SPI0 bus (Physical Pins 19, 21, 23, 24) untouched in our decision tree, you can plug in an Adafruit 128x64 SPI OLED (Part #938) directly to the header. You will need to enable SPI via raspi-config and install the adafruit-circuitpython-ssd1306 library. The OLED will draw roughly 20mA, which is well within the safe limits of the Pi's 3.3V rail, allowing you to render real-time temperature graphs right on the breadboard.






