Overview
In this tutorial, we will learn how to Interface BME280 with Raspberry Pi Pico using MicroPython. The BME280 is a popular environmental sensor from BOSCH that can measure temperature, humidity, pressure & altitude. BME280 can be used to make Weather Station projects as well as Altimeter.
Using the BME280 library, we can develop a MicroPython Code in Thonny IDE to read data using Raspberry Pi Pico. The tutorial contains circuits, connections, and Code to communicate BME280 via the I2C protocol. In the first example, we will display the BME280 Sensor reading on Shell. In the 2nd example, we will use a 0.96″ I2C OLED Display to display BME280 parameters.
Bill of Materials
We need the following components for this tutorial:
- Raspberry Pi Pico – 1
- BME280 Sensor – 1
- 0.96″ OLED Display – 1
- Jumper Wires – 10
- Breadbaord – 1
- Micro-USB Cable – 1
BME280 Barometric Pressure Sensor
The BME280 is Barometric Pressure Sensor that can measure temperature, Humidity & Atmospheric Pressure. The Sensor has an I2C Bus and operates on 3.3V Power Supply. The unit combines high linearity and high accuracy sensors and is perfectly feasible for low current consumption, long-term stability, and high EMC robustness.
The sensor is best for measuring humidity with ±3% accuracy, barometric pressure with ±1 hPa absolute accuracy, and temperature with ±1.0°C accuracy. Because pressure changes with altitude and the pressure measurements are so good, you can also use it as an altimeter with ±1 meter or better accuracy.
Interfacing BME280 Sensor with Raspberry Pi Pico
Let us connect BME280 Sensor with Raspberry Pi Pico via I2C pins. The connection is fairly simple.
Connect the VCC, GND, SCL & SDA pin of BME280 Sensor to 3.3V, GND, GP21 & GP20 of Raspberry Pi Pico respectively.
You can use a breadboard for assembly & testing.
MicroPython Code: BME280 & Pi Pico Interfacing
The code for interfacing the BME280 Sensor with Raspberry Pi Pico is divided into two parts:
- bme280.py
- main.py
First, the main code requires a library for BME280. The library has all the required modules and registers details to extract the data from the Sensor.
bme280.py
Open your Thonny IDE and paste the following code to the Thonny Editor. Save the file inside the Raspberry Pi Pico and name it as bme280.py.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# Authors: Paul Cunnane 2016, Peter Dahlebrg 2016 # # This module borrows from the Adafruit BME280 Python library. Original # Copyright notices are reproduced below. # # Those libraries were written for the Raspberry Pi. This modification is # intended for the MicroPython and esp8266 boards. # # Copyright (c) 2014 Adafruit Industries # Author: Tony DiCola # # Based on the BMP280 driver with BME280 changes provided by # David J Taylor, Edinburgh (www.satsignal.eu) # # Based on Adafruit_I2C.py created by Kevin Townsend. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import time from ustruct import unpack, unpack_from from array import array # 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_REGISTER_CONTROL_HUM = 0xF2 BME280_REGISTER_CONTROL = 0xF4 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 self.address = address if i2c is None: raise ValueError('An I2C object is required.') self.i2c = i2c # load calibration data dig_88_a1 = self.i2c.readfrom_mem(self.address, 0x88, 26) dig_e1_e7 = self.i2c.readfrom_mem(self.address, 0xE1, 7) self.dig_T1, self.dig_T2, self.dig_T3, self.dig_P1, \ self.dig_P2, self.dig_P3, self.dig_P4, self.dig_P5, \ self.dig_P6, self.dig_P7, self.dig_P8, self.dig_P9, \ _, self.dig_H1 = unpack("<HhhHhhhhhhhhBB", dig_88_a1) self.dig_H2, self.dig_H3 = unpack("<hB", dig_e1_e7) e4_sign = unpack_from("<b", dig_e1_e7, 3)[0] self.dig_H4 = (e4_sign << 4) | (dig_e1_e7[4] & 0xF) e6_sign = unpack_from("<b", dig_e1_e7, 5)[0] self.dig_H5 = (e6_sign << 4) | (dig_e1_e7[4] >> 4) self.dig_H6 = unpack_from("<b", dig_e1_e7, 6)[0] self.i2c.writeto_mem(self.address, BME280_REGISTER_CONTROL, bytearray([0x3F])) self.t_fine = 0 # temporary data holders which stay allocated self._l1_barray = bytearray(1) self._l8_barray = bytearray(8) self._l3_resultarray = array("i", [0, 0, 0]) def read_raw_data(self, result): """ Reads the raw (uncompensated) data from the sensor. Args: result: array of length 3 or alike where the result will be stored, in temperature, pressure, humidity order Returns: None """ self._l1_barray[0] = self._mode self.i2c.writeto_mem(self.address, BME280_REGISTER_CONTROL_HUM, self._l1_barray) self._l1_barray[0] = self._mode << 5 | self._mode << 2 | 1 self.i2c.writeto_mem(self.address, BME280_REGISTER_CONTROL, self._l1_barray) 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 # burst readout from 0xF7 to 0xFE, recommended by datasheet self.i2c.readfrom_mem_into(self.address, 0xF7, self._l8_barray) readout = self._l8_barray # pressure(0xF7): ((msb << 16) | (lsb << 8) | xlsb) >> 4 raw_press = ((readout[0] << 16) | (readout[1] << 8) | readout[2]) >> 4 # temperature(0xFA): ((msb << 16) | (lsb << 8) | xlsb) >> 4 raw_temp = ((readout[3] << 16) | (readout[4] << 8) | readout[5]) >> 4 # humidity(0xFD): (msb << 8) | lsb raw_hum = (readout[6] << 8) | readout[7] result[0] = raw_temp result[1] = raw_press result[2] = raw_hum def read_compensated_data(self, result=None): """ Reads the data from the sensor and returns the compensated data. Args: result: array of length 3 or alike where the result will be stored, in temperature, pressure, humidity order. You may use this to read out the sensor without allocating heap memory Returns: array with temperature, pressure, humidity. Will be the one from the result parameter if not None """ self.read_raw_data(self._l3_resultarray) raw_temp, raw_press, raw_hum = self._l3_resultarray # temperature var1 = ((raw_temp >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11) var2 = (((((raw_temp >> 4) - self.dig_T1) * ((raw_temp >> 4) - self.dig_T1)) >> 12) * self.dig_T3) >> 14 self.t_fine = var1 + var2 temp = (self.t_fine * 5 + 128) >> 8 # 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: pressure = 0 else: p = 1048576 - raw_press p = (((p << 31) - var2) * 3125) // var1 var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 var2 = (self.dig_P8 * p) >> 19 pressure = ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) # humidity h = self.t_fine - 76800 h = (((((raw_hum << 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 humidity = h >> 12 if result: result[0] = temp result[1] = pressure result[2] = humidity return result return array("i", (temp, pressure, humidity)) @property def values(self): """ human readable values """ t, p, h = self.read_compensated_data() p = p // 256 pi = p // 100 pd = p - pi * 100 hi = h // 1024 hd = h * 100 // 1024 - hi * 100 return ("{}C".format(t / 100), "{}.{:02d}hPa".format(pi, pd), "{}.{:02d}%".format(hi, hd)) |
main.py
Open another tab on Thonny IDE and paste the following code. Save the file as main.py on Raspberry Pi Pico.
|
1 2 3 4 5 6 7 8 9 10 11 |
from machine import Pin, I2C #importing relevant modules & classes from time import sleep import bme280 #importing BME280 library i2c=I2C(0,sda=Pin(20), scl=Pin(21), freq=400000) #initializing the I2C method while True: bme = bme280.BME280(i2c=i2c) #BME280 object created print(bme.values) sleep(10) #delay of 10s |
Run the main.py file now. If the hardware connection is correct, the Shell will display the following results.
You can check the value of temperature, pressure, and humidity.
Interfacing BME280 Sensor with Raspberry Pi Pico & OLED Display
Let us connect the BME280 Sensor with Raspberry Pi Pico & display the temperature, pressure, and humidity reading on a 0.96″ I2C OLED Display. The connection is fairly simple as shown below.
Connect the VCC, GND, SCL & SDA pin of OLED Display to 3.3V, GND, GP21 & GP20 of Raspberry Pi Pico respectively.
You can again use a breadboard for assembly & testing.
MicroPython Code: BME280, OLED Display & Pi Pico Interfacing
The code for interfacing the BME280 Sensor with Raspberry Pi Pico & OLED Display is divided into three parts:
- bme280.py
- ssd1306.py
- main.py
The main code requires a library for BME280 & SSD1306 OLED Display. The library has all the required modules and registers details to extract the data from the BME280 Sensor & display the reading on a 0.96″ OLED Display.
Copy the bme280 library from the above code and save it to Raspberry Pi Pico as bme280.py.
main.py
We also need a library for SSD1306 OLED Display. Open a tab on Thonny IDE and paste the following code. Save the file as ssd1306.py on Raspberry Pi Pico.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces from micropython import const import framebuf # register definitions SET_CONTRAST = const(0x81) SET_ENTIRE_ON = const(0xA4) SET_NORM_INV = const(0xA6) SET_DISP = const(0xAE) SET_MEM_ADDR = const(0x20) SET_COL_ADDR = const(0x21) SET_PAGE_ADDR = const(0x22) SET_DISP_START_LINE = const(0x40) SET_SEG_REMAP = const(0xA0) SET_MUX_RATIO = const(0xA8) SET_COM_OUT_DIR = const(0xC0) SET_DISP_OFFSET = const(0xD3) SET_COM_PIN_CFG = const(0xDA) SET_DISP_CLK_DIV = const(0xD5) SET_PRECHARGE = const(0xD9) SET_VCOM_DESEL = const(0xDB) SET_CHARGE_PUMP = const(0x8D) # Subclassing FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): def __init__(self, width, height, external_vcc): self.width = width self.height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) self.init_display() def init_display(self): for cmd in ( SET_DISP | 0x00, # off # address setting SET_MEM_ADDR, 0x00, # horizontal # resolution and layout SET_DISP_START_LINE | 0x00, SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 SET_MUX_RATIO, self.height - 1, SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 SET_DISP_OFFSET, 0x00, SET_COM_PIN_CFG, 0x02 if self.width > 2 * self.height else 0x12, # timing and driving scheme SET_DISP_CLK_DIV, 0x80, SET_PRECHARGE, 0x22 if self.external_vcc else 0xF1, SET_VCOM_DESEL, 0x30, # 0.83*Vcc # display SET_CONTRAST, 0xFF, # maximum SET_ENTIRE_ON, # output follows RAM contents SET_NORM_INV, # not inverted # charge pump SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, SET_DISP | 0x01, ): # on self.write_cmd(cmd) self.fill(0) self.show() def poweroff(self): self.write_cmd(SET_DISP | 0x00) def poweron(self): self.write_cmd(SET_DISP | 0x01) def contrast(self, contrast): self.write_cmd(SET_CONTRAST) self.write_cmd(contrast) def invert(self, invert): self.write_cmd(SET_NORM_INV | (invert & 1)) def show(self): x0 = 0 x1 = self.width - 1 if self.width == 64: # displays with width of 64 pixels are shifted by 32 x0 += 32 x1 += 32 self.write_cmd(SET_COL_ADDR) self.write_cmd(x0) self.write_cmd(x1) self.write_cmd(SET_PAGE_ADDR) self.write_cmd(0) self.write_cmd(self.pages - 1) self.write_data(self.buffer) class SSD1306_I2C(SSD1306): def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): self.i2c = i2c self.addr = addr self.temp = bytearray(2) self.write_list = [b"\x40", None] # Co=0, D/C#=1 super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.temp[0] = 0x80 # Co=1, D/C#=0 self.temp[1] = cmd self.i2c.writeto(self.addr, self.temp) def write_data(self, buf): self.write_list[1] = buf self.i2c.writevto(self.addr, self.write_list) class SSD1306_SPI(SSD1306): def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): self.rate = 10 * 1024 * 1024 dc.init(dc.OUT, value=0) res.init(res.OUT, value=0) cs.init(cs.OUT, value=1) self.spi = spi self.dc = dc self.res = res self.cs = cs import time self.res(1) time.sleep_ms(1) self.res(0) time.sleep_ms(10) self.res(1) super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(0) self.cs(0) self.spi.write(bytearray([cmd])) self.cs(1) def write_data(self, buf): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(1) self.cs(0) self.spi.write(buf) self.cs(1) |
main.py
Open another tab on Thonny IDE and paste the following code. Save the file as main.py on Raspberry Pi Pico.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from machine import Pin, I2C from ssd1306 import SSD1306_I2C import time import bme280 i2c=I2C(0,sda=Pin(20), scl=Pin(21), freq=400000) #initializing the I2C method WIDTH =128 HEIGHT= 64 oled = SSD1306_I2C(WIDTH,HEIGHT,i2c) while True: oled.fill(0) bme = bme280.BME280(i2c=i2c) print(bme.values) oled.text("Temp:", 0, 0) oled.text(str(bme.values[0]), 50, 0) oled.text("PA:", 0, 20) oled.text(str(bme.values[1]), 50, 20) oled.text("Humi:", 0, 40) oled.text(str(bme.values[2]), 50, 40) oled.show() time.sleep(1) |
Run the main.py code now. The OLED will immediately display the value of temperature, pressure, and humidity.
If you are making a weather station, you will need a display to view the live data. Therefore OLED display can be essential for this project.














5 Comments
-ver-italiano-Ciao ho eseguito questo tuo progetto alla lettera , ma mi ritorna sempre questo errore :
Hi, I ran your project to the letter, but I always get this error:
Traceback (most recent call last):
File “”, line 11, in
File “bme280.py”, line 74, in init
OSError: [Errno 5] EIO
— Potresti darmi un aiuto a risolverlo – grazie – Cordiali Saluti – Paolo
Could you give me some help to fix it – thank you – Best Regards – Paolo
only work with thronny???
try start it with usb power nothing hapend
Did you fix it?
The best tutorial for BME280 & Rpi Pico, thanks
A little mistake in text – before SSD1306 library code header says “main.py” – should be “ssd1306.py”