Building an I2C Environmental Sensor HAT
Overview
This tutorial walks through a compact Raspberry Pi HAT for logging temperature, relative humidity, and barometric pressure with a BME280 over I2C. The design uses a common 3.3 V BME280 breakout/module footprint so the tutorial stays buildable without hand-soldering the bare LGA sensor. It includes the support parts that make an I2C sensor board reliable in the field: local decoupling, SDA/SCL pull-up resistors, an optional OLED display header, and an unkeyed external 1x4 header for mounting the sensor away from the Pi.
What you will build
The HAT exposes one BME280 sensor on the Pi's primary I2C bus and provides a second 4-pin I2C header for either a small SSD1306 OLED or a remote sensor pod. The Pi supplies 3.3 V power, so all parts on the bus must be 3.3 V compatible.
Bill of materials
| Ref | Part | Notes |
|---|---|---|
| U1 | BME280 3.3 V I2C breakout/module | Temperature, humidity, and pressure sensor; use a module with VCC, GND, SCL, and SDA pins |
| R1, R2 | 4.7 kΩ resistors | Pull SDA and SCL up to 3.3 V |
| C1 | 100 nF capacitor | High-frequency local decoupling near U1 |
| C2 | 1 µF capacitor | Bulk decoupling for the sensor/header area |
| J1 | 1x4 2.54 mm header | Optional OLED display: VCC, GND, SCL, SDA |
| J2 | 1x4 2.54 mm header | External I2C sensor/probe connection |
Step 1: Start with the Raspberry Pi HAT outline
Use RaspberryPiHatBoard so the circuit has the Pi 40-pin header and the standard HAT board outline.
import { RaspberryPiHatBoard } from "@tscircuit/common"
export default () => (
<RaspberryPiHatBoard name="HAT1">
{/* Sensor circuit goes here */}
</RaspberryPiHatBoard>
)
Step 2: Add the BME280 in I2C mode
The bare BME280 is an LGA sensor with separate supply and mode pins, but this tutorial targets the common 3.3 V I2C breakout/module version. Use a module that already straps the sensor for I2C and exposes VCC, GND, SCL, and SDA. Most modules default to address 0x76; some expose an address jumper for 0x77.
Step 3: Add I2C pull-up resistors
I2C lines are open-drain, so the bus needs pull-ups. Use 4.7 kΩ as a good default for a short Pi HAT. Many BME280 breakouts and OLED modules already include onboard SDA/SCL pull-ups; if J1 or J2 connects to those modules, leave R1/R2 unpopulated as DNP or remove the duplicate module pull-ups so the effective bus resistance does not become too strong. If you add long cables or several modules, verify rise time with an oscilloscope and adjust the installed pull-up value.
<resistor name="R1" resistance="4.7k" footprint="0402" pcbX={-12} pcbY={8} />
<resistor name="R2" resistance="4.7k" footprint="0402" pcbX={-12} pcbY={3} />
<trace from=".R1 > .pin1" to=".HAT1_chip .V3_3" />
<trace from=".R1 > .pin2" to=".U1 .SDA" />
<trace from=".R2 > .pin1" to=".HAT1_chip .V3_3" />
<trace from=".R2 > .pin2" to=".U1 .SCL" />
Step 4: Add decoupling capacitors
Place a 100 nF capacitor within a few millimeters of the BME280 supply pin and add a 1 µF capacitor nearby for the optional OLED/external header load.
<capacitor name="C1" capacitance="100nF" footprint="0402" pcbX={7} pcbY={8} />
<capacitor name="C2" capacitance="1uF" footprint="0402" pcbX={7} pcbY={3} />
<trace from=".C1 > .pin1" to=".U1 .VCC" />
<trace from=".C1 > .pin2" to=".U1 .GND" />
<trace from=".C2 > .pin1" to=".U1 .VCC" />
<trace from=".C2 > .pin2" to=".U1 .GND" />
Step 5: Add optional OLED and external headers
Use the same pin order on both unkeyed headers so a cable can be shared between the optional OLED and an external sensor pod: VCC, GND, SCL, SDA. Mark pin 1 clearly on the silkscreen, and use keyed cables or a keyed connector footprint if field mis-plugging is likely.
<chip
name="J1"
manufacturerPartNumber="I2C OLED / sensor header"
footprint="pinrow4"
pinLabels={{ pin1: ["VCC"], pin2: ["GND"], pin3: ["SCL"], pin4: ["SDA"] }}
/>
<chip
name="J2"
manufacturerPartNumber="External environmental probe header"
footprint="pinrow4"
pinLabels={{ pin1: ["VCC"], pin2: ["GND"], pin3: ["SCL"], pin4: ["SDA"] }}
/>
PCB layout guidance
- Keep the BME280 away from voltage regulators, the Pi CPU, LEDs, and other warm parts so temperature readings are not biased.
- Put ventilation slots or keep-out copper near the sensor if the enclosure is sealed.
- Route SDA and SCL as short, parallel-but-not-overlapping traces with a continuous ground reference.
- Place R1/R2 near the Pi header or near the middle of the bus; place C1 directly next to U1.
- If J2 leaves the enclosure, add ESD protection and keep the cable short to avoid I2C signal integrity problems.
Raspberry Pi software setup
Enable I2C and confirm the device is visible:
sudo raspi-config nonint do_i2c 0
sudo reboot
sudo apt-get install -y i2c-tools python3-pip python3-venv
i2cdetect -y 1
You should see the BME280 at 0x76 if SDO is tied to ground, or 0x77 if SDO is tied to 3.3 V.
Python logging example
import time
import board
import busio
import adafruit_bme280.basic as adafruit_bme280
i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
bme280.sea_level_pressure = 1013.25
while True:
print(f"Temperature: {bme280.temperature:.1f} °C")
print(f"Humidity: {bme280.relative_humidity:.1f} %")
print(f"Pressure: {bme280.pressure:.1f} hPa")
print(f"Altitude estimate: {bme280.altitude:.1f} m")
time.sleep(2)
On current Raspberry Pi OS releases, install the CircuitPython library in a virtual environment instead of the system Python environment:
python3 -m venv ~/bme280-env
source ~/bme280-env/bin/activate
pip install adafruit-circuitpython-bme280
Microcontroller example: Raspberry Pi Pico / MicroPython
The same sensor module can be read from a small microcontroller. Wire VCC to 3V3, GND to GND, SDA to GP4, and SCL to GP5, then use a BME280 MicroPython driver such as bme280_float.py.
from machine import I2C, Pin
from time import sleep
import bme280_float as bme280
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=100_000)
sensor = bme280.BME280(i2c=i2c, address=0x76)
while True:
temperature, pressure, humidity = sensor.values
print("Temperature:", temperature)
print("Pressure:", pressure)
print("Humidity:", humidity)
sleep(2)
Bring-up checklist
- Power the Pi with the HAT attached and measure 3.3 V on U1 before installing an OLED.
- Run
i2cdetect -y 1and verify that only the expected addresses appear. - Touch the BME280 gently and confirm temperature rises, then breathe near the board and confirm humidity rises.
- If an OLED is installed, confirm it uses a different I2C address, commonly
0x3c. - Compare pressure readings against a local weather station and set
sea_level_pressurefor accurate altitude estimates.
Next improvements
- Add a Qwiic/Stemma QT connector for plug-in I2C accessories.
- Add a small cut-out or thermal isolation slot around the BME280 for better ambient temperature accuracy.
- Add a second address-select jumper so multiple sensor HATs can share the same bus during testing.