Electronics Design AU
Raspberry Pi

How to Interface Sensors and Peripherals with Raspberry Pi GPIO

Last updated 29 June 2026 · 13 min read

Direct Answer

Raspberry Pi GPIO pins operate at 3.3 V logic and are not 5 V tolerant — applying 5 V risks permanent damage to the BCM SoC. Each pin can typically source or sink up to 16 mA, with a combined 3.3 V GPIO supply budget of approximately 50 mA. Peripherals (I2C, SPI, UART, PWM) are enabled through device tree overlays in /boot/firmware/config.txt on Raspberry Pi OS Bookworm. For GPIO control in Python, use lgpio on Pi 5 or gpiozero on both Pi 4 and Pi 5 — RPi.GPIO is legacy code that uses direct BCM register access and is not recommended for new projects.

Detailed Explanation

Raspberry Pi GPIO interfacing involves three layers working together: the electrical interface (voltage levels, current limits, level shifting), the kernel interface (device tree overlays that enable hardware peripherals), and the software API (the library your application uses to control GPIO). Getting all three right is the difference between reliable peripheral integration and intermittent failures that are difficult to diagnose.

This page covers the Raspberry Pi 4, Pi 5, Compute Module 4 (CM4), and CM5. Where the CM4 carrier board design diverges from the standard Pi boards — particularly for I2C pull-ups and boot strapping — those differences are called out explicitly.

For a comparison of when to use a Raspberry Pi versus a microcontroller for sensor interfacing, see Raspberry Pi vs Microcontroller. For MCU-centric GPIO configuration covering push-pull, open-drain, pull resistors, and alternate functions, see How Do GPIO Pins Work?. For choosing between I2C, SPI, and UART based on your application requirements, see SPI vs I2C vs UART.

GPIO Electrical Constraints

All Raspberry Pi GPIO pins operate at 3.3 V logic. They are not 5 V tolerant.

Per-pin current limit. Per the Raspberry Pi GPIO documentation, each GPIO pin can typically source or sink up to 16 mA at the highest drive strength setting.

Total 3.3 V GPIO supply budget. The combined current available from the 3.3 V supply rail to all GPIO outputs is approximately 50 mA. This is a supply-rail constraint, not a per-pin limit — multiple simultaneously active GPIO outputs share this budget. Use GPIO outputs to control peripherals (via transistors or dedicated driver ICs) rather than to supply their operating current directly.

5 V signals damage the GPIO. The BCM2711 (Pi 4, CM4) and BCM2712 (Pi 5, CM5) SoCs have a maximum GPIO input voltage of 3.3 V (IOVDD). Exceeding this with a 5 V signal risks permanent damage to the GPIO input cell. The 5 V power header pins on a Raspberry Pi are power supply outputs with no protection to GPIO — they do not provide 5 V tolerance to adjacent GPIO pins.

Level Shifting for 5 V Peripherals

Most modern sensors use 3.3 V logic and connect directly to Pi GPIO without level shifting. Legacy and industrial peripherals commonly use 5 V and require a level shifter.

Bidirectional MOSFET level shifter (for I2C). I2C uses open-drain signalling — both the Pi and the peripheral can pull the bus low, so a bidirectional shifter is required. The standard approach uses N-channel MOSFETs (BSS138 or similar) with pull-up resistors on each side: a 3.3 V pull-up on the Pi side and a 5 V pull-up on the peripheral side. Dedicated I2C buffer ICs (PCA9306, TCA9516) also work and provide well-defined timing characteristics. Do not use a TXB0108 for I2C — it uses a push-pull output driver that is incompatible with open-drain bus signalling.

Unidirectional level shifting (for SPI MISO, UART RX into the Pi). A resistor voltage divider is sufficient to step a 5 V signal down to 3.3 V on a GPIO input: a 1 kΩ resistor in series with a 2 kΩ resistor to GND produces 3.3 V at the junction from a 5 V source. For higher-speed signals where the divider's capacitance becomes a constraint, a dedicated level translator (SN74LVC8T245, SN74AHCT125) is a better choice.

Pi to 5 V peripheral (output from Pi). Most 5 V CMOS inputs recognise a 3.3 V logic high as a valid high — verify the peripheral's VIH minimum in its datasheet before omitting the level shifter on output-only signals.

Enabling GPIO Peripherals: Device Tree Overlays

GPIO peripheral functions — I2C, SPI, UART, PWM, 1-Wire, PCM — are enabled via device tree overlays, not through direct hardware register access. On Raspberry Pi OS, overlays are configured in config.txt:

  • Raspberry Pi OS Bookworm (Debian 12, current): /boot/firmware/config.txt
  • Earlier Raspberry Pi OS releases: /boot/config.txt

The easiest route for standard interfaces is sudo raspi-config → Interface Options, which edits config.txt automatically. For finer control, edit config.txt directly and reboot.

After enabling an interface and rebooting, confirm the kernel device appeared:

ls /dev/i2c-*      # I2C buses — expect /dev/i2c-1 for I2C-1
ls /dev/spidev*    # SPI — expect /dev/spidev0.0 for SPI0
ls /dev/serial*    # UART — expect /dev/serial0 and /dev/serial1

I2C Peripheral Interfacing

I2C-1 is the standard user-accessible I2C bus on the 40-pin GPIO header: GPIO 2 (SDA1, header pin 3) and GPIO 3 (SCL1, header pin 5).

Enabling I2C-1 (add to /boot/firmware/config.txt):

dtparam=i2c_arm=on

Or via raspi-config → Interface Options → I2C → Enable, then reboot.

Pull-up resistors. The standard Raspberry Pi 4B and Pi 5 boards include 1.8 kΩ pull-up resistors to 3.3 V on GPIO 2 and GPIO 3 on the board. The CM4 module itself does not include these pull-ups — a custom CM4 carrier board must add them. Typically 2.2 kΩ to 4.7 kΩ to 3.3 V, sized toward the lower end (2.2 kΩ) for 400 kHz fast-mode I2C. Without pull-ups on a CM4 carrier board, I2C lines float and no communication occurs.

Scanning the bus:

sudo apt install i2c-tools
i2cdetect -y 1

i2cdetect scans I2C-1 (bus number 1) and prints a grid showing which 7-bit addresses respond. A working I2C device appears as a two-digit hex value. If no devices appear when expected: check wiring, confirm pull-ups are present (and sized appropriately for the bus speed), verify the peripheral is powered, and confirm the peripheral's address pin configuration matches the expected address.

Reading and writing registers from the command line:

i2cget -y 1 0x48 0x00 w    # Read 2 bytes from register 0x00 at device address 0x48
i2cset -y 1 0x48 0x01 0x83 # Write 0x83 to register 0x01 at device address 0x48

Python (smbus2):

import smbus2

bus = smbus2.SMBus(1)           # I2C bus 1
value = bus.read_byte_data(0x48, 0x00)
bus.write_byte_data(0x48, 0x01, 0x83)
bus.close()

Install: sudo apt install python3-smbus2 or pip install smbus2. For a higher-level interface with driver support for common sensors, the Adafruit CircuitPython libraries (via adafruit-blinka) work on Pi 4 and Pi 5.

For sensor interfacing specifics — reading a DS18B20 1-Wire temperature sensor, an MCP9808 I2C temperature sensor, or a multi-sensor I2C bus — see How Do You Interface a Digital Temperature Sensor?.

SPI Peripheral Interfacing

SPI0 is the standard user-accessible SPI bus on the 40-pin header. Enable it with one chip select (add to /boot/firmware/config.txt):

dtoverlay=spi0-1cs

After reboot, /dev/spidev0.0 appears. For two chip selects on SPI0, use dtoverlay=spi0-2cs.

Python (spidev):

import spidev

spi = spidev.SpiDev()
spi.open(0, 0)              # SPI bus 0, chip select 0
spi.max_speed_hz = 1_000_000  # 1 MHz — increase gradually while monitoring signal integrity
spi.mode = 0                # CPOL=0, CPHA=0 (check peripheral datasheet)
response = spi.xfer2([0x01, 0x00])
spi.close()

Install: sudo apt install python3-spidev or pip install spidev.

SPI clock rate is limited by trace length, capacitance, and the peripheral's maximum. Start at 1 MHz and increase while monitoring the MISO signal on a logic analyser or oscilloscope. The Pi 4 SoC supports SPI clock rates of several tens of MHz, but peripheral and PCB constraints typically limit practical designs to single-digit MHz.

UART Peripheral Interfacing

The Raspberry Pi 4 and CM4 have two UARTs accessible on the GPIO header: UART0 (PL011, full hardware FIFO) and UART1 (mini-UART, limited FIFO). By default, UART0 is assigned to the Bluetooth co-processor and UART1 is routed to GPIO 14 (TXD) and GPIO 15 (RXD).

To use the full PL011 UART on GPIO 14/15 (at the cost of losing hardware UART for Bluetooth, which falls back to the mini-UART), add to /boot/firmware/config.txt:

dtoverlay=miniuart-bt
enable_uart=1

The serial console also uses GPIO 14/15 by default. Disable it so application firmware has exclusive access: run sudo raspi-config → Interface Options → Serial Port → disable the shell, enable the hardware serial port. Alternatively, remove console=serial0,115200 from /boot/firmware/cmdline.txt manually.

After reboot, the hardware UART appears as /dev/ttyAMA0. On the Pi 5, the UART situation differs — the PL011 is available on GPIO 14/15 by default without the mini-UART workaround.

Hardware PWM

The Raspberry Pi 4/CM4 BCM2711 has two independent hardware PWM channels. They can be routed to four GPIO pins: GPIO 12 (PWM0), GPIO 13 (PWM1), GPIO 18 (PWM0), and GPIO 19 (PWM1).

Enable via device tree (add to /boot/firmware/config.txt):

dtoverlay=pwm-2chan

This routes PWM0 to GPIO 18 and PWM1 to GPIO 19.

Access from Python via the sysfs interface (/sys/class/pwm/) or the RPi.GPIO hardware PWM API on Pi 4. For servo control with precise period and duty cycle, the PCA9685 16-channel PWM IC over I2C is often more practical than using the Pi's hardware PWM channels directly.

Software PWM (bit-banging GPIO from Linux userspace) introduces timing jitter from the Linux kernel scheduler — typically tens of microseconds, occasionally milliseconds under load. Software PWM is acceptable for LED brightness (where timing imprecision is invisible) but should not be used for servo control, stepper motor step signals, or any application requiring sub-millisecond timing accuracy.

GPIO Control APIs

The Linux kernel exposes GPIO through a character device interface (/dev/gpiochip0). Several libraries sit above this:

LibraryPi 4Pi 5Notes
lgpioUses character device; recommended for Pi 5
gpiozeroHigh-level abstraction; best for readability
RPi.GPIOLimitedLegacy; BCM register access; not for new code
libgpiod (C/C++)Low-level character device; production C code

lgpio (Python, recommended for new Pi 5 code):

import lgpio

h = lgpio.gpiochip_open(0)
lgpio.gpio_claim_output(h, 17)    # GPIO 17 as output
lgpio.gpio_write(h, 17, 1)        # Drive high
lgpio.gpio_claim_input(h, 27)     # GPIO 27 as input
level = lgpio.gpio_read(h, 27)    # Read 0 or 1
lgpio.gpiochip_close(h)

gpiozero (Python, recommended for readability):

from gpiozero import LED, Button

led = LED(17)
button = Button(27, pull_up=True)

button.when_pressed = led.on
button.when_released = led.off

Install: sudo apt install python3-lgpio python3-gpiozero (available in Raspberry Pi OS Bookworm repositories).

GPIO Edge Detection and Callbacks

GPIO edge events (rising, falling, or both) can be detected asynchronously using lgpio:

import lgpio, time

def edge_callback(chip, gpio, level, tick):
    print(f"GPIO {gpio} → {level} at tick {tick}")

h = lgpio.gpiochip_open(0)
lgpio.gpio_claim_alert(h, 27, lgpio.RISING_EDGE)
cb = lgpio.callback(h, 27, lgpio.RISING_EDGE, edge_callback)

time.sleep(10)  # Application runs here

cb.cancel()
lgpio.gpiochip_close(h)

Timing caveat. Linux GPIO callbacks execute in a userspace thread subject to the kernel scheduler. Response latency from an edge to callback execution is typically 10–100 µs, with occasional spikes to several milliseconds under system load. This is acceptable for button presses, sensor alert pins, and encoder counting at low speeds. It is not suitable for step-generation, bit-banged serial protocols, or anything requiring deterministic sub-millisecond response — for those cases, add a co-processor MCU (STM32, RP2040) and communicate with it over UART or SPI.

Practical Examples

Reading a 1-Wire temperature sensor (DS18B20). Enable 1-Wire on GPIO 4 (add to /boot/firmware/config.txt):

dtoverlay=w1-gpio

After reboot, the DS18B20 (connected to GPIO 4 with a 4.7 kΩ pull-up to 3.3 V) appears in /sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave. Read temperature from Python by opening and parsing this file, or use the w1thermsensor library. Multiple DS18B20 sensors can share the same GPIO 4 wire, each identified by a unique 64-bit ROM code. See How Do You Interface a Digital Temperature Sensor? for the full DS18B20 and MCP9808 interfacing guide.

Verifying an I2C sensor is present. After wiring a sensor (BMP280, SHT40, MCP9808) to GPIO 2/3, connecting its supply to the 3.3 V header pin, and ensuring pull-ups are present:

i2cdetect -y 1

A working device shows its 7-bit address in the output grid (e.g. 76 for BMP280 at SDO=GND, 77 at SDO=VDD). If the scan shows nothing, systematically check: peripheral power, SDA/SCL wiring orientation, pull-up presence, and the expected address from the datasheet.

Design Considerations

  • CM4 carrier boards must add I2C pull-ups. The CM4 module does not include pull-up resistors on the I2C-1 bus (GPIO 2/3). A custom carrier board without them will show no I2C devices in i2cdetect despite correct sensor wiring. Add 2.2 kΩ to 4.7 kΩ pull-ups to 3.3 V on SDA and SCL on the carrier board — 2.2 kΩ is appropriate for 400 kHz fast-mode I2C with typical bus capacitance.
  • Real-time limitations are architectural, not configurable. The Linux scheduler on the Raspberry Pi introduces GPIO timing jitter that cannot be eliminated from userspace. If the design requires sub-millisecond deterministic GPIO response, add a co-processor MCU (STM32, RP2040) from the start and have it handle hardware-facing I/O. See Raspberry Pi vs Microcontroller for the analysis of when the hybrid pattern is appropriate.
  • Draw sensor supply from power header pins, not GPIO outputs. The 3.3 V GPIO supply rail is limited to approximately 50 mA combined. Power multiple sensors from the 3.3 V power header pins (which come from the board's main supply regulator and are not subject to this GPIO budget), and use GPIO outputs only as control signals.
  • Target lgpio or gpiozero for new code. RPi.GPIO uses BCM processor register access and has limited compatibility with the Pi 5's BCM2712 SoC. Code written against lgpio or gpiozero targets the kernel character device interface and is compatible with current and future Raspberry Pi hardware.
  • Use the Bookworm config.txt path. If running Raspberry Pi OS Bookworm (Debian 12 — the current release), config.txt lives at /boot/firmware/config.txt. Editing /boot/config.txt on a Bookworm system edits the wrong file with no error — overlays remain unloaded.
  • If your product needs GPIO peripheral software, device tree overlay configuration, or driver integration for a Raspberry Pi Compute Module design, Zeus Design's embedded Linux team provides embedded Linux application development and CM4 carrier board design services.

Common Mistakes

  • Connecting 5 V signals directly to GPIO. The BCM SoC GPIO input maximum is 3.3 V. This is the most common and most damaging mistake in Pi peripheral interfacing — damage may be immediate or progressive, and is not covered by warranty.
  • Using RPi.GPIO for new code, especially on Pi 5. RPi.GPIO uses direct BCM register access and has limited Pi 5 compatibility. Migrate to lgpio or gpiozero for new projects.
  • Omitting I2C pull-ups on a CM4 carrier board. The CM4 module has no on-board I2C pull-ups. A carrier board without them results in floating I2C lines and no device detection, even with correct wiring everywhere else.
  • Using software PWM for precision timing. Linux scheduler jitter makes software PWM unsuitable for servo control, motor step generation, or tone synthesis. Use the two hardware PWM channels, or a dedicated PWM IC (PCA9685) over I2C.
  • Powering sensors from GPIO output current. Drawing more than approximately 50 mA combined from GPIO outputs causes output voltage to droop and sensors to behave unpredictably. Connect sensor VCC to the 3.3 V or 5 V power header pins, not to GPIO output pins.
  • Editing the wrong config.txt path. Raspberry Pi OS Bookworm moved config.txt to /boot/firmware/config.txt. Editing the old path on a Bookworm system leaves overlays unloaded silently — the peripheral simply does not appear in /dev/.

Frequently Asked Questions

Can I connect 5 V sensors directly to Raspberry Pi GPIO?
No. Raspberry Pi GPIO pins operate at 3.3 V logic and are not 5 V tolerant. Connecting a 5 V signal to a GPIO input pin exceeds the BCM SoC's maximum input voltage and can permanently damage that GPIO cell. Always use a level shifter between 5 V peripherals and the Pi's GPIO — a MOSFET-based bidirectional level shifter for I2C (which requires open-drain support), or a unidirectional level shifter IC or resistor divider for input-only signals.
Which Python GPIO library should I use for Raspberry Pi?
For the Pi 5, use lgpio — it targets the kernel character device interface and is compatible with the BCM2712 SoC in the Pi 5. For code that needs to run on both Pi 4 and Pi 5, gpiozero is the safest choice: it abstracts the underlying GPIO library and supports multiple backends. RPi.GPIO uses direct BCM register access, is considered legacy, and has limited Pi 5 compatibility — avoid it for new code.
How do I enable I2C on the Raspberry Pi?
Run sudo raspi-config, go to Interface Options → I2C → Enable. Alternatively, add dtparam=i2c_arm=on to /boot/firmware/config.txt (Raspberry Pi OS Bookworm) and reboot. Confirm with ls /dev/i2c-* — the bus appears as /dev/i2c-1. Use i2cdetect -y 1 to scan the bus and verify your sensor is detected at its expected 7-bit address.

References

Related Questions

Related Forum Discussions