Learn how to use the BME680 environmental sensor module with the Raspberry Pi Pico board programmed with MicroPython to get data about temperature, humidity, pressure, and gas (air quality). We’ll build a basic example to show you how to wire the sensor, which library you should use, and a sample code to get data from the sensor.
New to the Raspberry Pi Pico? Get started with the Raspberry Pi Pico here.
Table of Contents:
- Introducing BME680 Environmental Sensor Module
- Wiring the BME680 to the Raspberry Pi Pico
- BME680 MicroPython Library
- BME680 Pressure, Temperature, Humidity, and Gas Air Quality – MicroPython Code
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 BME680 Environmental Sensor Module
The BME680 is an environmental sensor that combines gas, pressure, humidity, and temperature sensors. The gas sensor can detect a broad range of gases like volatile organic compounds (VOC). For this reason, the BME680 can be used in indoor air quality control.
BME680 Measurements
The BME680 is a 4-in-1 digital sensor that measures:
- Temperature
- Humidity
- Barometric pressure
- Gas: Volatile Organic Compounds (VOC) like ethanol and carbon monoxide
Gas Sensor
The BME680 contains a MOX (Metal-oxide) sensor that detects VOCs in the air. This sensor gives you a qualitative idea of the sum of VOCs/contaminants in the surrounding air – it is not specific for a specific gas molecule.
MOX sensors are composed of a metal-oxide surface, a sensing chip to measure changes in conductivity and a heater. It detects VOCs by adsorption of oxygen molecules on its sensitive layer. The BME680 reacts to most VOCs polluting indoor air (except CO2).
When the sensor comes into contact with the reducing gases, the oxygen molecules react and increase the conductivity across the surface. As a raw signal, the BME680 outputs resistance values. These values change due to variations in VOC concentrations:
- Higher concentration of VOCs » Lower resistance
- Lower concentration of VOCs » Higher resistance
The reactions that occur on the sensor surface (thus, the resistance) are influenced by parameters other than VOC concentration like temperature and humidity.
Relevant Information Regarding Gas Sensor
The gas sensor gives you a qualitative idea of VOCs gases in the surrounding air. So, you can get trends, compare your results, and see if the air quality is increasing or decreasing. To get precise measurements, you must calibrate the sensor against known sources and build a calibration curve.
When you first get the sensor, it is recommended to run it for 48 hours before starting to collect “real” data. After that, it is also recommended to run the sensor for 30 minutes before getting a gas reading.
BME680 Accuracy
Here’s the accuracy of the temperature, humidity, and pressure sensors of the BME680:
Sensor | Accuracy |
Temperature | +/- 1.0ºC |
Humidity | +/- 3% |
Pressure | +/- 1 hPa |
BME680 Operation Range
The following table shows the operation range for the temperature, humidity, and pressure sensors for the BME680.
Sensor | Operation Range |
Temperature | -40 to 85 ºC |
Humidity | 0 to 100 % |
Pressure | 300 to 1100 hPa |
BME680 Pinout
Here’s the BME680 Pinout:
VCC | Powers the sensor |
GND | Common GND |
SCL | SCL pin for I2C communication SCK pin for SPI communication |
SDA | SDA pin for I2C communication SDI (MOSI) pin for SPI communication |
SDO | SDO (MISO) pin for SPI communication |
CS | Chip select pin for SPI communication |
BME680 Interface
The BME680 supports I2C and SPI Interfaces.
BME680 I2C
This sensor communicates using I2C communication protocol, so the wiring is straighforward. You can use any I2C pin combination of the Raspberry Pi Pico. We’ll be using GPIO 5 (SCL) and GPIO 4 (SDA). You can use any other combination of I2C pins as long as you add them to the code.
BME680 | Raspberry Pi Pico |
Vin | 3.3V |
GND | GND |
SCL | GPIO 5 |
SDA | GPIO 4 |
Learn more about the Raspberry Pi Pico GPIOs: Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained
Parts Required
For this project, you need to wire the BME680 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 BME680 to the Raspberry Pi Pico
Wire the BME680 to any combination of the Pico I2C pins—we’ll be using GPIO 4 (SDA) and GPIO 5 (SCL).
Recommended reading: Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained
BME680 MicroPython Library
The MicroPython library package doesn’t come with a BME680 library by default. There are several different modules to read from the BME680 sensor. We’ll use the following module adapted from the Adafruit BME680 library.
# Spaces, comments and some functions have been removed from the original file to save memory
# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py
import time
import math
from micropython import const
from ubinascii import hexlify as hex
try:
import struct
except ImportError:
import ustruct as struct
_BME680_CHIPID = const(0x61)
_BME680_REG_CHIPID = const(0xD0)
_BME680_BME680_COEFF_ADDR1 = const(0x89)
_BME680_BME680_COEFF_ADDR2 = const(0xE1)
_BME680_BME680_RES_HEAT_0 = const(0x5A)
_BME680_BME680_GAS_WAIT_0 = const(0x64)
_BME680_REG_SOFTRESET = const(0xE0)
_BME680_REG_CTRL_GAS = const(0x71)
_BME680_REG_CTRL_HUM = const(0x72)
_BME280_REG_STATUS = const(0xF3)
_BME680_REG_CTRL_MEAS = const(0x74)
_BME680_REG_CONFIG = const(0x75)
_BME680_REG_PAGE_SELECT = const(0x73)
_BME680_REG_MEAS_STATUS = const(0x1D)
_BME680_REG_PDATA = const(0x1F)
_BME680_REG_TDATA = const(0x22)
_BME680_REG_HDATA = const(0x25)
_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16)
_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127)
_BME680_RUNGAS = const(0x10)
_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0,
2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0,
2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0,
2147483647.0)
_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0,
64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0,
500000.0, 250000.0, 125000.0)
def _read24(arr):
ret = 0.0
for b in arr:
ret *= 256.0
ret += float(b & 0xFF)
return ret
class Adafruit_BME680:
def __init__(self, *, refresh_rate=10):
self._write(_BME680_REG_SOFTRESET, [0xB6])
time.sleep(0.005)
chip_id = self._read_byte(_BME680_REG_CHIPID)
if chip_id != _BME680_CHIPID:
raise RuntimeError('Failed 0x%x' % chip_id)
self._read_calibration()
self._write(_BME680_BME680_RES_HEAT_0, [0x73])
self._write(_BME680_BME680_GAS_WAIT_0, [0x65])
self.sea_level_pressure = 1013.25
self._pressure_oversample = 0b011
self._temp_oversample = 0b100
self._humidity_oversample = 0b010
self._filter = 0b010
self._adc_pres = None
self._adc_temp = None
self._adc_hum = None
self._adc_gas = None
self._gas_range = None
self._t_fine = None
self._last_reading = 0
self._min_refresh_time = 1000 / refresh_rate
@property
def pressure_oversample(self):
return _BME680_SAMPLERATES[self._pressure_oversample]
@pressure_oversample.setter
def pressure_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def humidity_oversample(self):
return _BME680_SAMPLERATES[self._humidity_oversample]
@humidity_oversample.setter
def humidity_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def temperature_oversample(self):
return _BME680_SAMPLERATES[self._temp_oversample]
@temperature_oversample.setter
def temperature_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def filter_size(self):
return _BME680_FILTERSIZES[self._filter]
@filter_size.setter
def filter_size(self, size):
if size in _BME680_FILTERSIZES:
self._filter = _BME680_FILTERSIZES[size]
else:
raise RuntimeError("Invalid")
@property
def temperature(self):
self._perform_reading()
calc_temp = (((self._t_fine * 5) + 128) / 256)
return calc_temp / 100
@property
def pressure(self):
self._perform_reading()
var1 = (self._t_fine / 2) - 64000
var2 = ((var1 / 4) * (var1 / 4)) / 2048
var2 = (var2 * self._pressure_calibration[5]) / 4
var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
(self._pressure_calibration[2] * 32) / 8) +
((self._pressure_calibration[1] * var1) / 2))
var1 = var1 / 262144
var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
calc_pres = 1048576 - self._adc_pres
calc_pres = (calc_pres - (var2 / 4096)) * 3125
calc_pres = (calc_pres / var1) * 2
var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072
calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16)
return calc_pres/100
@property
def humidity(self):
self._perform_reading()
temp_scaled = ((self._t_fine * 5) + 128) / 256
var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
((temp_scaled * self._humidity_calibration[2]) / 200))
var2 = (self._humidity_calibration[1] *
(((temp_scaled * self._humidity_calibration[3]) / 100) +
(((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) /
64) / 100) + 16384)) / 1024
var3 = var1 * var2
var4 = self._humidity_calibration[5] * 128
var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
var6 = (var4 * var5) / 2
calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
calc_hum /= 1000
if calc_hum > 100:
calc_hum = 100
if calc_hum < 0:
calc_hum = 0
return calc_hum
@property
def altitude(self):
pressure = self.pressure
return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903))
@property
def gas(self):
self._perform_reading()
var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536
var2 = ((self._adc_gas * 32768) - 16777216) + var1
var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512
calc_gas_res = (var3 + (var2 / 2)) / var2
return int(calc_gas_res)
def _perform_reading(self):
if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1)
< self._min_refresh_time):
return
self._write(_BME680_REG_CONFIG, [self._filter << 2])
self._write(_BME680_REG_CTRL_MEAS,
[(self._temp_oversample << 5)|(self._pressure_oversample << 2)])
self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample])
self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
ctrl = self._read_byte(_BME680_REG_CTRL_MEAS)
ctrl = (ctrl & 0xFC) | 0x01
self._write(_BME680_REG_CTRL_MEAS, [ctrl])
new_data = False
while not new_data:
data = self._read(_BME680_REG_MEAS_STATUS, 15)
new_data = data[0] & 0x80 != 0
time.sleep(0.005)
self._last_reading = time.ticks_ms()
self._adc_pres = _read24(data[2:5]) / 16
self._adc_temp = _read24(data[5:8]) / 16
self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0]
self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64)
self._gas_range = data[14] & 0x0F
var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
var2 = (var1 * self._temp_calibration[1]) / 2048
var3 = ((var1 / 2) * (var1 / 2)) / 4096
var3 = (var3 * self._temp_calibration[2] * 16) / 16384
self._t_fine = int(var2 + var3)
def _read_calibration(self):
coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25)
coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16)
coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
coeff = [float(i) for i in coeff]
self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]]
self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]]
self._gas_calibration = [coeff[x] for x in [25, 24, 26]]
self._humidity_calibration[1] *= 16
self._humidity_calibration[1] += self._humidity_calibration[0] % 16
self._humidity_calibration[0] /= 16
self._heat_range = (self._read_byte(0x02) & 0x30) / 16
self._heat_val = self._read_byte(0x00)
self._sw_err = (self._read_byte(0x04) & 0xF0) / 16
def _read_byte(self, register):
return self._read(register, 1)[0]
def _read(self, register, length):
raise NotImplementedError()
def _write(self, register, values):
raise NotImplementedError()
class BME680_I2C(Adafruit_BME680):
def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10):
self._i2c = i2c
self._address = address
self._debug = debug
super().__init__(refresh_rate=refresh_rate)
def _read(self, register, length):
result = bytearray(length)
self._i2c.readfrom_mem_into(self._address, register & 0xff, result)
if self._debug:
print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result]))
return result
def _write(self, register, values):
if self._debug:
print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values]))
for value in values:
self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF]))
register += 1
Upload the previous library to your Raspberry Pi Pico board (save it with the name bme680.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 BME680 library code can be found here.
2. Go to File > Save as…
3. Select save to “Raspberry Pi Pico“:
4. Name your file as bme680.py and press the OK button:
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:
After uploading the library to your board, you can use the library methods by importing it at the beginning of your code.
BME680 Pressure, Temperature, Humidity, and Gas Air Quality – MicroPython Code
After uploading the library to the Raspberry Pi Pico, create a new file called main.py and paste the following code. It prints the temperature, humidity, pressure, and gas resistance into the console every 5 seconds.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details: https://RandomNerdTutorials.com/raspberry-pi-pico-bme680-micropython/
from machine import Pin, I2C
from time import sleep
from bme680 import *
# RPi Pico - Pin assignment
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4))
bme = BME680_I2C(i2c=i2c)
while True:
try:
temp = str(round(bme.temperature, 2)) + ' C'
#temp = (bme.temperature) * (9/5) + 32
#temp = str(round(temp, 2)) + 'F'
hum = str(round(bme.humidity, 2)) + ' %'
pres = str(round(bme.pressure, 2)) + ' hPa'
gas = str(round(bme.gas/1000, 2)) + ' KOhms'
print('Temperature:', temp)
print('Humidity:', hum)
print('Pressure:', pres)
print('Gas:', gas)
print('-------')
except OSError as e:
print('Failed to read sensor.')
sleep(5)
How the Code Works
First, you need to import the necessary libraries, including the bme680 module you’ve imported previously.
from machine import Pin, I2C
from time import sleep
from bme680 import *
Set the I2C id, and pins. We’re using GPIOs 5 and 4, but any other I2C pins should work.
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4))
Note: GPIOs 5 and 4 belong to I2C id=0—check here other combinations and their ids.
Create a BME680 object called bme with the I2C pins defined earlier:
bme = BME680_I2C(i2c=i2c)
Reading temperature, humidity, pressure, and gas resistance is as simple as getting the temperature, humidity, pressure, and gas attributes from the bme object.
temp = str(round(bme.temperature, 2)) + ' C'
#temp = (bme.temperature) * (9/5) + 32
#temp = str(round(temp, 2)) + 'F'
hum = str(round(bme.humidity, 2)) + ' %'
pres = str(round(bme.pressure, 2)) + ' hPa'
gas = str(round(bme.gas/1000, 2)) + ' KOhms'
Finally, print the readings on the shell:
print('Temperature: ', temp)
print('Humidity: ', hum)
print('Pressure: ', pres)
print('Gas:', gas)
In the end, we add a delay of 5 seconds:
sleep(5)
Demonstration
Run the code on your board.
New BME680 sensor readings should be displayed every 5 seconds.
Note: if you want a code to run automatically when the Raspberry Pi Pico boots (for example, without being connected to your computer), you need to save the file to the board with the name main.py.
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.
Wrapping Up
This tutorial was a getting started guide to the BME680 environmental and air quality sensor with the Raspberry Pi Pico using MicroPython firmware.
We hope you’ve found this tutorial useful. We have tutorials for other popular environmental sensors:
- Raspberry Pi Pico: DS18B20 Temperature Sensor (MicroPython) – Single and Multiple
- Raspberry Pi Pico: DHT11/DHT22 Temperature and Humidity Sensor (MicroPython)
- Raspberry Pi Pico: BME280 Get Temperature, Humidity, and Pressure (MicroPython)
You can check all our Raspberry Pi Pico projects on the following link:
Thanks for reading.
Unfortunately, this article didn’t work with my pi pico w and pimoroni bme680. I got the following error.
Traceback (most recent call last):
File “”, line 11, in
File “bme680.py”, line 342, in init
File “bme680.py”, line 104, in init
File “bme680.py”, line 357, in _write
OSError: [Errno 5] EIO
I double checked everything and i2c.scan() found the device.
Hi.
Are you sure you’re using a BME680 sensor?
Maybe you have a BME280 or BME180??
Regards,
Sara
Hi Sara,
i definitely using a BME680.
Regards
Thorsten
Hi.
I’m sorry, but I’m not sure what might be the issue.
Try just reading the temperature.
Does it work?
Regards,
Sara