If your Raspberry Pi Ethernet connection randomly drops or the PoE+ HAT fan controller throws I2C bus errors, the root cause is almost always clock-stretching contention on the I2C bus or a physical layer (PHY) negotiation failure. This guide cuts through the generic reboot advice and gives you the exact diagnostic steps, pin mappings, and Python telemetry code to isolate the fault.

Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS (Bookworm 64-bit).
Target HAT: Official Raspberry Pi PoE+ HAT (Pi 5 Edition).

Parts List and GPIO Pin Mapping

Before writing diagnostic code, verify your hardware stack. The Pi 5 PoE+ HAT differs from the Pi 4 version; it routes high-current power through the new FPC (Flexible Printed Circuit) PCIe connector while relying on the 40-pin header for I2C fan control and telemetry.

Table 1: Required Hardware Stack
Component Exact Variant / Spec Why This Specific Part
Compute Board Raspberry Pi 5 (8GB) Native Gigabit PHY, dedicated PoE FPC header.
PoE HAT Official PoE+ HAT (Pi 5 Edition) Supports 802.3at (30W), integrated I2C fan controller.
Ethernet Cable Cat6a UTP (Solid Copper) Prevents voltage drop over long runs; CCA (Copper Clad Aluminum) will cause link drops.
Sensor (Optional) BME280 Breakout (3.3V) Used in the code below to demonstrate I2C bus contention.

PoE+ HAT Pin Mapping (40-Pin Header)

The PoE+ HAT only physically connects to specific pins on the 40-pin header. The rest pass through to your external sensors.

Table 2: Critical Pin Mapping for PoE+ HAT
Pi 5 Pin GPIO / Function HAT Usage
1 3V3 Power Logic level reference for I2C.
3 GPIO 2 (SDA1) I2C Data line for fan RPM telemetry/control.
5 GPIO 3 (SCL1) I2C Clock line for fan RPM telemetry/control.
2, 4 5V Power Main 5V/5A DC output from the PoE flyback converter.
6, 9, 14 GND Common ground reference.

The First Three Things to Check When Ethernet Fails

When the eth0 interface drops carrier or fails to negotiate Gigabit, run through this physical and OS-level triage before blaming the software stack.

  1. Verify the FPC and GPIO Seating: The Pi 5 PoE+ HAT requires both the 40-pin GPIO header and the FPC ribbon cable to be fully seated. If the FPC cable is slightly crooked, the 5V power rail will brownout under load, causing the Gigabit PHY to reset and drop the link. Power down, disconnect the FPC, and reseat it using the black locking latches.
  2. Check I2C and Overlay Configuration: In Raspberry Pi OS Bookworm, ensure the I2C bus is enabled. Run sudo raspi-config > Interface Options > I2C. Then, verify your /boot/firmware/config.txt contains dtparam=i2c_arm=on. Without this, the HAT's fan controller will stall, leading to thermal throttling that indirectly crashes the USB/PCIe bus managing the Ethernet PHY.
  3. Inspect the Cable for CCA and Split Pairs: Use a wire map tester. Gigabit Ethernet requires all 8 pins (4 twisted pairs). If your cable is Copper Clad Aluminum (CCA) or has a broken pin 4, 5, 7, or 8, the Pi will silently fall back to 100Mbps (Fast Ethernet) or drop the link entirely when PoE current draw increases.

Python Telemetry and Error Handling Script

This script monitors the eth0 interface for carrier drops and RX/TX errors using psutil, while simultaneously polling an I2C sensor. This dual-poll approach intentionally triggers bus contention, allowing you to verify your pull-up resistors and HAT seating.

Bench Tip: Install dependencies first: sudo apt install python3-smbus python3-psutil i2c-tools and pip3 install smbus2.
import psutil
import smbus2
import time
import sys

# TARGET BOARD: Raspberry Pi 5 (8GB)
# TARGET OS: Raspberry Pi OS (Bookworm 64-bit)
# I2C BUS: 1 (Standard for Pi 4/5 GPIO pins 3/5)
I2C_BUS = 1
BME280_ADDR = 0x76  # Common address for BME280 environmental sensor
ETH_IFACE = "eth0"

def get_ethernet_telemetry():
    """Fetches link state, speed, and drop counters for eth0."""
    stats = psutil.net_if_stats().get(ETH_IFACE)
    counters = psutil.net_io_counters(pernic=True).get(ETH_IFACE)
    
    if not stats or not counters:
        return f"[CRITICAL] Interface {ETH_IFACE} not found or down."
    
    state = "UP" if stats.isup else "DOWN"
    return (
        f"Link: {state} | Speed: {stats.speed}Mbps | "
        f"RX_Errors: {counters.errin} | TX_Errors: {counters.errout} | "
        f"RX_Drops: {counters.dropin}"
    )

def read_i2c_sensor():
    """Attempts to read BME280 Chip ID to test I2C bus health."""
    try:
        with smbus2.SMBus(I2C_BUS) as bus:
            # Register 0xD0 holds the chip ID (should be 0x60 for BME280)
            chip_id = bus.read_byte_data(BME280_ADDR, 0xD0)
            return f"I2C OK | BME280 Chip ID: {hex(chip_id)}"
    except FileNotFoundError:
        return "[ERROR] I2C bus disabled in config.txt or raspi-config."
    except OSError as e:
        # This is the exact error we are hunting for
        raise e

def main():
    print("Starting Raspberry Pi Ethernet & I2C Telemetry Monitor...")
    print("Press Ctrl+C to exit.\n")
    
    while True:
        try:
            eth_status = get_ethernet_telemetry()
            i2c_status = read_i2c_sensor()
            
            print(f"[{time.strftime('%H:%M:%S')}] ETH: {eth_status}")
            print(f"[{time.strftime('%H:%M:%S')}] I2C: {i2c_status}")
            print("-" * 60)
            
            time.sleep(5)
            
        except OSError as e:
            if "[Errno 121]" in str(e):
                print(f"\n[FATAL] Caught exact error: {e}")
                print("Action: I2C bus collision detected. Check PoE HAT seating and pull-ups.")
                sys.exit(1)
            else:
                print(f"\n[UNEXPECTED I/O ERROR] {e}")
                sys.exit(2)
        except KeyboardInterrupt:
            print("\nMonitor stopped by user.")
            sys.exit(0)

if __name__ == "__main__":
    main()

Debugging "Remote I/O error" and Link Drops

When running the script above, or when using any I2C sensor alongside the PoE+ HAT, you will eventually hit this exact exception:

OSError: [Errno 121] Remote I/O error

This error originates from the Linux Kernel I2C subsystem when the master (Pi) sends a clock pulse but the slave device fails to acknowledge (NACK) or stretches the clock line indefinitely. Here are the ranked causes and fixes:

  1. I2C Bus Contention (Most Likely): The PoE+ HAT's onboard microcontroller manages the fan PWM and communicates via I2C. If your external sensor (like the BME280) lacks strong pull-up resistors (4.7kΩ), the HAT's clock stretching will corrupt the bus. Fix: Add a dedicated 4.7kΩ pull-up resistor pack to SDA and SCL on your external sensor breakout board.
  2. Loose FPC Power Ribbon: If the FPC cable is loose, the 5V rail sags when the Pi's CPU spikes. This brownout resets the PoE HAT's I2C controller mid-transaction, causing the Pi to register a Remote I/O error. Fix: Reseat the FPC cable and ensure the locking latches are fully engaged.
  3. Address Collision: Some older third-party PoE HATs use I2C address 0x76 or 0x77 for their own telemetry, colliding directly with BME280 sensors. Fix: Use i2cdetect -y 1 to map the bus. If a collision exists, change your sensor's address via its physical jumper pads.

Extending and Simplifying the Build

Depending on your deployment environment, you may need to scale this Raspberry Pi Ethernet node up for routing, or strip it down for reliability.

How to Extend: Dual-WAN / Router Build

To turn this into a firewall or OpenWrt router, you need a second Ethernet port. Do not use cheap SPI modules (like the ENC28J60); they max out at 10Mbps and consume excessive CPU interrupts. Instead, plug a USB 3.0 to Gigabit Ethernet Adapter (Realtek RTL8156B chipset) into the Pi 5's blue USB 3.0 port. The RTL8156B has native mainline Linux kernel support, presenting as eth1 with near-zero CPU overhead.

How to Simplify: The Passive PoE Splitter

If you don't need the HAT's integrated fan control and are tired of I2C bus errors, ditch the HAT entirely. Use a 48V to 5V/5A USB-C PoE Splitter. This injects power directly into the Pi 5's USB-C port, completely freeing up the 40-pin GPIO header and the I2C bus for your sensors, while still allowing you to run a single Cat6 cable to the enclosure.

Raspberry Pi Ethernet FAQ

Why is my Raspberry Pi Ethernet negotiating at 100Mbps instead of Gigabit?

Gigabit Ethernet (1000BASE-T) requires all four twisted pairs (8 wires) inside the Cat5e/Cat6 cable to be perfectly terminated. If even one wire in pins 4, 5, 7, or 8 is broken, poorly crimped, or missing from a cheap flat cable, the Pi's PHY will automatically fall back to 100Mbps (which only uses pins 1, 2, 3, and 6). Verify your cable with a wire-map tester and ensure you are using solid copper, not CCA (Copper Clad Aluminum).

Can I use the official Pi 4 PoE HAT with the Raspberry Pi 5?

No. While the Pi 4 PoE+ HAT will physically plug into the Pi 5's 40-pin header, it lacks the FPC connector required to route the high-current 5V power from the flyback converter. The Pi 5 will not power on via PoE using the Pi 4 HAT. You must use the specific PoE+ HAT designed for the Raspberry Pi 5, which utilizes the dedicated FPC PCIe/power header.

How do I assign a static IP to the Raspberry Pi Ethernet port in 2026?

Raspberry Pi OS Bookworm uses NetworkManager by default, rendering the old dhcpcd.conf method obsolete. To set a static IP on eth0, open the terminal and use the nmcli command line tool:
sudo nmcli con mod "Wired connection 1" ipv4.addresses 192.168.1.50/24 ipv4.gateway 192.168.1.1 ipv4.dns "1.1.1.1,8.8.8.8" ipv4.method manual
Then restart the connection with sudo nmcli con up "Wired connection 1".