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.