Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

BMP280 with Raspberry Pi Pico W — MicroPython pressure and altitude

Read temperature, pressure and altitude from a BMP280 on Pico W I2C0 (GP4/GP5) in MicroPython with a short driver built into the script.

MicroPythonVerified — micropython (mpy-cross v1.29.0), 3 Nov 2026Compile-only

Community draft — report a mistake

The BMP280 measures air pressure precisely enough to estimate altitude — it is the BME280 without humidity, and cheaper. On a Pico W you can read it with a short driver that lives in main.py itself.

Wire it to 3V3 and I2C0 (GP4/GP5) with SDO to GND for address 0x76. The script prints temperature, pressure in hPa and an altitude estimate every two seconds.

Wiring

Raspberry Pi Pico WOnly the pins used are shown · 3.3 V logicBMP280 breakout (GY-BMP280)GNDGP4GP53V3VCCGNDSCLSDACSB (not connected)SDO
Every connection
Raspberry Pi Pico WBMP280 breakout (GY-BMP280)WireNotes
3V3VCCred3V3 is pin 36. The purple 6-pin board has no regulator — 3.3 V only.
GNDGNDblack
GP5SCLyellowGP5 is physical pin 7 (I2C0 SCL).
GP4SDAblueGP4 is physical pin 6 (I2C0 SDA).
GNDSDOblackSDO to GND selects address 0x76. Leave CSB unconnected (pulled up = I2C mode).

3.3 V (boards with a regulator: 3.3–5 V) part, 3.3 V board. The common purple 6-pin GY-BMP280-3.3 board has no regulator and no level shifting: power it from 3.3 V only. On a 5 V Uno, use a board with a regulator or a level shifter on SDA/SCL.

Board quirks

  • The driver is about 35 lines inside main.py — Bosch’s floating-point compensation formulas from the datasheet, so there is nothing extra to copy to the board.
  • Chip ID 0x58 is a BMP280; 0x60 is a BME280 (it also has humidity — use the BME280 guide for that).
  • Altitude is estimated from 1013.25 hPa at sea level. Weather moves that by ±10 hPa (±80 m), so for real altitude enter your city’s current QNH from a METAR report.
  • The IIR filter and ×16 pressure oversampling make readings steady enough to see a floor change in a building.

Code

main.pyMicroPython
# BMP280 temperature + pressure on a Raspberry Pi Pico W (MicroPython).
# Wiring (purple 6-pin GY-BMP280-3.3): VCC -> 3V3 (pin 36), GND -> GND, SCL -> GP5 (pin 7),
# SDA -> GP4 (pin 6), SDO -> GND (address 0x76), CSB unconnected (I2C mode).
# Minimal driver written for FlashReady (MIT). Compensation = Bosch BMP280 datasheet float formulas.
from machine import Pin, I2C
import struct
import time

SEA_LEVEL_HPA = 1013.25  # for the altitude estimate; use your city's current QNH for accuracy


class BMP280:
    def __init__(self, i2c, addr):
        self.i2c, self.addr = i2c, addr
        chip = i2c.readfrom_mem(addr, 0xD0, 1)[0]
        if chip not in (0x56, 0x57, 0x58, 0x60):
            raise OSError("chip ID 0x%02X - not a BMP280" % chip)
        self.chip = chip
        (self.T1, self.T2, self.T3, self.P1, self.P2, self.P3, self.P4,
         self.P5, self.P6, self.P7, self.P8, self.P9) = struct.unpack("<HhhHhhhhhhhh", i2c.readfrom_mem(addr, 0x88, 24))
        i2c.writeto_mem(addr, 0xF5, b"\x10")  # IIR filter x16 smooths pressure

    def read(self):
        """One forced measurement -> (temperature C, pressure hPa)."""
        self.i2c.writeto_mem(self.addr, 0xF4, bytes([(2 << 5) | (5 << 2) | 1]))  # T x2, P x16, forced
        time.sleep_ms(45)
        while self.i2c.readfrom_mem(self.addr, 0xF3, 1)[0] & 0x08:
            time.sleep_ms(2)
        d = self.i2c.readfrom_mem(self.addr, 0xF7, 6)
        adc_p = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
        adc_t = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4)
        v1 = (adc_t / 16384.0 - self.T1 / 1024.0) * self.T2
        v2 = (adc_t / 131072.0 - self.T1 / 8192.0) ** 2 * self.T3
        t_fine = v1 + v2
        v1 = t_fine / 2.0 - 64000.0
        v2 = v1 * v1 * self.P6 / 32768.0 + v1 * self.P5 * 2.0
        v2 = v2 / 4.0 + self.P4 * 65536.0
        v1 = (self.P3 * v1 * v1 / 524288.0 + self.P2 * v1) / 524288.0
        v1 = (1.0 + v1 / 32768.0) * self.P1
        if v1 == 0:
            return t_fine / 5120.0, 0.0
        p = (1048576.0 - adc_p - v2 / 4096.0) * 6250.0 / v1
        p += (self.P9 * p * p / 2147483648.0 + p * self.P8 / 32768.0 + self.P7) / 16.0
        return t_fine / 5120.0, p / 100.0


i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=100_000)
found = i2c.scan()
addr = 0x76 if 0x76 in found else (0x77 if 0x77 in found else None)
if addr is None:
    raise SystemExit("No BMP280 at 0x76/0x77 - found {}".format([hex(a) for a in found]))
bmp = BMP280(i2c, addr)
print("BMP280 at {} (chip ID 0x{:02X}{})".format(hex(addr), bmp.chip, " = BME280, humidity ignored" if bmp.chip == 0x60 else ""))

while True:
    t, p = bmp.read()
    alt = 44330.0 * (1.0 - (p / SEA_LEVEL_HPA) ** 0.1903)
    print("Temp {:.2f} C  Pressure {:.2f} hPa  Altitude ~{:.0f} m".format(t, p, alt))
    time.sleep(2)

Expected serial output

REPL / Thonny shellOutput
BMP280 at 0x76 (chip ID 0x58)
Temp 28.61 C  Pressure 1006.42 hPa  Altitude ~58 m
Temp 28.62 C  Pressure 1006.40 hPa  Altitude ~58 m

Common errors

OSError: chip ID 0x.. - not a BMP280

Something else answered at 0x76/0x77 — another sensor on the same bus, or a mislabelled module. Unplug the others and check the chip marking.

No BMP280 at 0x76/0x77

CSB is tied LOW (SPI mode), SDA/SCL swapped, or no 3.3 V. GP4 is physical pin 6, GP5 is pin 7.

Pressure reads about 600 hPa or wildly wrong

A counterfeit or damaged sensor. Compare with a phone barometer; replace the module.

↑↓ to move · Enter to open