The pin configuration of Raspberry Pi 3 boards (specifically the Model B and B+) revolves around the 40-pin GPIO header. While the physical layout hasn't changed since the Pi 2, the underlying SoC (BCM2837 for Pi 3B, BCM2837B0 for Pi 3B+) and the modern Linux kernel handle these pins differently than older boards. The direct answer for beginners: the Pi 3 uses 3.3V logic on 26 usable GPIO pins, and mixing up BCM (Broadcom) software numbering with BOARD (physical) numbering is the root cause of 80% of all GPIO debugging headaches.
This guide cuts through the abstraction. We will map the physical pins to their BCM equivalents, wire up an I2C OLED and a GPIO button, write robust Python code with explicit error handling, and debug the exact error strings the kernel throws when your pin configuration is wrong.
The Raspberry Pi 3 40-Pin Header: BCM vs BOARD Mapping
When configuring pins in software, you must choose a numbering scheme. BOARD refers to the physical pin number on the header (1 through 40). BCM refers to the Broadcom SoC channel number. Modern libraries like gpiozero default to BCM. If you wire a LED to physical pin 11, but tell your code to use BCM 11, you will actually be toggling physical pin 26, and your LED won't light up.
Below is the data-dense reference table for the most critical pins on the Pi 3. Bookmark this.
| Physical Pin | BCM GPIO | Function / Protocol | Voltage / Notes |
|---|---|---|---|
| 1 | N/A | 3V3 Power | Max draw ~50mA across all 3.3V pins |
| 2 | N/A | 5V Power | Direct from USB input; use for high-current sensors |
| 3 | 2 (SDA1) | I2C Bus 1 Data | 3.3V; includes onboard 1.8k pull-up resistor |
| 5 | 3 (SCL1) | I2C Bus 1 Clock | 3.3V; includes onboard 1.8k pull-up resistor |
| 6 | N/A | Ground | Common ground reference |
| 8 | 14 (TXD) | UART Transmit | 3.3V; shared with Bluetooth on Pi 3B/3B+ |
| 10 | 15 (RXD) | UART Receive | 3.3V; shared with Bluetooth on Pi 3B/3B+ |
| 11 | 17 | General Purpose GPIO | 3.3V logic; safe for standard LEDs/buttons |
| 13 | 27 | General Purpose GPIO | 3.3V logic; supports hardware PWM0 |
| 19 | 10 (MOSI) | SPI0 Master Out Slave In | 3.3V; used for high-speed displays/RFID |
| 21 | 9 (MISO) | SPI0 Master In Slave Out | 3.3V |
| 23 | 11 (SCLK) | SPI0 Clock | 3.3V |
| 24 | 8 (CE0) | SPI0 Chip Enable 0 | 3.3V; active low |
| 38 | 20 (MOSI) | SPI1 / PCM | Alternate SPI; requires dtoverlay in config.txt |
| 40 | 21 | SPI1 / PCM | Alternate SPI / I2S audio |
Parts List and Wiring for I2C OLED + GPIO Button Monitor
To demonstrate proper pin configuration, we will build a hardware interrupt monitor. A button on BCM 27 will trigger an event, and an LED on BCM 17 will indicate system status, while an I2C OLED displays the bus state.
Exact Parts List:
- Board: Raspberry Pi 3 Model B+ (2018 revision, 1GB RAM, running Raspberry Pi OS Bookworm or newer)
- Display: 0.96-inch SSD1306 I2C OLED (128x64, 4-pin variant)
- Input: 12mm Momentary Tactile Push Button
- Output: 5mm Red LED with 330Ω current-limiting resistor
- Wiring: Female-to-Female and Male-to-Female Dupont jumper wires (24 AWG)
Wiring Steps:
- De-energize the Pi: Always unplug the 5V micro-USB power supply before manipulating GPIO header wires to prevent accidental shorting of the 5V rail to a data pin.
- Wire the I2C OLED: Connect OLED VCC to Physical Pin 1 (3.3V). Connect OLED GND to Physical Pin 6. Connect OLED SDA to Physical Pin 3 (BCM 2). Connect OLED SCL to Physical Pin 5 (BCM 3).
- Wire the LED: Connect the LED anode (long leg) to a 330Ω resistor, then to Physical Pin 11 (BCM 17). Connect the LED cathode (short leg) to Physical Pin 9 (GND).
- Wire the Button: Connect one button leg to Physical Pin 13 (BCM 27). Connect the other leg to Physical Pin 14 (GND). Note: We will use the Pi's internal pull-up resistor in software, so no external resistor is needed.
- Verify Connections: Use a multimeter in continuity mode to verify no shorts exist between the 3.3V/5V pins and your data pins before applying power.
Complete Python Code with Error Handling
This script targets the Raspberry Pi 3 Model B+. It uses gpiozero for GPIO management and smbus2 for raw I2C bus interaction. We explicitly define our pin configuration variables at the top to prevent magic numbers in the logic.
Prerequisites: Run sudo apt install python3-gpiozero python3-smbus2 and ensure I2C is enabled via sudo raspi-config.
import time
import sys
from gpiozero import LED, Button
from gpiozero.exc import GPIOPinInUse, BadPinFactory
from smbus2 import SMBus
# --- PIN CONFIGURATION (BCM Numbering) ---
# Target: Raspberry Pi 3 Model B+
LED_PIN = 17 # Physical Pin 11
BTN_PIN = 27 # Physical Pin 13
I2C_BUS_ID = 1 # Physical Pins 3 (SDA) and 5 (SCL)
OLED_I2C_ADDR = 0x3C # Standard SSD1306 address
def init_gpio():
"""Initialize GPIO pins with explicit error handling."""
try:
# gpiozero defaults to BCM numbering
status_led = LED(LED_PIN)
trigger_btn = Button(BTN_PIN, pull_up=True, bounce_time=0.05)
return status_led, trigger_btn
except GPIOPinInUse as e:
print(f'GPIO Error: {e}. Check for zombie processes or SPI overlays.')
sys.exit(1)
except BadPinFactory as e:
print(f'Pin Factory Error: {e}. Ensure rpi-lgpio is installed on Bookworm.')
sys.exit(1)
def scan_i2c_bus():
"""Scan I2C Bus 1 to verify OLED pin configuration and wiring."""
try:
with SMBus(I2C_BUS_ID) as bus:
devices = []
for device in range(128):
try:
bus.read_byte(device)
devices.append(hex(device))
except OSError:
pass
return devices
except FileNotFoundError as e:
print(f'I2C Error: {e}. I2C interface is disabled or bus 1 does not exist.')
sys.exit(1)
except PermissionError as e:
print(f'Permission Error: {e}. Add user to i2c group: sudo usermod -aG i2c $USER')
sys.exit(1)
def main():
print('Initializing Raspberry Pi 3 Pin Configuration...')
led, button = init_gpio()
print(f'Scanning I2C Bus {I2C_BUS_ID} (Pins 3 & 5)...')
i2c_devices = scan_i2c_bus()
if hex(OLED_I2C_ADDR) in i2c_devices:
print(f'Success: OLED found at {hex(OLED_I2C_ADDR)}')
led.on()
else:
print(f'Warning: OLED not found at {hex(OLED_I2C_ADDR)}. Found: {i2c_devices}')
led.blink(0.5, 0.5)
print('Waiting for button press on BCM 27 (Physical Pin 13)...')
try:
while True:
if button.is_pressed:
print('Button Pressed! Toggling LED and reading I2C...')
led.toggle()
# Debounce delay
time.sleep(0.5)
time.sleep(0.01)
except KeyboardInterrupt:
print('\nScript interrupted. Cleaning up pins...')
finally:
led.off()
led.close()
button.close()
print('GPIO resources released.')
if __name__ == '__main__':
main()
Debugging Pin Configuration Errors: The First Three Checks
When your script crashes, don't guess. The Linux kernel and the GPIO daemon will tell you exactly what is wrong if you know how to read the trace. Here are the first three things to check, mapped to their exact error strings.
1. The Permission Denied Error
PermissionError: [Errno 13] Permission denied: '/dev/gpiomem'
- Ranked Cause 1: Your user is not in the
gpiogroup. Fix: Runsudo usermod -aG gpio $USERand reboot. - Ranked Cause 2: You are running the script via
cronorsystemdwithout loading the user environment. Fix: Explicitly define the user in your systemd service file or use the absolute path to the Python binary.
2. The Pin Already In Use Error
gpiozero.exc.GPIOPinInUse: Pin 17 is already in use
- Ranked Cause 1: A zombie process from a previous crashed script is still holding the file descriptor. Fix: Run
fuser /dev/gpiomemto find the PID, thenkill -9 [PID]. - Ranked Cause 2: A device tree overlay (like SPI or UART) has claimed the pin at boot. Fix: Check
/boot/firmware/config.txtand comment out conflictingdtoverlaylines.
3. The Missing I2C Bus Error
FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'
- Ranked Cause 1: The I2C kernel module is blacklisted or disabled. Fix: Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. - Ranked Cause 2: You are trying to access Bus 0 instead of Bus 1 in your code. The Pi 3 uses Bus 1 (Pins 3/5) for general user access. Bus 0 is reserved for the internal HAT ID EEPROM. Fix: Ensure your code sets
I2C_BUS_ID = 1.
Extending and Simplifying the Build
Once you have the baseline pin configuration working, you can scale the project up or down based on your hardware constraints.
| Protocol | Pins Used | Speed | Best Use Case |
|---|---|---|---|
| I2C | 2 (SDA, SCL) | Up to 400 kHz (Fast Mode) | Low-speed sensors (BME280), OLEDs, addressing multiple devices on the same two wires. |
| SPI | 4+ (MOSI, MISO, SCLK, CE) | Up to 125 MHz (theoretical) | High-speed data (TFT displays, SD cards, RFID RC522). Requires separate Chip Enable for each target. |
| UART | 2 (TX, RX) | Up to 115200 baud (typical) | GPS modules, legacy serial consoles, communicating with Arduino/ESP32 boards. |
How to Simplify
If you are troubleshooting basic logic and don't have an I2C display, strip the build down to just gpiozero. Remove the smbus2 imports and the I2C scan function. Use the LED as your sole output indicator (solid for boot success, blinking for button press). This isolates the physical pin configuration from I2C kernel module issues, allowing you to verify your wiring and basic permissions first.
How to Extend
To add high-speed data acquisition, utilize the Pi 3's SPI0 interface. Wire an RC522 RFID reader or an MCP3008 analog-to-digital converter using Physical Pins 19 (MOSI), 21 (MISO), 23 (SCLK), and 24 (CE0). You will need to enable SPI via raspi-config. Because SPI does not have the built-in 1.8k pull-up resistors that the Pi 3's I2C lines have, ensure your breakout boards have their own pull-ups or use a dedicated level shifter if the sensor operates at 5V logic.
For further reading on the underlying hardware architecture, refer to the official Raspberry Pi hardware documentation and the gpiozero library API reference for advanced pin factory configurations.






