If you are building a raspberry pi weather sensor, skip the DHT11 and DHT22. Those legacy capacitive sensors drift over time, suffer from self-heating errors, and rely on fragile bit-banged 1-Wire timing that breaks under heavy CPU loads. The professional hobbyist standard is the Bosch BME280. It measures temperature, humidity, and barometric pressure over a hardware I2C bus, freeing up your Pi’s CPU while delivering lab-grade stability.
This guide walks through wiring the BME280 to a Raspberry Pi 4 Model B (fully compatible with the Pi 5), writing robust Python code with proper error handling, and debugging the inevitable I2C bus failures.
Time to Complete: 30 minutes
Target Board: Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS Bookworm (64-bit)
Parts List & Sensor Specifications
Before ordering parts, you need to choose the right breakout board. Raw BME280 chips operate at 1.8V to 3.3V. If you buy ultra-cheap clone boards without a voltage regulator or logic level shifters, connecting them to a 5V line will instantly fry the silicon. We recommend the Adafruit STEMMA QT version for its built-in 3.3V LDO and pull-up resistors.
- Microcontroller: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (~$55 - $60)
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652) (~$15.00)
- Wiring: STEMMA QT to Male Header Cable (Product ID: 4204) or 4x female-to-female Dupont jumpers (~$2.50)
- Power Supply: Official 27W USB-C Power Supply (for Pi 4) or 27W PD supply (for Pi 5)
Sensor Comparison Matrix
Here is why the BME280 wins for indoor/outdoor weather stations compared to other common environmental sensors on the market in 2026.
| Sensor Model | Variables Measured | Temp Accuracy (±) | Interface & Address | Self-Heating Error | Street Price |
|---|---|---|---|---|---|
| Bosch BME280 | Temp, RH, Pressure | 1.0°C | I2C (0x77 / 0x76) | ~0.2°C (Low) | $15.00 |
| Bosch BME680 | Temp, RH, Press, VOC | 1.0°C | I2C (0x77 / 0x76) | ~1.5°C (High due to gas heater) | $20.00 |
| Aosong DHT22 | Temp, RH | 0.5°C | 1-Wire (Bitbang) | ~0.5°C (Moderate) | $10.00 |
| ASAIR AHT20 | Temp, RH | 0.3°C | I2C (0x38) | ~0.1°C (Very Low) | $6.00 |
Note: The BME680 includes a gas sensor for VOC (Volatile Organic Compounds) detection, but the internal heater required to read the gas element causes localized self-heating, which skews the ambient temperature reading unless heavily compensated in software. For pure weather tracking, the BME280 is superior.
Hardware Wiring & Pin Mapping
The Raspberry Pi’s 40-pin GPIO header includes a dedicated hardware I2C bus (I2C1) on pins 3 and 5. We will use this bus. Never connect the BME280 VCC line to the Pi's 5V pins (Pin 2 or 4) unless your specific breakout board explicitly states it has a 5V-to-3.3V voltage regulator. Feeding 5V directly to a raw BME280 chip will destroy it.
Pin Mapping Table
| Raspberry Pi 40-Pin Header | GPIO / Function | BME280 Breakout Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 1 | 3.3V Power | VIN / VCC | Red |
| Pin 3 | GPIO 2 (SDA1) | SDA | Blue |
| Pin 5 | GPIO 3 (SCL1) | SCL / SCK | Yellow |
| Pin 6 | Ground | GND | Black |
Physical Assembly Steps
- Power down completely: Unplug the Raspberry Pi from the wall. Never hot-plug I2C sensors; voltage spikes on the SDA/SCL lines can latch up the Pi's I2C controller, requiring a full reboot.
- Connect Power and Ground: Attach the Red wire to Pin 1 (3.3V) and the Black wire to Pin 6 (GND).
- Connect Data Lines: Attach Blue to Pin 3 (SDA) and Yellow to Pin 5 (SCL). If using a STEMMA QT cable, the color mapping is Red=VIN, Black=GND, Blue=SDA, Yellow=SCL.
- Verify I2C is enabled: Boot the Pi, open a terminal, and run
sudo raspi-config. Navigate to Interface Options > I2C and ensure it is enabled. Reboot if you changed this setting. - Scan the bus: Run
sudo i2cdetect -y 1. You should see77(or76) in the grid output. This confirms hardware communication before writing any Python.
Software Setup & Python Implementation
We will use Adafruit’s CircuitPython libraries running on standard Raspberry Pi OS via the Blinka compatibility layer. This approach provides robust, maintained drivers rather than relying on abandoned GitHub repositories.
First, create an isolated virtual environment (best practice for Python on Bookworm OS) and install the dependencies:
mkdir ~/weather_sensor && cd ~/weather_sensor
python3 -m venv venv
source venv/bin/activate
pip3 install adafruit-circuitpython-bme280
Complete Python Read Script
The following code targets the Raspberry Pi 4 Model B (and Pi 5). It initializes the I2C bus, defines the physical pin mapping in comments for clarity, and includes try/except blocks to handle the two most common I2C bus errors: device disconnects and bus lockups.
import time
import board
import busio
import adafruit_bme280
# ---------------------------------------------------------
# PIN DEFINITIONS (Raspberry Pi 40-Pin Header Mapping)
# ---------------------------------------------------------
# Physical Pin 3 -> GPIO 2 (SDA) -> board.SDA
# Physical Pin 5 -> GPIO 3 (SCL) -> board.SCL
# Physical Pin 1 -> 3.3V Power
# Physical Pin 6 -> Ground
# ---------------------------------------------------------
def main():
# Initialize the hardware I2C bus (bus 1 on Raspberry Pi)
i2c = busio.I2C(board.SCL, board.SDA)
try:
# The default I2C address for Adafruit BME280 is 0x77.
# Some generic clones use 0x76. Change address=0x76 if i2cdetect shows 76.
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
# Optional: Configure sensor for indoor weather monitoring
sensor.mode = adafruit_bme280.MODE_NORMAL
sensor.oversampling_temperature = 2 # 2x oversampling for stability
sensor.oversampling_pressure = 2
sensor.oversampling_humidity = 2
sensor.iir_filter_coefficient = 4 # Smooth out sudden spikes
print("BME280 initialized successfully. Reading data...")
while True:
try:
temp_c = sensor.temperature
humidity = sensor.relative_humidity
pressure_hpa = sensor.pressure
# Calculate altitude based on standard sea-level pressure (1013.25 hPa)
altitude_m = sensor.altitude
print(f"Temp: {temp_c:.2f} °C | Humidity: {humidity:.2f} % | "
f"Pressure: {pressure_hpa:.2f} hPa | Alt: {altitude_m:.1f} m")
# Sleep for 10 seconds. The sensor needs time between reads to avoid self-heating.
time.sleep(10)
except OSError as e:
# Catches [Errno 121] Remote I/O error if a wire wiggles loose during a read
print(f"[WARNING] I2C Read Error: {e}. Retrying in 5 seconds...")
time.sleep(5)
except ValueError as e:
# Catches the library error if the sensor is not found on the bus at boot
print(f"[CRITICAL] Initialization Failed: {e}")
print("Check your wiring, ensure I2C is enabled, and verify the address.")
except Exception as e:
print(f"[CRITICAL] Unexpected Error: {e}")
if __name__ == "__main__":
main()
Debugging I2C Communication Failures
When working with I2C on the Raspberry Pi, you will eventually encounter bus errors. The most common exact error string thrown by the Adafruit Blinka library is:
Another frequent error during the while loop is:
The First Three Things to Check
Before rewriting code or blaming the sensor, execute these three diagnostic steps in order:
- Run
i2cdetect -y 1: If the grid is entirely empty (only dashes), your I2C interface is disabled inraspi-config, or you are missing the ground connection. If you see76instead of77, you must change theaddress=0x76parameter in the Python script. - Verify SDA and SCL aren't swapped: I2C is not hot-swappable and is not auto-negotiating. If Pin 3 (SDA) is wired to the sensor's SCL pin, the Pi will silently fail to handshake. Swap the blue and yellow wires at the Pi header.
- Measure the VIN rail with a multimeter: Put your multimeter in DC voltage mode. Probe the sensor's VIN and GND pins. You must read between 3.2V and 3.4V. If you read 0V, your Pi's 3.3V polyfuse has tripped or your jumper wire is broken internally.
Ranked Causes for "No I2C device at address"
| Rank | Root Cause | The Fix |
|---|---|---|
| 1 | Wrong I2C Address (0x76 vs 0x77) | The BME280's CSB (Chip Select Bar) pin dictates the address. If pulled high, it's 0x77. If pulled low, it's 0x76. Update your Python address kwarg to match i2cdetect. |
| 2 | Missing I2C Pull-Up Resistors | The Pi has onboard 1.8kΩ pull-ups, but long wire runs (>30cm) degrade the signal. Add external 4.7kΩ pull-up resistors between SDA/SCL and 3.3V, or buy a breakout board that includes them. |
| 3 | High-Resistance Dupont Wires | Cheap, crimped Dupont wires often have loose metal tabs inside the plastic housing. Crimp them tighter with needle-nose pliers or switch to soldered connections / STEMMA QT cables. |
Pro-Tip for Remote I/O Errors: If your script runs fine for 3 hours and then throws OSError: [Errno 121], you are likely experiencing I2C bus capacitance issues or electromagnetic interference (EMI) from a nearby AC mains cable. Keep I2C runs under 1 meter and route them away from 120V/240V AC lines.
Extending and Simplifying the Build
Once your raspberry pi weather sensor is reliably printing to the console, you need to decide how to handle the data long-term. Here are two distinct paths depending on your project goals.
Path A: Simplify (Cron-Logged CSV)
If you don't need real-time dashboards and just want a historical log of your basement humidity, strip the while True loop out of the Python script. Let the script read the sensor once, append a row to a CSV file, and exit. Then, use Linux cron to trigger the script every 5 minutes.
# Open crontab
crontab -e
# Add this line to run every 5 minutes, logging to a CSV
*/5 * * * * /home/pi/weather_sensor/venv/bin/python /home/weather_sensor/read_once.py >> /home/pi/weather_data.csv 2>&1
This approach uses virtually zero RAM and prevents the Python garbage collector from causing memory leaks over multi-month uptime periods.
Path B: Extend (MQTT to Home Assistant)
If you want to integrate this sensor into a smart home ecosystem, push the data via MQTT. Install the paho-mqtt library (pip3 install paho-mqtt) and format your payload as a JSON string.
import json
import paho.mqtt.client as mqtt
client = mqtt.Client("PiWeatherNode")
client.connect("192.168.1.50", 1883, 60) # Replace with your MQTT broker IP
payload = {
"temperature": round(temp_c, 2),
"humidity": round(humidity, 2),
"pressure": round(pressure_hpa, 2)
}
client.publish("homeassistant/sensor/weather_basement/state", json.dumps(payload))
Home Assistant will automatically discover this payload if you configure the MQTT integration, allowing you to build automations like triggering a dehumidifier when the humidity key exceeds 60%.
For deeper technical specifications on barometric pressure compensation and I2C timing diagrams, refer to the Bosch BME280 Datasheet. For official Raspberry Pi I2C bus configuration and device tree overlays, consult the Raspberry Pi Configuration Documentation. If you are using the Adafruit STEMMA QT ecosystem, their BME280 Learning Guide provides excellent Fritzing diagrams and alternative Arduino/CircuitPython codebases.






