MicroPython Driver for BME280 Sensor

BME280 Driver Code for micro:bit

Download as zip file

'''
BME280 Temperature and Barometric Pressure sensor
MicroPython driver for micro:bit

This is a major revision of Ver 1.00

EXAMPLE USAGE:
from fc_bme280 import *
sensor = BME280()
Temperature = sensor.T
Pressure = sensor.P
Humidity = sensor.RH

Modes:
0 : Ultra low power
1 : Low power
2 : Standard (default)
3 : High resolution
4 : Ultra high resolution

Temperature returned in Celsius.
Pressure returned in hPa.
Relative Humidity returned in %RH

AUTHOR: fredscave.com
DATE  : 2025/06
VERSION : 2.00
'''

from microbit import *
from micropython import const

_REG_MEASURE = const(0xF7)
_REG_CTRL_MEAS = const(0xF4)
_REG_CTRL_HUM  = const(0xF2)
_REG_CONFIG = const(0xF5)
_REG_ID = const(0xD0)
_REG_RESET = const(0xE0)

_CMD_RESET = const(0xB6)

# Force measurement configuration per mode.
CTRL_HUM = (0x01, 0x02, 0x03, 0x04, 0x05)
CTRL_MEAS = (0x25, 0x29, 0x2D, 0x31, 0x55)

# IIR filter coefficient per mode.
CONFIG = (0x00, 0x04, 0x08, 0x0C, 0x10)

# Sample conversion time (ms) per mode.
ADCT = (10, 15, 25, 43, 80)

# Convert Celsius to Fahrenheit
CtoF = lambda C, d=2: round((C * 9/5) +32, d)

class BME280():
    def __init__(self, ADDR=0x76):
        self.ADDR = ADDR
        self.SetMode()
        self._Load_Calibration_Data()

    # Mode 0 : Ultra low power
    # Mode 1 : Low power
    # Mode 2 : Standard (default)
    # Mode 3 : High resolution
    # Mode 4 : Ultra high resolution
    def SetMode(self, Mode=2):
        if Mode in range(0, 5):
           self.Mode = Mode
        else:
           self.Mode = 2
        self.Reset()

    # Performs a soft reset.
    # All registers are loaded with power-on values.
    # The selected sampling mode is reconfigured.
    def Reset(self):
        self._writeReg([_REG_RESET, _CMD_RESET])
        sleep(20)
        self._writeReg([_REG_CONFIG, CONFIG[self.Mode]])

# *******************************************
#              Properties
# *******************************************

    # Trigger temperature, pressure, humidity measurements.
    # Read uncompensated data and do the compensation
    # calculations.
    @property
    def Reading(self):
        # Read uncompensated temperature, pressure and humidity.
        self._writeReg([_REG_CTRL_HUM, CTRL_HUM[self.Mode]])
        self._writeReg([_REG_CTRL_MEAS, CTRL_MEAS[self.Mode]])
        sleep(ADCT[self.Mode])
        buf = self._readReg(_REG_MEASURE, 8)
        adc_P = (buf[0] << 12) + (buf[1] << 4) + (buf[2] >> 4)
        adc_T = (buf[3] << 12) + (buf[4] << 4) + (buf[5] >> 4)
        adc_H = (buf[6] << 8) + buf[7]
        #Convert to actual temperature, pressure and humidity.
        return self._Compensate(adc_T, adc_P, adc_H)

   # Returns temperature
    @property
    def T(self):
        temp = self.Reading
        return temp[0]

    # Returns pressure
    @property
    def P(self):
        pressure = self.Reading
        return pressure[1]
        
    # Return relative humidity
    @property
    def RH(self):
        rh = self.Reading
        return(rh[2])

    # Returns Mode (0..4)
    @property
    def GetMode(self):
        return self.Mode

    # Returns the chip's ID
    @property
    def ID(self):
        id = self._readReg(_REG_ID, 1)
        return hex(id[0])

# *******************************************
#             Altitude Calculations
# *******************************************

    # Calculate mean sea level pressure (MSLP)
    # If the altitude of the sensor is known then
    # the absolute pressure can be adjusted to its
    # equivalent sea level pressure.
    #
    # This is the pressure that is reported by
    # official weather services.
    def MSLP(self, Altitude=None):
        if Altitude == None:
            return None
        else:
            P0 = 1013.25
            a = 2.25577E-5
            b = 5.25588
            P = self.Reading[1]
            PS = P0 * (1 - a * Altitude) ** b
            offset = P0 - PS
            return P + offset

    # If pressure readings are taken at different
    # altitudes then this method will calculate
    # the difference between these two altitudes
    # in meters.
    def AltDiff(self, P1, P2):
        a = 0.1157227
        return (P1 - P2) / a

    # If the Mean Sea Level pressure is known
    # (usually obtained from the local weather service)
    # then this method will calculate the altitude
    # of the sensor in meters.
    # The sensor is read to obtain the absolute
    # pressure value.
    def Altitude(self, MSLP=None):
        if MSLP == None:
            return None
        else:
            p = self.P
            a = -2.25577E-5
            b = 0.1902631
            h = (((p/MSLP) ** b) - 1) / a
            return int(round(h, 0))

# *******************************************
#              Private Methods
# *******************************************

    # Get the trimming constants from NVM.
    def _Load_Calibration_Data(self):
        self.dig_T1 = self._getCal(0x88, 0)
        self.dig_T2 = self._getCal(0x8A, 1)
        self.dig_T3 = self._getCal(0x8C, 1)
        self.dig_P1 = self._getCal(0x8E, 0)
        self.dig_P2 = self._getCal(0x90, 1)
        self.dig_P3 = self._getCal(0x92, 1)
        self.dig_P4  = self._getCal(0x94, 1)
        self.dig_P5  = self._getCal(0x96, 1)
        self.dig_P6  = self._getCal(0x98, 1)
        self.dig_P7  = self._getCal(0x9A, 1)
        self.dig_P8  = self._getCal(0x9C, 1)
        self.dig_P9  = self._getCal(0x9E, 1)
        self.dig_H1  = self._readReg(0xA1, 1)[0]
        self.dig_H2  = self._getCal(0xE1, 1)
        self.dig_H3  = self._readReg(0xE3, 1)[0]
        E5 = self._readReg(0xE5, 1)[0]
        self.dig_H4  = (self._readReg(0xE4, 1)[0] << 4) + (E5 % 16)
        self.dig_H5  = (self._readReg(0xE6, 1)[0] << 4) + (E5 >> 4)
        self.dig_H6  = self._readReg(0xE7, 1)[0]
        if self.dig_H6 > 127:
            self.dig_H6 -= 256

    # Does the grunt work retrieving the
    # trimming constants from the device's
    # non-volatile memory (NVM).
    def _getCal(self, Reg, Signed):
        buf = self._readReg(Reg, 2)
        d = buf[1]*256 + buf[0]
        if Signed == 1:
            if d > 32767:
                return d - 65536
            else:
                return d
        else:
            return d

    # Convert uncompensated temperature and pressure
    # to actual (compensated) values.
    def _Compensate(self, adc_T, adc_P, adc_H):
        # Calculated actual temperature.
        var1 = (((adc_T>>3)-(self.dig_T1<<1))*self.dig_T2)>>11
        var2 = (((((adc_T>>4)-self.dig_T1)*((adc_T>>4) - self.dig_T1))>>12)*self.dig_T3)>>14
        t_fine = var1 + var2
        T = ((t_fine * 5 + 128) >> 8)/100
        # Calculate actual pressure.
        var1 = (t_fine >> 1) - 64000
        var2 = (((var1 >> 2) * (var1 >> 2)) >> 11 ) * self.dig_P6
        var2 = var2 + ((var1 * self.dig_P5) << 1)
        var2 = (var2 >> 2)+(self.dig_P4 << 16)
        var1 = (((self.dig_P3*((var1>>2)*(var1>>2))>>13)>>3) + (((self.dig_P2) * var1)>>1))>>18
        var1 = ((32768 + var1) * self.dig_P1) >> 15
        if var1 == 0:
            return  # avoid exception caused by division by zero.
        p = ((1048576 - adc_P) - (var2 >> 12)) * 3125
        if p < 0x80000000:
            p = (p << 1) // var1
        else:
            p = (p // var1) * 2
        var1 = (self.dig_P9 * (((p >> 3) * (p >> 3)) >> 13)) >> 12
        var2 = (((p >> 2)) * self.dig_P8) >> 13
        P = p + ((var1 + var2 + self.dig_P7) >> 4)
        # Calculate actual Humidity
        var1 = t_fine - 76800
        var2 = (((adc_H << 14) - (self.dig_H4 << 20) - (self.dig_H5 * var1)) + 16384) >> 15
        var1 = var2*(((((((var1*self.dig_H6)>>10)*(((var1*self.dig_H3)>>11)+32768))>>10)+2097152)*self.dig_H2+8192)>>14)
        var2 = var1-(((((var1>>15) * (var1>>15))>>7) * self.dig_H1)>>4)
        if var2 < 0:
            var2 = 0
        if var2 > 419430400:
            var2 = 419430400
        H = (var2 >> 12) / 1024
        return [T, P/100, H]        

    # Writes one or more bytes to register.
    # Bytes is expected to be a list.
    # First element is the register address.
    def _writeReg(self, Bytes):
        i2c.write(self.ADDR, bytes(Bytes))

    # Read a given number of bytes from
    # a register.
    def _readReg(self, Reg, Num):
        self._writeReg([Reg])
        buf = i2c.read(self.ADDR, Num)
        return buf
          

Using BMP280 Sensors With the BME280 Driver

The BME280 sensor is backwardly compatible with the BMP280 sensor.

Additionally, the BME280 driver can be used with the BMP280 and will return the correct temperature and pressure. Attempting to read humidity however will return a 0.0 value as the BMP280 doesn't have a relative humidity sensor.

This final example will connect a BMP280 and a BME280 to the micro:bit's I2C bus. The BME280 driver will be used.

Hookup guide

Make the following connections:

  • micro:bit 3.3V to VCC pins on both boards.
  • micro:bit GND to GND pins on both boards.
  • micro:bit Pin 19 to SCL pins on both boards.
  • micro:bit pin 20 to SDA pins on both board.
  • On the BMP280 board, connect the VCC pin to the SDO pin. This is the white wire in the image below.

The  fc_bme280.py driver file must also be on the micro:bit's file system.

BME280 and BMP280 breakout boards connected to the micro:bit's I2C bus
BME280 breakout board (left) and BMP280 breakout board (right) connected to the micro:bit's I2C bus
The Code:

# Demonstrate that the BMP280 sensor
# works correctly with the BME280
# MicroPython driver on the micro:bit.
# It is fine to read temperature and
# pressure but mustn't attempt to
# read humidity.

# A BMP280 and a BME280 will be connected
# to the micro:bit's I2C bus.

# BME280 address is 0x76
# BMP280 address is 0x77
# (Jumper between SDO and VCC pins)

from fc_bme280 import *

print('          T         P         MSLP        RH')

bme280 = BME280(ADDR=0x76)
bmp280 = BME280(ADDR=0x77)
altitude = 400 # Metres

# Read BME280 and report values
buf = bme280.Reading
T = "{:.2f}".format(buf[0])
P = "{:.4f}".format(buf[1])
mslp = "{:.3f}".format(bme280.MSLP(altitude))
H = "{:.4f}".format(buf[2])
print( 'BME280 ', T, '  ', P, ' ', mslp, '  ', H)

# Read BMP280 and report values
buf = bmp280.Reading
T = "{:.2f}".format(buf[0])
P = "{:.4f}".format(buf[1])
mslp = "{:.3f}".format(bmp280.MSLP(altitude))
print( 'BMP280 ', T, '  ', P, ' ', mslp, '      -')
          

Typical Output:


           T         P         MSLP        RH
BME280  20.00    974.3400   1021.469    43.6738
BMP280  21.78    972.9099   1020.039       -
          
Wrap up

The local weather service's published barometric pressure observation was 1020 hPa (and steady). There was some difference in temperature but (somewhat) good agreement between pressure with both sensors. The BMP280 and BME280 are both reasonable sensors especially considering their low cost.