If you ask who created Raspberry Pi, the direct answer is a team of Cambridge University computer scientists led by Eben Upton, alongside Rob Mullins, Jack Lang, Alan Mycroft, David Braben, and Pete Lomas. They formed the Raspberry Pi Foundation in 2009 and launched the first board in 2012. Their goal was to reverse the decline in computer science applicants by providing a cheap, hackable, bare-metal-friendly machine.

But for embedded engineers and bench technicians in 2026, the more critical question is how Upton’s foundational hardware decisions—specifically the choice of Broadcom System-on-Chips (SoCs) and the 40-pin GPIO header—dictate how we write, wire, and debug embedded projects today. The transition from the original memory-mapped Broadcom GPIOs to the modern RP1 southbridge architecture on the Pi 5 has fundamentally changed our debugging workflows.

The Founders and the Broadcom Architecture Evolution

Eben Upton’s decision to partner with Broadcom gave the original Pi access to the BCM2835, a multimedia SoC with undocumented but highly accessible memory-mapped registers. This allowed early hobbyists to write direct-to-hardware C and Python libraries (like the original RPi.GPIO). However, as the platform matured into a serious industrial and embedded tool, the limitations of hanging peripherals directly off the main SoC’s bus became apparent.

Understanding this architectural shift is mandatory before wiring up sensors on modern boards. The table below tracks how the creator's original vision evolved into the current silicon reality.

Board Generation Core SoC GPIO Controller Default I2C Bus Architecture Shift & Debugging Impact
Pi 1 / Zero BCM2835 Integrated (ARM MMIO) /dev/i2c-1 Direct memory access. Legacy RPi.GPIO works. High CPU overhead on bit-banging.
Pi 2 / 3 BCM2836/7 Integrated (ARM MMIO) /dev/i2c-1 Multi-core ARM introduced. I2C clock stretching bugs emerged in hardware.
Pi 4 / 400 BCM2711 Integrated (ARM MMIO) /dev/i2c-1 PCIe Gen 2 added. USB-C power negotiation issues required firmware patches.
Pi 5 (2024+) BCM2712 RP1 Southbridge /dev/i2c-1 GPIOs moved to RP1 chip via PCIe. RPi.GPIO is deprecated; lgpio is now mandatory.

As detailed in the official RP1 Peripherals Datasheet, the Pi 5 routes all GPIO, I2C, and SPI traffic through the RP1 southbridge. If you are porting old code from a Pi 3 to a Pi 5, your legacy GPIO libraries will fail silently or throw permission errors. You must adapt to the new silicon.

Project Build: Pi 5 Hardware Watchdog & I2C Environmental Logger

To demonstrate modern Pi 5 embedded debugging, we will build an environmental logger that reads a BME280 sensor over I2C and triggers a hardware watchdog transistor to pulse a reset line if the I2C bus locks up—a common failure mode in electrically noisy environments.

Difficulty Rating: Intermediate (Requires I2C wiring, NPN transistor biasing, and Linux daemon configuration).
Time to Build: 45 minutes.
Target Board Variant: Raspberry Pi 5 (8GB RAM variant, running Raspberry Pi OS Bookworm or later).

Parts List

  • Microcomputer: Raspberry Pi 5 (8GB)
  • Sensor: Bosch BME280 I2C breakout board (Adafruit 2652 or generic 3.3V variant)
  • Transistor: 2N2222 NPN Bipolar Junction Transistor (for watchdog pulse)
  • Resistors: 1x 1kΩ (base resistor), 2x 10kΩ (I2C pull-ups, if breakout lacks them)
  • Wiring: 24 AWG silicone stranded wire, 2.54mm female header duponts

Pin Mapping Table

Pi 5 Physical Pin BCM / RP1 Name Function Connected To
1 3V3 Power BME280 VIN
3 GPIO2 (SDA1) I2C Data BME280 SDA (via 10kΩ pull-up to 3V3)
5 GPIO3 (SCL1) I2C Clock BME280 SCL (via 10kΩ pull-up to 3V3)
6 GND Ground BME280 GND & 2N2222 Emitter
37 GPIO26 Watchdog Out 2N2222 Base (via 1kΩ resistor)

Complete Python Code with I2C Error Handling

Because the Pi 5 uses the RP1 southbridge, we use lgpio for GPIO control instead of the deprecated RPi.GPIO. We use smbus2 for I2C communication. Install dependencies via terminal: sudo apt install python3-smbus2 python3-rpi-lgpio.

import smbus2
import lgpio
import time
import sys

# --- PIN & I2C DEFINITIONS ---
I2C_BUS = 1
BME280_ADDR = 0x76  # Verify with `i2cdetect -y 1` (some modules use 0x77)
WATCHDOG_PIN = 26   # Physical Pin 37

# Initialize lgpio chip handle for Pi 5 RP1 southbridge
try:
    chip = lgpio.gpiochip_open(4) # Chip 4 is typically the RP1 header on Pi 5
    lgpio.gpio_claim_output(chip, WATCHDOG_PIN)
except Exception as e:
    print(f"FATAL: Failed to open GPIO chip via lgpio: {e}")
    sys.exit(1)

bus = smbus2.SMBus(I2C_BUS)

def pulse_watchdog():
    """Pulses the 2N2222 base to trigger external hardware reset."""
    lgpio.gpio_write(chip, WATCHDOG_PIN, 1)
    time.sleep(0.5)
    lgpio.gpio_write(chip, WATCHDOG_PIN, 0)

def read_bme280_temp():
    """Reads uncompensated temperature from BME280 register 0xFA."""
    # BME280 Register 0xFA holds MSB of temperature
    data = bus.read_i2c_block_data(BME280_ADDR, 0xFA, 3)
    adc_t = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    return adc_t

def main():
    print("Starting Pi 5 Environmental Logger & Watchdog...")
    lgpio.gpio_write(chip, WATCHDOG_PIN, 0) # Ensure watchdog is low on start
    
    while True:
        try:
            raw_temp = read_bme280_temp()
            print(f"Raw ADC Temp: {raw_temp} | Watchdog: OK")
            time.sleep(2)
            
        except OSError as e:
            # Catching the exact I2C bus lockup error
            if e.errno == 121:
                print(f"CRITICAL: {e} - I2C Bus Locked. Pulsing Watchdog.")
                pulse_watchdog()
                time.sleep(5) # Wait for external hardware to reset sensor
            else:
                print(f"Unhandled I2C OSError: {e}")
                break
                
        except KeyboardInterrupt:
            print("\nShutting down gracefully.")
            break

    # Cleanup RP1 GPIO state
    lgpio.gpio_write(chip, WATCHDOG_PIN, 0)
    lgpio.gpiochip_close(chip)
    bus.close()

if __name__ == "__main__":
    main()

Debugging the Pi 5: Exact Error Strings and Fixes

When working with the RP1 southbridge and I2C peripherals, you will inevitably encounter bus lockups or permission faults. If your script crashes, look for this exact error string in your terminal output:

OSError: [Errno 121] Remote I/O error

This is the standard Linux kernel EREMOTEIO error, passed up through the smbus2 library. It means the Pi 5 sent an I2C clock pulse but received no ACK (acknowledge) bit from the slave device.

The First Three Things to Check When It Fails

  1. Run i2cdetect -y 1 in the terminal: If the output shows -- across the entire grid, your BME280 is not pulling the SDA line low to acknowledge. If it shows UU at address 0x76, another kernel driver (like bmp280) has claimed the device, blocking user-space smbus2 access.
  2. Verify Pull-Up Resistors: The Pi 5 RP1 chip has internal pull-ups, but they are weak (~50kΩ). For I2C runs longer than 10cm, or in noisy environments, you must have external 4.7kΩ or 10kΩ pull-ups on both SDA and SCL to 3.3V. Without them, the signal edges are too slow, causing the RP1 I2C controller to time out and throw Errno 121.
  3. Check for 5V Logic Contamination: The Pi 5 GPIOs are strictly 3.3V. If you are using a cheap BME280 breakout board that includes an onboard 5V voltage regulator and logic level shifter, ensure you are feeding it 5V on the VIN pin. If you feed it 3.3V on VIN but the level shifter expects 5V, the I2C bus will dead-lock.

Ranked Causes for Errno 121

Rank Cause Measurement / Fix
1 Missing external I2C pull-ups Measure SDA/SCL with oscilloscope; rise time > 1µs. Add 10kΩ resistors.
2 Kernel driver conflict (UU in i2cdetect) Edit /boot/firmware/config.txt and remove dtparam=i2c-sensor=on.
3 Capacitance on I2C lines (long wires) Keep I2C wires under 30cm. Lower bus speed to 10kHz in config.txt.
4 Sensor brownout / VCC droop Measure 3.3V pin under load. If < 3.1V, power sensor from Pi 5V pin via LDO.

Extending and Simplifying the Build

Depending on your deployment environment, you may need to scale this architecture up for industrial use or down for a quick weekend prototype.

How to Simplify the Build

If you are running this on a clean workbench and do not need a hardware watchdog, drop the 2N2222 transistor and the lgpio dependency entirely. You can rely on a pure software watchdog using Linux's built-in systemd service manager. Wrap the Python script in a .service file and use the Restart=on-failure directive. This eliminates the GPIO wiring and reduces the code to just the smbus2 I2C read loop.

How to Extend the Build

For a robust remote deployment (e.g., an off-grid solar monitoring station), extend the build by adding an MQTT telemetry bridge. Install the paho-mqtt Python library and publish the compensated temperature/humidity data to a local Mosquitto broker. Furthermore, if you find the Pi 5 is drawing too much idle current (approx. 2.5W) for your solar battery bank, offload the I2C polling to an ESP32-S3 running deep sleep, and have the ESP32 wake the Pi 5 only when an environmental threshold is breached via the Pi 5's dedicated PCIe wake pin.

Understanding the lineage of the hardware—from Eben Upton's original Cambridge prototypes to the RP1 silicon on your bench today—is what separates a hobbyist who copies code from an embedded engineer who can debug it when the I2C bus inevitably locks up.