Raspberry Pi Pico: BME280 Get Temperature, Humidity, and Pressure (MicroPython)

Learn how to use the BME280 sensor module with the Raspberry Pi Pico board with MicroPython to get temperature, humidity, and pressure readings. This is a short tutorial to get you started quickly interfacing the BME280 sensor with the Raspberry Pi Pico board.

Raspberry Pi Pico with BME280 Get Temperature Humidity and Pressure MicroPython

New to the Raspberry Pi Pico? Get started with the Raspberry Pi Pico here.

Table of Contents:

Do you prefer to program the Raspberry Pi Pico using Arduino IDE? Check this tutorial instead: Raspberry Pi Pico – BME280 Get Temperature, Humidity, and Pressure (Arduino IDE).

Prerequisites – MicroPython Firmware

To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.

The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.

Introducing the BME280 Sensor Module

The BME280 sensor module reads barometric pressure, temperature, and humidity. Because pressure changes with altitude, you can also estimate altitude. There are several versions of this sensor module, but we’re using the one shown in the figure below.

BME280 sensor temperature, humidity, and pressure

This sensor communicates using I2C communication protocol, so the wiring is very simple. You can use any Raspberry Pi Pico I2C pins to connect the BME280 sensor. We’ll be using GPIO 4 (SDA) and GPIO 5 (SCL)—learn more about the Raspberry Pi Pico GPIOs.

BME280Raspberry Pi Pico
Vin3.3V (OUT)
GNDGND
SCLGPIO 5
SDAGPIO 4

Parts Required

Wiring the BME280 to the Raspberry Pi Pico parts required

For this project, you need to wire the BME280 sensor module to the Raspberry Pi Pico I2C pins. Here’s a list of parts you need for this tutorial:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring the BME280 to the Raspberry Pi Pico

Wire the BME280 to any combination of the Pico I2C pins—we’ll be using GPIO 4 (SDA) and GPIO 5 (SCL).

Wiring the BME280 to the Raspberry Pi Pico

Recommended reading: Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained

BME280 MicroPython Library

The library to read from the BME280 sensor isn’t part of the standard MicroPython library by default. There are different modules/libraries you can use to read from the BME280 sensor. We’ll use the following (adapted from the Adafruit BME280 Library).

from machine import I2C
import time

# BME280 default address.
BME280_I2CADDR = 0x76

# Operating Modes
BME280_OSAMPLE_1 = 1
BME280_OSAMPLE_2 = 2
BME280_OSAMPLE_4 = 3
BME280_OSAMPLE_8 = 4
BME280_OSAMPLE_16 = 5

# BME280 Registers

BME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers
BME280_REGISTER_DIG_T2 = 0x8A
BME280_REGISTER_DIG_T3 = 0x8C

BME280_REGISTER_DIG_P1 = 0x8E
BME280_REGISTER_DIG_P2 = 0x90
BME280_REGISTER_DIG_P3 = 0x92
BME280_REGISTER_DIG_P4 = 0x94
BME280_REGISTER_DIG_P5 = 0x96
BME280_REGISTER_DIG_P6 = 0x98
BME280_REGISTER_DIG_P7 = 0x9A
BME280_REGISTER_DIG_P8 = 0x9C
BME280_REGISTER_DIG_P9 = 0x9E

BME280_REGISTER_DIG_H1 = 0xA1
BME280_REGISTER_DIG_H2 = 0xE1
BME280_REGISTER_DIG_H3 = 0xE3
BME280_REGISTER_DIG_H4 = 0xE4
BME280_REGISTER_DIG_H5 = 0xE5
BME280_REGISTER_DIG_H6 = 0xE6
BME280_REGISTER_DIG_H7 = 0xE7

BME280_REGISTER_CHIPID = 0xD0
BME280_REGISTER_VERSION = 0xD1
BME280_REGISTER_SOFTRESET = 0xE0

BME280_REGISTER_CONTROL_HUM = 0xF2
BME280_REGISTER_CONTROL = 0xF4
BME280_REGISTER_CONFIG = 0xF5
BME280_REGISTER_PRESSURE_DATA = 0xF7
BME280_REGISTER_TEMP_DATA = 0xFA
BME280_REGISTER_HUMIDITY_DATA = 0xFD


class Device:
  """Class for communicating with an I2C device.

  Allows reading and writing 8-bit, 16-bit, and byte array values to
  registers on the device."""

  def __init__(self, address, i2c):
    """Create an instance of the I2C device at the specified address using
    the specified I2C interface object."""
    self._address = address
    self._i2c = i2c

  def writeRaw8(self, value):
    """Write an 8-bit value on the bus (without register)."""
    value = value & 0xFF
    self._i2c.writeto(self._address, value)

  def write8(self, register, value):
    """Write an 8-bit value to the specified register."""
    b=bytearray(1)
    b[0]=value & 0xFF
    self._i2c.writeto_mem(self._address, register, b)

  def write16(self, register, value):
    """Write a 16-bit value to the specified register."""
    value = value & 0xFFFF
    b=bytearray(2)
    b[0]= value & 0xFF
    b[1]= (value>>8) & 0xFF
    self.i2c.writeto_mem(self._address, register, value)

  def readRaw8(self):
    """Read an 8-bit value on the bus (without register)."""
    return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF

  def readU8(self, register):
    """Read an unsigned byte from the specified register."""
    return int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF

  def readS8(self, register):
    """Read a signed byte from the specified register."""
    result = self.readU8(register)
    if result > 127:
      result -= 256
    return result

  def readU16(self, register, little_endian=True):
    """Read an unsigned 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF
    if not little_endian:
      result = ((result << 8) & 0xFF00) + (result >> 8)
    return result

  def readS16(self, register, little_endian=True):
    """Read a signed 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = self.readU16(register, little_endian)
    if result > 32767:
      result -= 65536
    return result

  def readU16LE(self, register):
    """Read an unsigned 16-bit value from the specified register, in little
    endian byte order."""
    return self.readU16(register, little_endian=True)

  def readU16BE(self, register):
    """Read an unsigned 16-bit value from the specified register, in big
    endian byte order."""
    return self.readU16(register, little_endian=False)

  def readS16LE(self, register):
    """Read a signed 16-bit value from the specified register, in little
    endian byte order."""
    return self.readS16(register, little_endian=True)

  def readS16BE(self, register):
    """Read a signed 16-bit value from the specified register, in big
    endian byte order."""
    return self.readS16(register, little_endian=False)


class BME280:
  def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
               **kwargs):
    # Check that mode is valid.
    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
        raise ValueError(
            'Unexpected mode value {0}. Set mode to one of '
            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
            'BME280_ULTRAHIGHRES'.format(mode))
    self._mode = mode
    # Create I2C device.
    if i2c is None:
      raise ValueError('An I2C object is required.')
    self._device = Device(address, i2c)
    # Load calibration values.
    self._load_calibration()
    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
    self.t_fine = 0

  def _load_calibration(self):

    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)

    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)

    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)

    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
    h4 = (h4 << 24) >> 20
    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)

    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
    h5 = (h5 << 24) >> 20
    self.dig_H5 = h5 | (
        self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)

  def read_raw_temp(self):
    """Reads the raw (uncompensated) temperature from the sensor."""
    meas = self._mode
    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
    meas = self._mode << 5 | self._mode << 2 | 1
    self._device.write8(BME280_REGISTER_CONTROL, meas)
    sleep_time = 1250 + 2300 * (1 << self._mode)

    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    time.sleep_us(sleep_time)  # Wait the required time
    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_pressure(self):
    """Reads the raw (uncompensated) pressure level from the sensor."""
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_humidity(self):
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
    raw = (msb << 8) | lsb
    return raw

  def read_temperature(self):
    """Get the compensated temperature in 0.01 of a degree celsius."""
    adc = self.read_raw_temp()
    var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
    var2 = ((
        (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
        self.dig_T3) >> 14
    self.t_fine = var1 + var2
    return (self.t_fine * 5 + 128) >> 8

  def read_pressure(self):
    """Gets the compensated pressure in Pascals."""
    adc = self.read_raw_pressure()
    var1 = self.t_fine - 128000
    var2 = var1 * var1 * self.dig_P6
    var2 = var2 + ((var1 * self.dig_P5) << 17)
    var2 = var2 + (self.dig_P4 << 35)
    var1 = (((var1 * var1 * self.dig_P3) >> 8) +
            ((var1 * self.dig_P2) >> 12))
    var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
    if var1 == 0:
      return 0
    p = 1048576 - adc
    p = (((p << 31) - var2) * 3125) // var1
    var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
    var2 = (self.dig_P8 * p) >> 19
    return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)

  def read_humidity(self):
    adc = self.read_raw_humidity()
    # print 'Raw humidity = {0:d}'.format (adc)
    h = self.t_fine - 76800
    h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
         16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
                          self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
                          self.dig_H2 + 8192) >> 14))
    h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
    h = 0 if h < 0 else h
    h = 419430400 if h > 419430400 else h
    return h >> 12

  @property
  def temperature(self):
    "Return the temperature in degrees."
    t = self.read_temperature()
    ti = t // 100
    td = t - ti * 100
    return "{}.{:02d}C".format(ti, td)

  @property
  def pressure(self):
    "Return the temperature in hPa."
    p = self.read_pressure() // 256
    pi = p // 100
    pd = p - pi * 100
    return "{}.{:02d}hPa".format(pi, pd)

  @property
  def humidity(self):
    "Return the humidity in percent."
    h = self.read_humidity()
    hi = h // 1024
    hd = h * 100 // 1024 - hi * 100
    return "{}.{:02d}%".format(hi, hd)

View raw code

Upload the previous library to your Raspberry Pi Pico board (save it with the name BME280.py). Follow the instructions below to learn how to upload the library using Thonny IDE.

If you’re using Thonny IDE, follow the next steps:

1. Copy the library code to a new file. The BME280 library code can be found here.

BME280 library MicroPython Thonny IDE

2. Go to File > Save as…

Thonny IDE ESP32 ESP8266 MicroPython Save file library to device save as

3. Select save to “Raspberry Pi Pico“:

Save Files to Raspberry Pi Pico Thonny IDE

4. Name your file as BME280.py and press the OK button:

BME280 library save to Raspberry Pi Pico Thonne IDE

And that’s it. The library was uploaded to your board. To make sure that it was uploaded successfully, go to File > Save as… and select the MicroPython device. Your file should be listed there:

BME280 Raspberry Pi pico library saved Thonny IDE

After uploading the library to your board, you can use the library functionalities in your code by importing the library.

BME280 Pressure, Temperature, and Humidity – MicroPython Code

After uploading the library to the Raspberry Pi Pico, create a new file called main.py and paste the following code. It simply prints the temperature, humidity, and pressure into the console every 5 seconds.

# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-bme280-micropython/

from machine import Pin, I2C
from time import sleep
import BME280

# Initialize I2C communication
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4), freq=10000)

while True:
    try:
        # Initialize BME280 sensor
        bme = BME280.BME280(i2c=i2c)
        
        # Read sensor data
        tempC = bme.temperature
        hum = bme.humidity
        pres = bme.pressure
        
        # Convert temperature to fahrenheit
        tempF = (bme.read_temperature()/100) * (9/5) + 32
        tempF = str(round(tempF, 2)) + 'F'
        
        # Print sensor readings
        print('Temperature: ', tempC)
        print('Temperature: ', tempF)
        print('Humidity: ', hum)
        print('Pressure: ', pres)
        
    except Exception as e:
        # Handle any exceptions during sensor reading
        print('An error occurred:', e)

    sleep(5)

View raw code

How the Code Works

First, you need to import the necessary libraries, including the BME280 module you’ve imported previously.

from machine import Pin, I2C
from time import sleep
import BME280

Set the I2C id, pins, and frequency. We’re using GPIOs 5 and 4, but any other I2C pins should work.

i2c = I2C(id=0, scl=Pin(5), sda=Pin(4), freq=10000)

Note: GPIOs 5 and 4 belong to I2C id=0—check here other combinations and their ids.

In the while loop, create a BME280 object called bme with the I2C pins defined earlier:

bme = BME280.BME280(i2c=i2c)

By default, the library assumes your BME280 sensor has the default 0x76 I2C address (if you open the library code, you’ll see the address defined right on line 5 BME280_I2CADDR = 0x76).

If your sensor is not working, it may be the case that it has a different I2C address. To check the I2C address, you can take a look at the following tutorial.

If after running the I2C scanner we provided previously, your sensor has a different I2C address, change this line:

bme = BME280.BME280(i2c=i2c)

To:

bme = BME280.BME280(i2c=i2c, addr=YOUR_I2C_ADDRESS)

 For example, if your I2C address is 0x78, it will look as follows:

bme = BME280.BME280(i2c=i2c, addr=0x78)

Reading temperature, humidity, and pressure is as simple as using the temperature, humidity, and pressure methods on the bme object.

temp = bme.temperature
hum = bme.humidity
pres = bme.pressure

If you want to display the temperature in Fahrenheit degrees, you just need to uncomment (remove the #) the next two lines:

# temp = (bme.read_temperature()/100) * (9/5) + 32
# temp = str(round(temp, 2)) + 'F'

Finally, print the readings on the shell:

print('Temperature: ', temp)
print('Humidity: ', hum)
print('Pressure: ', pres)

In the end, we add a delay of 5 seconds:

sleep(5)

We use try and except to catch any unexpected errors that might occur while trying to read the sensor.

except Exception as e:
    print('An error occurred:', e)

Demonstration

Save the code to your Raspberry Pi Pico board using Thonny IDE or any other MicroPython IDE of your choice.

Copy the MicroPython code provided to a new file on Thonny IDE.

Raspberry Pi Pico Thonny IDE Read Temperature, Humidity, and Pressure from BME280

With the code copied to the file, click on the Save icon. Then, select Raspberry Pi Pico.

Saving file to Raspberry Pi Pico MicroPython IDE

Save the file with the following name: main.py.

Saving file main.py to Raspberry Pi Pico MicroPython IDE

Note: When you name a file main.py, the Raspberry Pi Pico will run that file automatically on boot. If you call it a different name, it will still be saved on the board filesystem, but it will not run automatically on boot.

Click the little green button “Run Current Script” or press F5.

run script on thonny ide

New temperature and humidity readings will be published on the shell approximately every five seconds.

Raspberry Pi Pico print temperature, humidity and pressure from BME280

Troubleshooting

Your BME280 is not working? It might be: incorrect wiring, wrong I2C address, broken sensor or you got a fake BME280 sensor. Take a look at this BME280 troubleshooting guide to find the cause and solution for your problem.

Wrapping Up

In this tutorial, you learned how to interface the BME280 temperature, humidity, and pressure sensor with the Raspberry Pi Pico and how to write a MicroPython program to get and display readings on the MicroPython console. This is one of the simplest examples to get you started using the BME280 sensor.

We hope you’ve found this tutorial useful. We have tutorials for other popular environmental sensors:

You can check all our Raspberry Pi Pico projects on the following link:

Finally, if you would like to interface the BME280 with other microcontrollers, we have tutorials for ESP32, ESP8266, Arduino, and Raspberry Pi:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

4 thoughts on “Raspberry Pi Pico: BME280 Get Temperature, Humidity, and Pressure (MicroPython)”

  1. Nice article, as usual!

    Also, I have created ESP32-CAM and RPi-PICO based designs that incorporated both the BME280 & BME680 (same as ‘280 but adds a VOC gas sensor) along with OLED display and a single”NeoPixel/WS2812″ RGB LED for visual status indication with both of then running MicroPython.

    The RPi-PICO was just a simple standalone design that displayed data on its OLED and outputting sensor data via USB Serial to a host, whereas, the ESP32-CAM based design not only displayed the data on OLED but also directly logged all data from both sensors to a MySQL database in the cloud via WiFi. The ESP32-CAM board has been running non-stop (except for infrequent power outages!) for a few years updating the MySQL database table every 15 seconds!

    I also hacked enough support of the integrated 2MP camera module of the ESP32-CAM to slowly get still image JPEGs from the camera, all written MicroPython code!

    My overall intent for the ESP32-CAM based design was to use some AI/ML technology to glean indoor environmental trends and potentially identify certain “odor” related events!

    Rui and company, please keep up all the great work you all do!

    Best Wishes!

    Reply
  2. Bravo!! This project only took me about 10 min before I had success. I’ve been trying to get this exact program (with a web server) working for a couple of days using other websites’ code. As usual, RNT is the best. Please continue with Raspberry Pi Pico (W) projects! Thanks again.

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.