To build a reliable weather station on Raspberry Pi, use the Bosch BME280 sensor over I2C paired with a Raspberry Pi 4 Model B (4GB) or Pi 5. The BME280 provides temperature, humidity, and barometric pressure in a single package, avoiding the self-heating errors and high failure rates common in cheaper DHT11/DHT22 modules. Furthermore, the Pi 4 and 5 offer native hardware I2C controllers that handle the BME280's strict timing requirements far better than bit-banged GPIO implementations on older boards.

Decision Path: Choosing Your Sensor and Board

Before buying parts, map your environmental requirements to the correct silicon. Many builders mistakenly buy a BMP280 thinking it includes humidity, or attempt to run long I2C cable runs without bus extenders. Use this decision matrix to finalize your hardware pick.

If your requirement is... Then choose this sensor... Why / Trade-offs
Budget under $5, basic indoor temp/humidity DHT22 (AM2302) High failure rate, slow 2s polling, requires precise GPIO timing.
Temp, humidity, pressure, high accuracy Bosch BME280 Industry standard. Low self-heating. I2C/SPI native. Best overall value.
Above + Air Quality (VOC/eCO2) Bosch BME688 Requires complex BSEC library compilation on Pi. Overkill for basic weather.
Sensor must be >2 meters from the Pi BME280 + LTC4311 Extender I2C capacitance limits standard runs to ~1 meter. Extenders solve this.
Concrete Pick: For a standard, highly reliable outdoor or indoor weather station, terminate your decision here: Buy the Adafruit BME280 Breakout (Product ID 2652) and run it on a Raspberry Pi 4 Model B (4GB). The Adafruit variant includes onboard 10kΩ pull-up resistors and a 3.3V LDO, eliminating the two most common I2C failure points found on $3 generic clone boards.

Parts List and Spec Sheet

The following bill of materials assumes you are building a headless, always-on node running Raspberry Pi OS (Bookworm or later, 64-bit). Prices reflect 2026 market averages for genuine components.

Component Exact Variant / Model Est. Price Technical Notes
Microcontroller Raspberry Pi 4 Model B (4GB RAM) $55.00 Pi 5 is also compatible, but Pi 4 runs cooler, reducing ambient thermal bleed to the sensor.
Sensor Adafruit BME280 (PID 2652) $19.50 Verify chip laser etching says 'BME', not 'BMP'. Default I2C address: 0x77.
Power Supply Official Pi 27W USB-C PSU (5.1V / 5A) $12.00 Prevents brownout warnings when WiFi and I2C bus spike simultaneously.
Wiring 22 AWG Solid Core Hookup Wire $8.00 Keep I2C runs under 1 meter to stay under the 400pF bus capacitance limit.
Enclosure Stevenson Screen (Solar Shield) $25.00 Mandatory for outdoor use to block solar radiation and rain while allowing airflow.

Pin Mapping and Physical Wiring

The Raspberry Pi uses Broadcom (BCM) pin numbering for its GPIO header. The BME280 communicates via I2C, requiring only four physical connections. Ensure the Pi is completely powered down and unplugged before making these connections.

Raspberry Pi Pin (BCM / Physical) BME280 Breakout Pin Function
3.3V Power (Pin 1) VIN (or 3Vo) Power input. Do NOT use 5V; the BME280 is strictly a 3.3V device. 5V will destroy the silicon.
Ground (Pin 6) GND Common ground reference.
GPIO 2 / SDA (Pin 3) SDI (or SDA) I2C Data line.
GPIO 3 / SCL (Pin 5) SCK (or SCL) I2C Clock line.

Wiring Steps:

  1. Connect the 3.3V pin on the Pi to the VIN pin on the BME280.
  2. Connect Pi GND to BME280 GND.
  3. Connect Pi SDA (BCM 2) to BME280 SDI.
  4. Connect Pi SCL (BCM 3) to BME280 SCK.
  5. Boot the Pi, open a terminal, and run sudo raspi-config.
  6. Navigate to Interface Options > I2C and select Yes to enable the ARM I2C interface.
  7. Reboot the Pi and verify the sensor is visible by running sudo i2cdetect -y 1. You should see 77 in the grid output.
Thermal Isolation Warning: The Raspberry Pi's CPU generates significant heat. If you mount the BME280 directly on a breadboard adjacent to the Pi, your temperature readings will be 2°C to 4°C higher than ambient. Mount the sensor at least 15cm away from the Pi board, or use a ribbon cable to place it in a separate ventilated enclosure.

Python Code with I2C Error Handling

This script targets the Raspberry Pi 4 Model B and Pi 5 running a 64-bit OS. We use the adafruit-circuitpython-bme280 library, which handles the complex Bosch compensation algorithms internally. Install the dependencies via pip in your virtual environment: pip3 install adafruit-circuitpython-bme280.

The code below includes explicit oversampling configurations to reduce noise, and robust try/except blocks to catch the specific I2C errors that plague weather station builds.

import time
import board
import busio
import adafruit_bme280
import sys

# Target: Raspberry Pi 4 Model B / Pi 5 (BCM Pin Mapping)
# SDA = BCM 2 (Physical Pin 3)
# SCL = BCM 3 (Physical Pin 5)

def main():
    try:
        # Initialize I2C bus explicitly defining the hardware pins
        i2c = busio.I2C(board.SCL, board.SDA)
        
        # Attempt to connect to the BME280 at default I2C address 0x77
        # Note: Generic clone boards often default to 0x76. 
        # If you get a ValueError, change address=0x76 below.
        bme280 = adafruit_bme280.I2C(i2c, address=0x77)
        
        # Configure oversampling for outdoor weather station accuracy
        # 16x oversampling reduces RMS noise at the cost of slightly higher power draw
        bme280.temperature_oversampling = 16
        bme280.humidity_oversampling = 16
        bme280.pressure_oversampling = 16
        
        # Enable the IIR filter to smooth out sudden pressure spikes (e.g., wind gusts slamming a door)
        bme280.filter = 16 
        
        print('BME280 initialized successfully. Reading sensors...')
        
        while True:
            temp_c = bme280.temperature
            humidity = bme280.humidity
            pressure = bme280.pressure
            
            print(f'Temp: {temp_c:.1f} C | Humidity: {humidity:.1f} % | Pressure: {pressure:.2f} hPa')
            
            # Sleep for 10 seconds. BME280 draws ~3.6uA in standby, so frequent polling is fine,
            # but 10s is standard for weather logging to avoid self-heating from continuous measurement.
            time.sleep(10)
            
    except ValueError as e:
        # Catches wrong I2C address (e.g., sensor is at 0x76 instead of 0x77)
        print(f'DEVICE ADDRESS ERROR: {e}')
        print('Fix: Run i2cdetect -y 1. If you see 76, change address=0x76 in this script.')
        sys.exit(1)
        
    except OSError as e:
        # Catches I2C bus failures, disconnected wires, or missing pull-ups
        print(f'I2C BUS ERROR: {e}')
        print('Fix: Check wiring, ensure I2C is enabled in raspi-config, and verify pull-up resistors.')
        sys.exit(1)
        
    except KeyboardInterrupt:
        print('\nExiting weather station script gracefully.')
        sys.exit(0)

if __name__ == '__main__':
    main()

Debugging: "OSError: [Errno 121] Remote I/O error"

When working with I2C sensors on the Raspberry Pi, you will inevitably encounter the dreaded OSError: [Errno 121] Remote I/O error (or sometimes [Errno 110] Connection timed out). This error means the Pi's I2C controller sent a clock signal, but the sensor failed to acknowledge (NACK) or pulled the SDA line low and held it there.

The First 3 Things to Check When It Fails:

  1. Run i2cdetect -y 1: If the grid is entirely empty, your wiring is wrong or I2C is disabled. If it shows UU, another driver has claimed the device. If it shows 76 instead of 77, your code's address parameter is wrong.
  2. Verify Pull-Up Resistors: I2C is an open-drain protocol. It requires resistors to pull the SDA and SCL lines up to 3.3V. The Adafruit BME280 has these built-in. If you bought a $2 generic clone, it likely lacks them. Measure the resistance between SDA and 3.3V with a multimeter; it should read ~4.7kΩ to 10kΩ. If it reads infinite (OL), solder 4.7kΩ resistors to the lines.
  3. Check Cable Length and Capacitance: The I2C specification limits bus capacitance to 400pF. Standard 22 AWG wire adds roughly 2-3pF per inch. If your sensor is mounted outside in a Stevenson screen and the cable run exceeds 1 meter, the signal edges will degrade, causing Errno 121. Use a dedicated I2C bus extender IC (like the LTC4311) for long runs.
Ranked Cause Symptom Exact Fix
1. Missing Pull-up Resistors Intermittent Errno 121, works for 5 mins then crashes. Add 4.7kΩ resistors from SDA to 3.3V and SCL to 3.3V.
2. Address Mismatch (0x76 vs 0x77) Immediate ValueError or Errno 121 on first read. Change address=0x76 in the Python initialization.
3. Wire Seating / Breadboard Contact Works when touched, fails when left alone. Solder header pins. Breadboard contacts oxidize and fail outdoors.
4. Bus Capacitance Overload Consistent Errno 121 on long cable runs (>1m). Shorten wires or add an LTC4311 I2C bus extender module.

For deeper hardware-level debugging of I2C bus states, refer to the official Raspberry Pi Hardware Configuration Documentation, which details how to adjust the I2C baud rate via config.txt if you are driving long capacitive lines.

Extending or Simplifying the Build

Once your baseline BME280 node is logging data reliably, you will likely want to adapt the footprint or integrate it into a broader smart home ecosystem.

How to Simplify (Lower Power / Smaller Footprint):
If this weather station is running on a battery or solar setup in a remote part of your yard, the Raspberry Pi 4 is overkill. Swap the compute module for a Raspberry Pi Zero 2 W ($15). Flash Raspberry Pi OS Lite (64-bit, headless) to eliminate the desktop environment's RAM and CPU overhead. Add a crontab entry to wake the Pi, run the Python script, append the data to a CSV, and execute sudo halt to shut down completely between readings. This drops average power consumption from ~2.5W to under 0.8W during active cycles.

How to Extend (Smart Home Integration):
To push data to Home Assistant or a cloud dashboard, integrate the Paho MQTT library. Instead of printing to the console, format the readings as a JSON payload and publish to an MQTT broker. For advanced wind and rain tracking, you cannot use I2C sensors alone. Extend the build by wiring an MCP3008 Analog-to-Digital Converter to the Pi's SPI bus. This allows you to read analog voltages from a traditional tipping-bucket rain gauge and a reed-switch anemometer, bridging the gap between digital silicon and mechanical meteorology.

For comprehensive wiring diagrams and integration examples for the MCP3008 ADC, the Adafruit Analog-to-Digital Converters Guide provides excellent reference schematics that pair perfectly with the BME280 setup outlined above.

Final Recommendation: Do not compromise on the sensor breakout board. The $16 price difference between a generic clone and the Adafruit BME280 (PID 2652) saves you hours of debugging I2C pull-up issues and compensating for BMP280 humidity dropouts. Build it right the first time with hardware I2C, proper thermal isolation, and robust Python error handling, and your weather station will run unattended for years.