Skip to main content
Raspberry Pi HATs

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

RefPartNotes
U1BME280 3.3 V I2C breakout/moduleTemperature, humidity, and pressure sensor; use a module with VCC, GND, SCL, and SDA pins
R1, R24.7 kΩ resistorsPull SDA and SCL up to 3.3 V
C1100 nF capacitorHigh-frequency local decoupling near U1
C21 µF capacitorBulk decoupling for the sensor/header area
J11x4 2.54 mm headerOptional OLED display: VCC, GND, SCL, SDA
J21x4 2.54 mm headerExternal 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.

Schematic Circuit Preview

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

  1. Power the Pi with the HAT attached and measure 3.3 V on U1 before installing an OLED.
  2. Run i2cdetect -y 1 and verify that only the expected addresses appear.
  3. Touch the BME280 gently and confirm temperature rises, then breathe near the board and confirm humidity rises.
  4. If an OLED is installed, confirm it uses a different I2C address, commonly 0x3c.
  5. Compare pressure readings against a local weather station and set sea_level_pressure for 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.