MicroPython Driver for BMP280 Sensor

BMP280 Driver Code for micro:bit

Download as zip file

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

This is a major revision of Ver 1.00

EXAMPLE USAGE:
from fc_bmp280 import *
sensor = BMP280()
Temperature = sensor.T
Pressure = sensor.P

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.

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_CONFIG = const(0xF5)
_REG_ID = const(0xD0)
_REG_RESET = const(0xE0)

_CMD_RESET = const(0xB6)

# Force measurement configuration per mode.
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 = (8, 10, 15, 24, 45)

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

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

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

    # Returns pressure
    @property
    def P(self):
        pressure = self.Reading
        return pressure[1]

    # 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)

    # 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):
        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)
        return [T, P/100]

    # 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 Two BMP280 Sensors

It's easy to use two BMP280 simultaneously on the same I2C bus.

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 one only board, connect the VCC pin to the SDO pin. This is the white wire in the image below.

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

Two BMP280 breakout boards connected to the micro:bit's I2C bus
Two BMP280 breakout boards connected to the micro:bit's I2C bus
The Code:

# Test MicroPython driver for the BMP280
# temperature and barometric pressure sensor.

# Two BMP280 sensors will be used on the
# same I2C bus.

# Sensor1:
#   Address = 0x76
# Sensor2:
#   Address = 0x77
#   SDO is tied to VCC.

from fc_bmp280 import *

# Create 2 x BMP80 sensor objects.
Sensor1 = BMP280()
Sensor1.SetMode(4)
Sensor2 = BMP280(ADDR=0x77)
Sensor2.SetMode(4)

# Read temperature and pressure
# from both sensors.
T1, P1 = Sensor1.T, Sensor1.P
altitude = 400 # Metres
MSLP1 = Sensor1.MSLP(altitude)
T2, P2 = Sensor2.T, Sensor2.P
MSLP2 = Sensor2.MSLP(altitude)

# Report
print('             T        P          MSLP')
print('Sensor 1', '  ', T1, '  ', P1, '  ', MSLP1)
print('Sensor 2', '  ', T2, '  ', P2, '  ', MSLP2)
          

Typical Output:


             T        P          MSLP
Sensor 1    19.68    975.09    1022.239
Sensor 2    19.98    976.88    1024.009
          

The local weather service's published barometric pressure observation was 1023 hPa.