Raspberry Pi Pico With BMP280 – MicroPython Guide

In this article, we will learn how to interface the GY-BMP280 temperature and pressure sensor with Raspberry Pi Pico(RP2040). We will use a MicroPython library to interface with the sensor. The connection diagram and detailed instructions are discussed step-by-step in this guide. The article shows how to display BMP280 data on a Serial Monitor as well as on an OLED display.

BMP280 RASPBERRY PI PICO TUTORIAL

Overview Of BMP280 Temperature & Pressure Sensor

The BMP280 is a barometric pressure and temperature sensor that can operate in extremely low currents such as 2.7 µA at 1Hz. Hence, it is an ideal sensor for mobile applications where power saving is crucial. It is 63% smaller than its predecessor, the BMP180. It can be interfaced using SPI and I2C protocols. Here is the image of the sensor.

bmp280 pinout
BMP280 Pinout
Bottom and top view of the BMP280 sensor module

BMP280 contains a Piezo-resistive pressure sensing element. This MEMS(Micro-electromechanical systems) sensor sends data to an ASIC (Application Specific Integrated Circuit) within the module. The ASIC sends data about temperature and pressure to external devices such as microcontrollers.

ⓘ Read our in-depth article on BMP280 where you can learn more about the sensor.

Requirements For BMP280 Raspberry Pi Pico Interfacing

  • A Raspberry Pi Pico RP2040 running MicroPython.
  • BMP280 sensor breakout board module.
  • A Breadboard.
  • Connecting wires(jumper/DuPont wires).
  • USB cable(Micro-B).
  • OLED display(Optional).

Your Raspberry Pi Pico needs to be preloaded with a MicroPython UF2 file to program it in MicroPython. You can read our getting started guide for Raspberry Pi Pico where we show all the steps required to start programming Raspberry Pi Pico & Pico W.If you are using macOS, follow our guide to program Raspberry Pi Pico on macOS using Thonny IDE.

Also read: Arduino vs MicroPython vs CircuitPython: Which One Will You Choose?

How To Interface BMP280 with Raspberry Pi Pico

In this guide, we will use I2C to transmit data from BMP280 to Raspberry Pi Pico’s RP2040 microcontroller. I2C protocol uses 4 wires – 2 wires to send and synchronize data, and 2 wires for the power supply. Below is the Pinout of Raspberry Pi Pico W. We can use any of the pins marked as SDA/SCL to interface with BMP280.

raspberry pi pico w pinout

We will use pins 1 and 2 of Pi Pico to interface with BMP280. Pin 1 is the SDA pin while Pin 2 is the SCL pin. You can use any of the other I2C pins for your project. You can read our article on I2C communication in Raspberry Pi Pico to know more about this mode of communication.

raspberry pi pico I2C pinout

BMP280 & Raspberry Pi Pico Wiring

Connect the two devices as shown in the diagram below. Although the 10-kiloohm resistors can be omitted, it is better to connect them for reliability. The 10-kiloohm resistors act as pull-ups and help in data transmission.

Connection Details:

Raspberry Pi Pico PinBMP280 Pin
Pin 36(3V3OUT)VCC
Pin 1(GP0)SDA
Pin 2(GP1)SCL
Pin 3(GND)GND

BMP280 MicroPython Library

To read the digital data from the BMP280 sensor, we will use a library. The BMP280 MicroPython library has functions to read and write data to the BMP280.

After you have completed the connections as shown in the diagram, connect your Pico board to the computer. The upcoming steps are explained using Thonny IDE.

Open Thonny IDE and set the interpreter to MicroPython(Raspberry Pi Pico).

Create a new file using File>New or use the shortcut Ctrl+N.

Copy and paste the following lines of code in the new file. This code is the BMP280 library(Link to library).

from micropython import const
from ustruct import unpack as unp

# Author David Stenwall Wahlund (david at dafnet.se)

# Power Modes
BMP280_POWER_SLEEP = const(0)
BMP280_POWER_FORCED = const(1)
BMP280_POWER_NORMAL = const(3)

BMP280_SPI3W_ON = const(1)
BMP280_SPI3W_OFF = const(0)

BMP280_TEMP_OS_SKIP = const(0)
BMP280_TEMP_OS_1 = const(1)
BMP280_TEMP_OS_2 = const(2)
BMP280_TEMP_OS_4 = const(3)
BMP280_TEMP_OS_8 = const(4)
BMP280_TEMP_OS_16 = const(5)

BMP280_PRES_OS_SKIP = const(0)
BMP280_PRES_OS_1 = const(1)
BMP280_PRES_OS_2 = const(2)
BMP280_PRES_OS_4 = const(3)
BMP280_PRES_OS_8 = const(4)
BMP280_PRES_OS_16 = const(5)

# Standby settings in ms
BMP280_STANDBY_0_5 = const(0)
BMP280_STANDBY_62_5 = const(1)
BMP280_STANDBY_125 = const(2)
BMP280_STANDBY_250 = const(3)
BMP280_STANDBY_500 = const(4)
BMP280_STANDBY_1000 = const(5)
BMP280_STANDBY_2000 = const(6)
BMP280_STANDBY_4000 = const(7)

# IIR Filter setting
BMP280_IIR_FILTER_OFF = const(0)
BMP280_IIR_FILTER_2 = const(1)
BMP280_IIR_FILTER_4 = const(2)
BMP280_IIR_FILTER_8 = const(3)
BMP280_IIR_FILTER_16 = const(4)

# Oversampling setting
BMP280_OS_ULTRALOW = const(0)
BMP280_OS_LOW = const(1)
BMP280_OS_STANDARD = const(2)
BMP280_OS_HIGH = const(3)
BMP280_OS_ULTRAHIGH = const(4)

# Oversampling matrix
# (PRESS_OS, TEMP_OS, sample time in ms)
_BMP280_OS_MATRIX = [
    [BMP280_PRES_OS_1, BMP280_TEMP_OS_1, 7],
    [BMP280_PRES_OS_2, BMP280_TEMP_OS_1, 9],
    [BMP280_PRES_OS_4, BMP280_TEMP_OS_1, 14],
    [BMP280_PRES_OS_8, BMP280_TEMP_OS_1, 23],
    [BMP280_PRES_OS_16, BMP280_TEMP_OS_2, 44]
]

# Use cases
BMP280_CASE_HANDHELD_LOW = const(0)
BMP280_CASE_HANDHELD_DYN = const(1)
BMP280_CASE_WEATHER = const(2)
BMP280_CASE_FLOOR = const(3)
BMP280_CASE_DROP = const(4)
BMP280_CASE_INDOOR = const(5)

_BMP280_CASE_MATRIX = [
    [BMP280_POWER_NORMAL, BMP280_OS_ULTRAHIGH, BMP280_IIR_FILTER_4, BMP280_STANDBY_62_5],
    [BMP280_POWER_NORMAL, BMP280_OS_STANDARD, BMP280_IIR_FILTER_16, BMP280_STANDBY_0_5],
    [BMP280_POWER_FORCED, BMP280_OS_ULTRALOW, BMP280_IIR_FILTER_OFF, BMP280_STANDBY_0_5],
    [BMP280_POWER_NORMAL, BMP280_OS_STANDARD, BMP280_IIR_FILTER_4, BMP280_STANDBY_125],
    [BMP280_POWER_NORMAL, BMP280_OS_LOW, BMP280_IIR_FILTER_OFF, BMP280_STANDBY_0_5],
    [BMP280_POWER_NORMAL, BMP280_OS_ULTRAHIGH, BMP280_IIR_FILTER_16, BMP280_STANDBY_0_5]
]

_BMP280_REGISTER_ID = const(0xD0)
_BMP280_REGISTER_RESET = const(0xE0)
_BMP280_REGISTER_STATUS = const(0xF3)
_BMP280_REGISTER_CONTROL = const(0xF4)
_BMP280_REGISTER_CONFIG = const(0xF5)  # IIR filter config

_BMP280_REGISTER_DATA = const(0xF7)


class BMP280:
    def __init__(self, i2c_bus, addr=0x76, use_case=BMP280_CASE_HANDHELD_DYN):
        self._bmp_i2c = i2c_bus
        self._i2c_addr = addr

        # read calibration data
        # < little-endian
        # H unsigned short
        # h signed short
        self._T1 = unp('<H', self._read(0x88, 2))[0]
        self._T2 = unp('<h', self._read(0x8A, 2))[0]
        self._T3 = unp('<h', self._read(0x8C, 2))[0]
        self._P1 = unp('<H', self._read(0x8E, 2))[0]
        self._P2 = unp('<h', self._read(0x90, 2))[0]
        self._P3 = unp('<h', self._read(0x92, 2))[0]
        self._P4 = unp('<h', self._read(0x94, 2))[0]
        self._P5 = unp('<h', self._read(0x96, 2))[0]
        self._P6 = unp('<h', self._read(0x98, 2))[0]
        self._P7 = unp('<h', self._read(0x9A, 2))[0]
        self._P8 = unp('<h', self._read(0x9C, 2))[0]
        self._P9 = unp('<h', self._read(0x9E, 2))[0]

        # output raw
        self._t_raw = 0
        self._t_fine = 0
        self._t = 0

        self._p_raw = 0
        self._p = 0

        self.read_wait_ms = 0  # interval between forced measure and readout
        self._new_read_ms = 200  # interval between
        self._last_read_ts = 0

        if use_case is not None:
            self.use_case(use_case)

    def _read(self, addr, size=1):
        return self._bmp_i2c.readfrom_mem(self._i2c_addr, addr, size)

    def _write(self, addr, b_arr):
        if not type(b_arr) is bytearray:
            b_arr = bytearray([b_arr])
        return self._bmp_i2c.writeto_mem(self._i2c_addr, addr, b_arr)

    def _gauge(self):
        # TODO limit new reads
        # read all data at once (as by spec)
        d = self._read(_BMP280_REGISTER_DATA, 6)

        self._p_raw = (d[0] << 12) + (d[1] << 4) + (d[2] >> 4)
        self._t_raw = (d[3] << 12) + (d[4] << 4) + (d[5] >> 4)

        self._t_fine = 0
        self._t = 0
        self._p = 0

    def reset(self):
        self._write(_BMP280_REGISTER_RESET, 0xB6)

    def load_test_calibration(self):
        self._T1 = 27504
        self._T2 = 26435
        self._T3 = -1000
        self._P1 = 36477
        self._P2 = -10685
        self._P3 = 3024
        self._P4 = 2855
        self._P5 = 140
        self._P6 = -7
        self._P7 = 15500
        self._P8 = -14600
        self._P9 = 6000

    def load_test_data(self):
        self._t_raw = 519888
        self._p_raw = 415148

    def print_calibration(self):
        print("T1: {} {}".format(self._T1, type(self._T1)))
        print("T2: {} {}".format(self._T2, type(self._T2)))
        print("T3: {} {}".format(self._T3, type(self._T3)))
        print("P1: {} {}".format(self._P1, type(self._P1)))
        print("P2: {} {}".format(self._P2, type(self._P2)))
        print("P3: {} {}".format(self._P3, type(self._P3)))
        print("P4: {} {}".format(self._P4, type(self._P4)))
        print("P5: {} {}".format(self._P5, type(self._P5)))
        print("P6: {} {}".format(self._P6, type(self._P6)))
        print("P7: {} {}".format(self._P7, type(self._P7)))
        print("P8: {} {}".format(self._P8, type(self._P8)))
        print("P9: {} {}".format(self._P9, type(self._P9)))

    def _calc_t_fine(self):
        # From datasheet page 22
        self._gauge()
        if self._t_fine == 0:
            var1 = (((self._t_raw >> 3) - (self._T1 << 1)) * self._T2) >> 11
            var2 = (((((self._t_raw >> 4) - self._T1)
                      * ((self._t_raw >> 4)
                         - self._T1)) >> 12)
                    * self._T3) >> 14
            self._t_fine = var1 + var2

    @property
    def temperature(self):
        self._calc_t_fine()
        if self._t == 0:
            self._t = ((self._t_fine * 5 + 128) >> 8) / 100.
        return self._t

    @property
    def pressure(self):
        # From datasheet page 22
        self._calc_t_fine()
        if self._p == 0:
            var1 = self._t_fine - 128000
            var2 = var1 * var1 * self._P6
            var2 = var2 + ((var1 * self._P5) << 17)
            var2 = var2 + (self._P4 << 35)
            var1 = ((var1 * var1 * self._P3) >> 8) + ((var1 * self._P2) << 12)
            var1 = (((1 << 47) + var1) * self._P1) >> 33

            if var1 == 0:
                return 0

            p = 1048576 - self._p_raw
            p = int((((p << 31) - var2) * 3125) / var1)
            var1 = (self._P9 * (p >> 13) * (p >> 13)) >> 25
            var2 = (self._P8 * p) >> 19

            p = ((p + var1 + var2) >> 8) + (self._P7 << 4)
            self._p = p / 256.0
        return self._p

    def _write_bits(self, address, value, length, shift=0):
        d = self._read(address)[0]
        m = int('1' * length, 2) << shift
        d &= ~m
        d |= m & value << shift
        self._write(address, d)

    def _read_bits(self, address, length, shift=0):
        d = self._read(address)[0]
        return d >> shift & int('1' * length, 2)

    @property
    def standby(self):
        return self._read_bits(_BMP280_REGISTER_CONFIG, 3, 5)

    @standby.setter
    def standby(self, v):
        assert 0 <= v <= 7
        self._write_bits(_BMP280_REGISTER_CONFIG, v, 3, 5)

    @property
    def iir(self):
        return self._read_bits(_BMP280_REGISTER_CONFIG, 3, 2)

    @iir.setter
    def iir(self, v):
        assert 0 <= v <= 4
        self._write_bits(_BMP280_REGISTER_CONFIG, v, 3, 2)

    @property
    def spi3w(self):
        return self._read_bits(_BMP280_REGISTER_CONFIG, 1)

    @spi3w.setter
    def spi3w(self, v):
        assert v in (0, 1)
        self._write_bits(_BMP280_REGISTER_CONFIG, v, 1)

    @property
    def temp_os(self):
        return self._read_bits(_BMP280_REGISTER_CONTROL, 3, 5)

    @temp_os.setter
    def temp_os(self, v):
        assert 0 <= v <= 5
        self._write_bits(_BMP280_REGISTER_CONTROL, v, 3, 5)

    @property
    def press_os(self):
        return self._read_bits(_BMP280_REGISTER_CONTROL, 3, 2)

    @press_os.setter
    def press_os(self, v):
        assert 0 <= v <= 5
        self._write_bits(_BMP280_REGISTER_CONTROL, v, 3, 2)

    @property
    def power_mode(self):
        return self._read_bits(_BMP280_REGISTER_CONTROL, 2)

    @power_mode.setter
    def power_mode(self, v):
        assert 0 <= v <= 3
        self._write_bits(_BMP280_REGISTER_CONTROL, v, 2)

    @property
    def is_measuring(self):
        return bool(self._read_bits(_BMP280_REGISTER_STATUS, 1, 3))

    @property
    def is_updating(self):
        return bool(self._read_bits(_BMP280_REGISTER_STATUS, 1))

    @property
    def chip_id(self):
        return self._read(_BMP280_REGISTER_ID, 2)

    @property
    def in_normal_mode(self):
        return self.power_mode == BMP280_POWER_NORMAL

    def force_measure(self):
        self.power_mode = BMP280_POWER_FORCED

    def normal_measure(self):
        self.power_mode = BMP280_POWER_NORMAL

    def sleep(self):
        self.power_mode = BMP280_POWER_SLEEP

    def use_case(self, uc):
        assert 0 <= uc <= 5
        pm, oss, iir, sb = _BMP280_CASE_MATRIX[uc]
        p_os, t_os, self.read_wait_ms = _BMP280_OS_MATRIX[oss]
        self._write(_BMP280_REGISTER_CONFIG, (iir << 2) + (sb << 5))
        self._write(_BMP280_REGISTER_CONTROL, pm + (p_os << 2) + (t_os << 5))

    def oversample(self, oss):
        assert 0 <= oss <= 4
        p_os, t_os, self.read_wait_ms = _BMP280_OS_MATRIX[oss]
        self._write_bits(_BMP280_REGISTER_CONTROL, p_os + (t_os << 3), 2)Code language: Python (python)

Click File>Save as and save it to your Raspberry Pi Pico.

Thonny Save to

MicroPython Code to Read BMP280

With the library in place, let us now write a script to read data from BMP280. Copy the following BMP280 example code, and save it in your Raspberry Pi Pico with a “.py” extension file name. You can read about how to save scripts into your Pi Pico here.

# Source: Electrocredible.com, Language: MicroPython

from machine import Pin,I2C
from bmp280 import *
import time

sdaPIN=machine.Pin(0)
sclPIN=machine.Pin(1)
bus = I2C(0,sda=sdaPIN, scl=sclPIN, freq=400000)
bmp = BMP280(bus)

bmp.use_case(BMP280_CASE_INDOOR)

while True:
    pressure=bmp.pressure
    p_bar=pressure/100000
    p_mmHg=pressure/133.3224
    temperature=bmp.temperature
    print("Temperature: {} C".format(temperature))
    print("Pressure: {} Pa, {} bar, {} mmHg".format(pressure,p_bar,p_mmHg))
    time.sleep(1)Code language: Python (python)

Save the code as main.py if you want the code to execute each time RPi Pico powers up. The shell of Thonny IDE must now show the values of temperature and pressure. Here is how the output looks in Thonny IDE:

Did you know? There is an in-built temperature sensor in Raspberry Pi Pico W. To use it, read our article -> Raspberry Pi Pico Onboard Temperature Sensor Tutorial Using MicroPython.

Advertisement

BMP280 MicroPython Code Explained

We first import the necessary libraries in the first 3 lines of our code.

from machine import Pin,I2C
from bmp280 import *
import timeCode language: JavaScript (javascript)

The SDA and SCL pins are set in the next lines of code. Also, an object called bmp of the BMP280 class is created with the I2C parameters.

sdaPIN=machine.Pin(0)
sclPIN=machine.Pin(1)
bus = I2C(0,sda=sdaPIN, scl=sclPIN, freq=400000)
bmp = BMP280(bus)Code language: Python (python)

BMP280 comes with various Use Cases for the sensor to be used in different environments. Below is the table describing how Use Cases relate to other parameters.

Use CaseModeOversampling SettingPressure OversamplingTemperature Oversampling
IIR Filter Coefficient
Handheld device
Low-power
(e.g. mobile device)
NormalUltra-high
resolution
(×)16(×)2(×)4
Handheld device
dynamic
NormalStandard
resolution
(×)4(×)1(×)16
Weather
monitoring
(lowest power consumption)
ForcedUltra-low
power
(×)1(×)1(×)Off
Elevator/floor
change detection
NormalStandard
resolution
(×)4(×)1(×)4
Drop detectionNormalLow
power
(×)2(×)1(×)Off
Indoor navigation(All parameters in maximum)NormalUltra-high
resolution
(×)16(×)2(×)16
Table: BMP280 Use Cases

The BMP280 can be operated in three power modes: Sleep mode, Forced mode, and Normal mode. To learn more about the modes, oversampling, and IIR filter, you can refer to the datasheet of BMP280 that we linked earlier. Choose any one of the use cases depending on your application. Here we are using the settings for Indoor Navigation use case as it gives very ultra-high resolution readings.

bmp.use_case(BMP280_CASE_INDOOR)Code language: CSS (css)

The variable pressure holds the value of pressure returned by the bmp.pressure function. The unit of this value is in Pascal(Pa). We also convert it to bar and mmHg which are the two other units of measuring pressure. The pressure value in Pascal is divided by 100000 and 133.3224 to get the values in bar and mmHg respectively.

pressure=bmp.pressure
p_bar=pressure/100000
p_mmHg=pressure/133.3224Code language: Python (python)

The following line of code stores the temperature value (Celcius) in the variable called temperature.

temperature=bmp.temperature

We finally print the values of pressure and temperature and introduce a delay of 1 second.

print("Temperature: {} C".format(temperature))
print("Pressure: {} Pa, {} bar, {} mmHg".format(pressure,p_bar,p_mmHg))
time.sleep(1)Code language: Python (python)

Show BMP280 Data On OLED Display Using Raspberry Pi Pico

Let us now connect an OLED display to our project so that we can view the sensor readings easily. Connect Raspberry Pi Pico, OLED display, BMP280, and resistors as shown in the diagram below.

With the connections done, upload the following script to your Pi Pico:

# Source: Electrocredible.com, Language: MicroPython

from machine import Pin,I2C
from bmp280 import *
from ssd1306 import SSD1306_I2C
import time

bus = I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
bmp = BMP280(bus)

bmp.use_case(BMP280_CASE_INDOOR)

WIDTH =128                     
HEIGHT= 64
i2c=I2C(0,scl=Pin(1),sda=Pin(0),freq=200000)
oled = SSD1306_I2C(WIDTH,HEIGHT,i2c)

while True:
    pressure=bmp.pressure
    p_bar=pressure/100000
    p_mmHg=pressure/133.322
    temperature=bmp.temperature
    oled.text("Temperature:", 0, 0)
    oled.text(str(temperature), 50, 15)
    oled.text("Pressure:", 0, 30)
    oled.text(str(pressure), 40, 45)
    oled.show()
    time.sleep(1)
    oled.fill(0)
Code language: Python (python)

Save it as main.py or any other name with a “.py” file extension. The OLED display must now show the values as shown in the image below.

Code Explanation:

We set the width, and height of the OLED display and also created an oled object with I2C parameters.

WIDTH =128                     
HEIGHT= 64
i2c=I2C(0,scl=Pin(1),sda=Pin(0),freq=200000)
oled = SSD1306_I2C(WIDTH,HEIGHT,i2c)Code language: Python (python)

The oled.text() function takes 3 parameters: string, x-position & y-position. We print the temperature and pressure on different lines using this function. The str(temperature) function converts the temperature value to a string.

oled.text("Temperature:", 0, 0)
oled.text(str(temperature), 50, 15)
oled.text("Pressure:", 0, 30)
oled.text(str(pressure), 40, 45)Code language: Python (python)

The function oled.show() prints the contents on the display. After an interval of 1 second, oled.fill(0) function clears the display so that the next reading can be shown.

oled.show()
time.sleep(1)
oled.fill(0)Code language: Python (python)

To learn more about how an OLED display is interfaced with Pi Pico, follow the tutorial Raspberry Pi Pico OLED Display Interfacing Tutorial Using MicroPython.

Wrapping Up

I hope you found this tutorial on Raspberry Pi Pico BMP280 interfacing to be helpful. Like the BMP280, BME280 is a sensor that can give readings of temperature, pressure, and humidity. This 3-in-one sensor is almost the same in all aspects as the BMP280, with the added benefit of an accurate humidity sensor. You can read about how to interface it with Raspberry Pi Pico in our article Raspberry Pi Pico BME280 Interfacing Guide Using MicroPython.

DHT22 and DHT11 are two other sensors that can give us temperature and humidity. We also have detailed guides on these sensors:

Please leave your thoughts or queries in the comments below. Thank you for reading.


Posted

in

by

Comments

5 responses to “Raspberry Pi Pico With BMP280 – MicroPython Guide”

  1. Ogeid Avatar
    Ogeid

    Does not work for me.. always the same error because of the line:
    bmp = BMP280(bus)

  2. Alan McDermott-Roe Avatar
    Alan McDermott-Roe

    This is the only project for the PicoW that has worked with the 6 pin versions of the device. I am trying to use this to produce a version of the Pico-W with web server to be able to use this on line. The 4 pin devices are so much more expensive that the 6 pin. Thank a great write up and worked fine for me..

  3. Akinetopsia Avatar
    Akinetopsia

    temperature does not update past the first poll when Weather
    monitoring use case set

    Code crashes when temperature goes below zero

    1. Akinetopsia Avatar
      Akinetopsia

      Oops. Correction: *MY* code crashes when temperature goes below zero. 😅 There was an issue with my dew point calculation.

      Temperature definitely doesn’t update in weather monitoring mode, tho. Updates correctly when BMP280_CASE_INDOOR use case is selected.

  4. RaduVD Avatar
    RaduVD

    Worked like a charm from the first try!

Leave a Reply

Your email address will not be published. Required fields are marked *