If you are building a weather station, the direct answer for 95% of makers is the Bosch BME280 (specifically the Adafruit I2C/SPI Breakout, Product ID 2652). It costs around $11, communicates over a hardware I2C bus, and measures temperature, humidity, and barometric pressure. While the DHT22 is cheaper, its 1-Wire-style timing protocol frequently fails on the Raspberry Pi because Linux is not a real-time operating system; kernel interrupts routinely cause missed microsecond pulses, resulting in dropped readings.
This guide cuts through the guesswork. We will run a decision matrix to prove why the BME280 wins, wire it to a Raspberry Pi 4 Model B, write robust Python code with exact error handling, and cover the specific I2C debugging steps you need when the bus locks up.
The Verdict: Which Weather Sensor Should You Buy?
Use this decision tree to select the right sensor for your specific environment and data requirements.
| If your project needs... | Then choose this sensor | Why it wins (or fails) |
|---|---|---|
| Basic indoor temp/humidity on a strict <$5 budget | DHT22 (AM2302) | Cheap, but relies on bit-banged timing. High failure rate on Pi due to OS thread jitter. |
| Reliable temp, humidity, AND barometric pressure | BME280 (Concrete Pick) | Hardware I2C handles timing in the background. Includes pressure for altitude/weather forecasting. |
| Indoor air quality (VOCs) alongside weather | BME680 | Adds a gas sensor, but requires complex burn-in calibration and costs ~$16. |
| High-precision outdoor temp/humidity only | AHT20 | Excellent I2C stability and accuracy, but lacks the pressure sensor of the BME280. |
Hardware Spec Sheet & Parts List
This build targets the Raspberry Pi 4 Model B (4GB RAM). The code and wiring are 100% compatible with the Pi 3B+ and Pi 5, as the 40-pin GPIO I2C layout remains unchanged across these generations.
- Microcontroller: Raspberry Pi 4 Model B (4GB) — ~$55
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID 2652) — ~$11. (Includes onboard 10kΩ pull-up resistors and a 3.3V voltage regulator).
- Wiring: 4x Female-to-Female jumper wires (or a STEMMA QT / Qwiic cable if using a compatible Pi shim).
- Software Stack: Raspberry Pi OS (Bookworm or newer), Python 3.9+,
adafruit-circuitpython-bme280library.
Wiring the BME280 to Raspberry Pi 4B
The BME280 supports both SPI and I2C. We are using I2C because it only requires two data pins and allows you to daisy-chain other sensors later. The Adafruit breakout defaults to I2C address 0x77.
| Raspberry Pi 4B Pin (Physical) | GPIO / Function | BME280 Breakout Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 1 | 3.3V Power | VIN (or 3Vo) | Red |
| Pin 6 | Ground | GND | Black |
| Pin 3 | GPIO 2 (SDA.1) | SDI (SDA) | Blue |
| Pin 5 | GPIO 3 (SCL.1) | SCK (SCL) | Yellow |
Crucial Safety Note: Never connect the BME280 VIN pin to the Pi's 5V pins (Pin 2 or 4) unless your specific breakout board explicitly states it has a 5V-tolerant voltage regulator. The Pi's I2C pins (SDA/SCL) are strictly 3.3V tolerant. Feeding 5V back into the SDA line will permanently destroy the BCM2711 SoC.
Python Build: Polling I2C Weather Data
Before running the code, enable the I2C interface on your Pi by running sudo raspi-config, navigating to Interface Options -> I2C -> Enable, and rebooting. Next, install the required CircuitPython library:
pip3 install adafruit-circuitpython-bme280
The following script initializes the I2C bus, handles the specific hardware exceptions that occur when a sensor is missing, and polls the data every 5 seconds.
import time
import board
import busio
import adafruit_bme280
# Define the I2C bus using the Pi's hardware SCL and SDA pins
i2c = busio.I2C(board.SCL, board.SDA)
# Attempt to initialize the sensor with error handling
def init_sensor():
try:
# Adafruit breakouts default to 0x77. Generic clones often use 0x76.
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
print("BME280 initialized successfully at 0x77.")
return sensor
except ValueError as e:
# Catches chip ID mismatch or incorrect address
print(f"[FATAL] Sensor not found or wrong address. Error: {e}")
print("Action: Run 'i2cdetect -y 1' to find the correct hex address.")
exit(1)
except RuntimeError as e:
# Catches I2C bus lockups or OS-level permission denied errors
print(f"[FATAL] I2C Bus Error. Error: {e}")
print("Action: Ensure I2C is enabled in raspi-config and you have i2c permissions.")
exit(1)
sensor = init_sensor()
# Configure the sensor for indoor weather station use
# Higher oversampling reduces noise but increases read time
sensor.oversampling_humidity = adafruit_bme280.OVERSAMPLING_X2
sensor.oversampling_temperature = adafruit_bme280.OVERSAMPLING_X2
sensor.oversampling_pressure = adafruit_bme280.OVERSAMPLING_X2
sensor.mode = adafruit_bme280.MODE_NORMAL
sensor.standby_period = adafruit_bme280.STANDBY_TC_500
print("Polling weather data... (Press Ctrl+C to stop)")
try:
while True:
temp_c = sensor.temperature
humidity = sensor.relative_humidity
pressure_hpa = sensor.pressure
# Convert to Fahrenheit and inHg for US users
temp_f = (temp_c * 9/5) + 32
pressure_inhg = pressure_hpa * 0.02953
print(f"Temp: {temp_c:.1f}°C ({temp_f:.1f}°F) | "
f"Humidity: {humidity:.1f}% | "
f"Pressure: {pressure_hpa:.1f} hPa ({pressure_inhg:.2f} inHg)")
time.sleep(5)
except KeyboardInterrupt:
print("\nPolling stopped by user.")
except OSError as e:
print(f"\n[ERROR] I2C Bus dropped during read: {e}")
print("Action: Check for loose jumper wires or excessive cable capacitance.")
Debugging: Exact Errors & The First 3 Checks
When working with I2C on the Raspberry Pi, things will eventually go wrong. If your script crashes, do not guess. Follow this exact diagnostic path.
The First 3 Things to Check When It Fails
- Verify I2C is enabled and detected: Run
sudo i2cdetect -y 1in the terminal. You should see a grid with77(or76) highlighted. If the grid is entirely empty, your wiring is wrong or I2C is disabled in the OS. - Check for 5V on the I2C line: Use a multimeter to measure the voltage between the Pi's GND (Pin 6) and the SDA wire. It must read ~3.3V. If it reads 5V, you have a short or a faulty breakout board that is back-feeding voltage.
- Verify User Permissions: If running without
sudo, ensure your user is in thei2cgroup by runningsudo usermod -aG i2c $USERand rebooting.
Ranked Causes for Exact Error Strings
| Exact Error String | Rank | Root Cause & Fix |
|---|---|---|
ValueError: No I2C device at address: 0x77 |
1st | Cause: You are using a generic clone BME280 that defaults to 0x76. Fix: Change address=0x77 to address=0x76 in the Python script. |
OSError: [Errno 121] Remote I/O error |
2nd | Cause: I2C bus lockup due to parasitic capacitance on long wires, or a loose SDA connection. Fix: Shorten wires to <30cm, or add external 4.7kΩ pull-up resistors to 3.3V. |
ValueError: Bad chip ID... expected 0x60 |
3rd | Cause: You bought a BMP280 (temp/pressure only) mislabeled as a BME280 on Amazon/AliExpress. The BMP280 chip ID is 0x58. Fix: Use the adafruit-circuitpython-bmp280 library instead. |
Extending the Build: Wind, Rain, and Lightning
The BME280 handles the ambient air perfectly, but a complete outdoor weather station needs mechanical sensors. Here is how you extend the system without overloading the Pi's GPIO.
- To Simplify (Indoor Only): If you only care about indoor climate control, stop here. Mount the BME280 inside a slotted 3D-printed Stevenson screen to prevent direct sunlight from skewing the temperature readings, and pipe the data to Home Assistant via MQTT.
- To Extend (Wind & Rain): Do not connect mechanical anemometers (wind speed) and rain gauges (tipping buckets) directly to the Pi's GPIO. They use reed switches that cause contact bounce, and running long wires to mechanical switches acts as an antenna for ESD (Electrostatic Discharge), which will fry the Pi. Instead, use an Arduino Nano or ESP32 as a dedicated node to debounce the reed switches and count interrupts, then send the aggregated data to the Pi via UART or ESP-NOW.
- To Extend (Lightning): Add the SparkFun AS3935 Lightning Detector (Product ID: SEN-15441). It uses SPI or I2C. Note that if you share the I2C bus with the BME280, the AS3935's internal oscillator can introduce noise. Keep the lightning detector at least 5cm away from the BME280 on your breadboard to prevent barometric pressure jitter.
By anchoring your build on the BME280's hardware I2C protocol, you eliminate the timing nightmares of cheaper sensors and build a foundation stable enough to support a full-scale meteorological array.






