Pico with BME280 breakout (via Explorer Base and MicroPython)

Not quite sure the best place to put this but I’ve been able to communicate with the BME280 breakout board using the Pico and MicroPython though my code still needs more work it might help people communicate with more breakout boards or finish the bme280 calibration calculations part which is missing from mine:

from machine import Pin, I2C
from micropython import const
from time import sleep

_ADDR = const(0x76)

_CALIB2 = const(0xe1)
_CALIB1 = const(0x88)
_DATA = const(0xf7)
_CONFIG = const(0xf5)
_CTRL_MEAS = const(0xf4)
_CTRL_HUM = const(0xf2)
_CHIP_ID = const(0xd0)
_RESET = const(0xe0)

class TooManyAttempts(Exception):
“”“Too many attempts”“”

class BME280:
def init(self, i2c):
self._i2c = i2c
self._ready = bytearray(1)
self._chip_id = bytearray(1)
self._data = bytearray(8)
self._calib1 = bytearray(26)
self._calib2 = bytearray(7)

def ready(self):
    attempts = 100
    while True:
        self._i2c.readfrom_into(_ADDR, self._ready)
        if self._ready == b'\x00':
            break
        attempts -= 1
        if not attempts:
            raise TooManyAttempts()

def chip_id(self):
    self.ready()
    self._i2c.readfrom_mem_into(_ADDR, _CHIP_ID, self._chip_id)
    return self._chip_id

def reset(self):
    self.ready()
    self._i2c.writeto_mem(_ADDR, _RESET, b'\xb6')
    
def ctrl_hum(self, osrs_h=16):
    self.ready()
    self._i2c.writeto_mem(_ADDR, _CTRL_HUM, chr(0b101))
    
def ctrl_meas(self, osrs_t=16, osrs_p=16, mode='normal'):
    self.ready()
    self._i2c.writeto_mem(_ADDR, _CTRL_MEAS, chr(0b10110111))
    
def config(self, t_sb=500, filter=2):
    self.ready()
    self._i2c.writeto_mem(_ADDR, _CONFIG, chr(0b10001000))
    
def data(self):
    self.ready()
    self._i2c.readfrom_mem_into(_ADDR, _DATA, self._data)
    return self._data

def calibration(self):
    self.ready()
    self._i2c.readfrom_mem_into(_ADDR, _CALIB1, self._calib1)
    self.ready()
    self._i2c.readfrom_mem_into(_ADDR, _CALIB2, self._calib2)

def init(self):
    self.reset()
    sleep(0.1)
    self.ctrl_hum()
    self.ctrl_meas()
    self.config()
    self.calibration()

bme280 = BME280(I2C(0, scl=Pin(21), sda=Pin(20)))
print(bme280.chip_id())

Great news that somebody is looking into this.
You may find these useful
GitHub - robert-hh/BME280: Micropython driver for the BME280 sensor, target platform Pycom devices

Thanks so much for this. When I didn’t see one in the pimoroni repo I assumed I’d have to write my own! Forced me to learn about MicroPython though.

Hope you manage to get it working. Please post when you do.
Beyond my current skills at the moment but I need a MicroPython driver for this sensor.
I think Raspberry Pi Pico team should be putting some effort into the MicroPython libraries for the most common sensors or most users will soon get fed up with just flashing LEDs and reading buttons and use CircuitPython which does not need a Pico!

from machine import Pin, I2C
from micropython import const
from time import sleep
import ustruct

_ADDR = const(0x76)

_CALIB2 = const(0xE1)
_CALIB1 = const(0x88)
_DATA = const(0xF7)
_CONFIG = const(0xF5)
_CTRL_MEAS = const(0xF4)
_CTRL_HUM = const(0xF2)
_CHIP_ID = const(0xD0)
_RESET = const(0xE0)


class TooManyAttempts(Exception):
    """Too many attempts"""


class BME280:
    def __init__(self, i2c):
        self._i2c = i2c
        self._ready = bytearray(1)
        self._chip_id = bytearray(1)
        self._data = bytearray(8)
        self._calib1 = bytearray(26)
        self._calib2 = bytearray(7)

    def ready(self):
        attempts = 100
        while True:
            self._i2c.readfrom_into(_ADDR, self._ready)
            if self._ready == b"\x00":
                break
            attempts -= 1
            if not attempts:
                raise TooManyAttempts()

    def chip_id(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CHIP_ID, self._chip_id)
        return self._chip_id

    def reset(self):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _RESET, b"\xb6")

    def ctrl_hum(self, osrs_h=16):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CTRL_HUM, chr(0b101))

    def ctrl_meas(self, osrs_t=16, osrs_p=16, mode="normal"):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CTRL_MEAS, chr(0b10110111))

    def config(self, t_sb=500, filter=2):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CONFIG, chr(0b10001000))

    def data(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _DATA, self._data)

    @property
    def raw_pressure(self):
        data = self._data
        return (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)

    @property
    def raw_temperature(self):
        data = self._data
        return (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)

    @property
    def raw_humidity(self):
        data = self._data
        return (data[6] << 8) | data[7]

    @property
    def temperature(self):
        temp_raw = self.raw_temperature
        dig_T1, dig_T2, dig_T3 = self._calib[:3]
        var1 = ((((temp_raw >> 3) - (dig_T1 << 1))) * (dig_T2)) >> 11
        var2 = (
            ((((temp_raw >> 4) - (dig_T1)) * ((temp_raw >> 4) - (dig_T1))) >> 12)
            * (dig_T3)
        ) >> 14
        t_fine = var1 + var2
        return float(((t_fine * 5) + 128) >> 8) / 100.0

    def calibration(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CALIB1, self._calib1)
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CALIB2, self._calib2)
        self._calib = ustruct.unpack("HhhHhhhhhhhhBb", self._calib1) + ustruct.unpack(
            "hBbbbB", self._calib2
        )

    def init(self):
        self.reset()
        sleep(0.1)
        self.ctrl_hum()
        self.ctrl_meas()
        self.config()
        self.calibration()


bme280 = BME280(I2C(0, scl=Pin(21), sda=Pin(20)))
print(bme280.chip_id())
bme280.init()

then

bme280.data() refreshes the data and bme280.temperature is a float containing the current temperature.

My temperature calibration code is from MattHawkinsUK/rpispy-misc which is using bit shifting whereas the pimoroni code at pimoroni/bme280-python seems to be doing lots of floating point maths.

I’m confused by the differences between MicroPython and CircuitPython, however, I’m trying to get a BME280 (or 680) to run via a pExplorer Breakout Garden socket. Thus far, I have one running connected using jumper wires and plugged into the green breadboard See photo.

I’m using CircuitPython, purely because I found an example on YouTube Raspberry Pi Pico - BME280 Datalogger with CircuitPython & Mu. (I’d prefer to use Thonny as Mu seems buggy when trying to access the Serial window with the Pico - I presume I have to use Mu with CircuitPython???)

This uses a bme280.mpy library and a bus_device library (both from Adafruit). If I try plugging in the BME280 directly in to a breakout socket, it is not recognised. I haven’t yet got my head round whether the SDA & SCL terminals in the Breakout sockets are hardwired to the SDA & SCL terminals in the middle of the pExplorer…

This is how the BME280 is wired:

# BME280 - GND - RPi PicoExplorer, GND
# BME280 - 3.3 - RPi PicoExplorer, 3v3
# BME280 - SDA - RPi PicoExplorer, GP0
# BME280 - SCL - RPi PicoExplorer, GP1

The script offers the following to define the I2C/SPI connectivity:-

# Create library object using Bus I2C port
#i2c = busio.I2C(board.SCL, board.SDA)
i2c = busio.I2C(board.GP1, board.GP0)
#bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
#or with other sensor address
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)

# OR create library object using our Bus SPI port
#spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
#bme_cs = digitalio.DigitalInOut(board.D10)
#bme280 = adafruit_bme280.Adafruit_BME280_SPI(spi, bme_cs)

(The un-hashed lines of code worked for the way I wired the BME280.)

I tried alternate lines in the code to get the BME280 working from within a Breakout socket but failed…

Seems so near yet so far…

Is this another problem that is occurring because I’m not using the Pimoroni custom UF2 firmware?

I assume the same problem would occur with the forthcoming Pico Breakout Garden Bases (PIM549 etc)?

From right at the bottom of my code there is:

bme280 = BME280(I2C(0, scl=Pin(21), sda=Pin(20)))

So, without knowing about CircuitPython, I would try:

i2c = busio.I2C(board.GP21, board.GP20)
1 Like

OMG I could kiss you - except I don’t kiss dry fish!

Just codding!

I’ll get me coat

You’ve just made buying a pExplorer worthwhile!

Now to display the output on the little screen…

Cheers

PS, I can get REPL no problem if using a Raspberry Pi as a host…

Could you please put up the complete MicroPython code now that you have it working?

Hi Tony, this is the adapted code I used to get two sensors (BME280 & BME680) fitted into the Breakout Garden slots on the Pico Explorer board working and sending data to the serial terminal, please note this is in CircuitPython and adapted from YouTube/cPyPicoBME280.py at master · AnchorageBot/YouTube · GitHub

My version is:-

# This script uses a RPi Pico & BME280 & BME680 to log temp, pressure, & humidity

# Ensure that the following CircuitPython Libraries are loaded on the Pico
    # adafruit_bme280.mpy
    # adafruit_bme680.mpy
    # adafruit_bus_device

import microcontroller
import board
import digitalio
import busio
import time
import adafruit_bme280
import adafruit_bme680

i2c = busio.I2C(board.GP21, board.GP20)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
bme680 = adafruit_bme680.Adafruit_BME680_I2C(i2c, address=0x77)

# change this to match the location's pressure (mb) at sea level
bme280.sea_level_pressure = 1034
bme680.sea_level_pressure = 1034

while True:
    print("\nBME280 Temperature: %0.1f C" % bme280.temperature)
    print("BME280 Humidity: %0.1f %%" % bme280.relative_humidity)
    print("BME280 Pressure: %0.1f mb" % bme280.pressure)
    print("BME280 Altitude = %0.1f metres" % bme280.altitude)

    print("\nBME680 Temperature: %0.1f C" % bme680.temperature)
    print("BME680 Humidity: %0.1f %%" % bme680.relative_humidity)
    print("BME680 Pressure: %0.1f mb" % bme680.pressure)
    print("BME680 Altitude = %0.1f metres" % bme680.altitude)
    time.sleep(10)

I now want to get the data formatted and outputted onto the LCD screen…

Oh! I think I’m going to switch to CircuitPython. Thanks.

I’ve always wanted to do this with Micropython and now that I have found a suitable driver (from Waveshare and their Environmental Sensing module for the Pi Pico) I will have a try to create the Explorer display.
Here is the code I’ve been using:

# BME280 - TPH - Working Original
# Driver from https://www.waveshare.com/wiki/Pico-Environment-Sensor
from machine import Pin, I2C
import time
I2C_ADDR = 0x76

digT = []
digP = []
digH = []

t_fine = 0.0

class BME280:
	def __init__(self, address = I2C_ADDR):
		self.i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=100000)
		self.address = address
		
		self.calib = []
		self.osrs_t = 1			#Temperature oversampling x 1
		self.osrs_p = 1			#Pressure oversampling x 1
		self.osrs_h = 1			#Humidity oversampling x 1
		self.mode   = 3			#Normal self.mode
		self.t_sb   = 5			#Tstandby 1000ms
		self.filter = 0			#self.filter off
		self.spi3w_en = 0			#3-wire SPI Disable

		ctrl_meas_reg = (self.osrs_t << 5) | (self.osrs_p << 2) | self.mode
		config_reg    = (self.t_sb << 5) | (self.filter << 2) | self.spi3w_en
		ctrl_hum_reg  = self.osrs_h

		self.writeReg(0xF2, ctrl_hum_reg)
		self.writeReg(0xF4, ctrl_meas_reg)
		self.writeReg(0xF5, config_reg)
			
	def writeReg(self, cmd, val):
		self.i2c.writeto_mem(int(self.address), int(cmd), bytes([int(val)]))
        
	def get_calib_param(self):
		for i in range (0x88,0x88+24):
			rdate = self.i2c.readfrom_mem(int(self.address), int(i), 1)
			self.calib.append(rdate[0])
		rdate2 = self.i2c.readfrom_mem(int(self.address), int(0xA1), 1)
		self.calib.append(rdate2[0])
		for i in range (0xE1,0xE1+7):
			rdate3 = self.i2c.readfrom_mem(int(self.address), int(i), 1)
			self.calib.append(rdate3[0])

		digT.append((self.calib[1] << 8) | self.calib[0])
		digT.append((self.calib[3] << 8) | self.calib[2])
		digT.append((self.calib[5] << 8) | self.calib[4])
		digP.append((self.calib[7] << 8) | self.calib[6])
		digP.append((self.calib[9] << 8) | self.calib[8])
		digP.append((self.calib[11]<< 8) | self.calib[10])
		digP.append((self.calib[13]<< 8) | self.calib[12])
		digP.append((self.calib[15]<< 8) | self.calib[14])
		digP.append((self.calib[17]<< 8) | self.calib[16])
		digP.append((self.calib[19]<< 8) | self.calib[18])
		digP.append((self.calib[21]<< 8) | self.calib[20])
		digP.append((self.calib[23]<< 8) | self.calib[22])
		digH.append( self.calib[24] )
		digH.append((self.calib[26]<< 8) | self.calib[25])
		digH.append( self.calib[27] )
		digH.append((self.calib[28]<< 4) | (0x0F & self.calib[29]))
		digH.append((self.calib[30]<< 4) | ((self.calib[29] >> 4) & 0x0F))
		digH.append( self.calib[31] )
		
		for i in range(1,2):
			if digT[i] & 0x8000:
				digT[i] = (-digT[i] ^ 0xFFFF) + 1

		for i in range(1,8):
			if digP[i] & 0x8000:
				digP[i] = (-digP[i] ^ 0xFFFF) + 1

		for i in range(0,6):
			if digH[i] & 0x8000:
				digH[i] = (-digH[i] ^ 0xFFFF) + 1  

	def readData(self):
		data = []
		for i in range (0xF7, 0xF7+8):
			rdate = self.i2c.readfrom_mem(int(self.address), int(i), 1)
			data.append(rdate[0])
		pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
		temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
		hum_raw  = (data[6] << 8)  |  data[7]
		
		pressure = self.compensate_P(pres_raw)
		temperature = self.compensate_T(temp_raw)
		var_h = self.compensate_H(hum_raw)
		# print "pressure : %7.2f hPa" % (pressure/100)
		# print "temp : %-6.2f ℃" % (temperature) 
		# print "hum : %6.2f %" % (var_h)
		return [pressure, temperature, var_h]

	def compensate_P(self, adc_P):
		global  t_fine
		pressure = 0.0
		
		v1 = (t_fine / 2.0) - 64000.0
		v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
		v2 = v2 + ((v1 * digP[4]) * 2.0)
		v2 = (v2 / 4.0) + (digP[3] * 65536.0)
		v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
		v1 = ((32768 + v1) * digP[0]) / 32768
		
		if v1 == 0:
			return 0
		pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
		if pressure < 0x80000000:
			pressure = (pressure * 2.0) / v1
		else:
			pressure = (pressure / v1) * 2
		v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
		v2 = ((pressure / 4.0) * digP[7]) / 8192.0
		pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)  
		return (pressure/100)
		# print "pressure : %7.2f hPa" % (pressure/100)

	def compensate_T(self, adc_T):
		global t_fine
		v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
		v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
		t_fine = v1 + v2
		temperature = t_fine / 5120.0
		# print "temp : %-6.2f ℃" % (temperature) 
		return temperature

	def compensate_H(self, adc_H):
		global t_fine
		var_h = t_fine - 76800.0
		if var_h != 0:
			var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)))
		else:
			return 0
		var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)
		if var_h > 100.0:
			var_h = 100.0
		elif var_h < 0.0:
			var_h = 0.0
		# print "hum : %6.2f %" % (var_h)
		return var_h


sensor = BME280()
sensor.get_calib_param()
time.sleep(1)
# sensor.setup()
data = []
data = sensor.readData()
print("pressure : %7.2f hPa" %data[0])
print("temp : %-6.2f ℃" %data[1])
print("hum : %6.2f %" %data[2])

If you are into sensing you may be interested in this:
Pi Pico Environmental Sensing Workout : 10 Steps - Instructables

Have fun

1 Like

I no longer have my Pico Explorer Base, but I do have a Pico Breakout Garden Base, BME280, and that display as a breakout. Running the code on that should be easy peasy with a few edits to remove what i don’t have. I do have an LTR-599 and VEML6075 though. Just have to see if they are both in Pimoroni’s uf2. The BME280 is now in there, and that display, so I’ll likely start there.
I’ll post back how it goes for me, thanks @Tonygo2.

@dryfish I don’t know if this was mentioned or not, but you can just plug your BME280 breakout into Breakout 1 or Breakout 2 slots. And skip the extender and jumpers. Those teo sockets are hardwired to i2c on the PICO. The pinout shows you what pins are used, and are the same ones used by the header you have yours wired too.

The Pimoroni BME280 breakout is now supported in Micro Python via the Pimoroni uf2 image.
The following worked on my PICO Breakout Garden

import time
from breakout_bme280 import BreakoutBME280
from pimoroni_i2c import PimoroniI2C

PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5}
PINS_PICO_EXPLORER = {"sda": 20, "scl": 21}

i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
bme = BreakoutBME280(i2c)

while True:
    reading = bme.read()
    print(reading)
    time.sleep(1.0)

To use it on the PICO Explorer change
i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
to
i2c = PimoroniI2C(**PINS_PICO_EXPLORER)