BME280 Class (Solution)
BME280.py
from machine import I2C, Pin
from struct import unpack
from time import sleep
# Define the following constants to access the registers in the BME280 chip
# Using the const expression saves memory in the microcontroller
# The meaning of the various registers is explained in the data sheet.
BME_ADR = const(0x77) # This is the I2C address of the chip
BME_REG_CHIP_ID = const(0xD0) # This register holds an identification code (0x60)
BME_REG_RESET = const(0xE0)
# Registers to define details on how the measurements should be performed and
# registers to trigger the actual measurement.
BME_REG_CTRL_HUM = const(0xF2) # 5 : oversampling 16
BME_REG_STATUS = const(0xF3)
BME_REG_CTRL_MEAS = const(0xF4)
BME_REG_CTRL_CONFIG = const(0xF5)
# Registers which hold the result of a measurement:
BME_REG_PRESS = const(0xF7) # adr F7 ... F9 are 20 bits pressure (big endian)
# F9 bits 4..7 are the most significant 4 bits)
BME_REG_TEMP = const(0xFA) # adr FA ... FC contains the 20 bits for temperature
BME_REG_HUM = const(0xFD) # adr FD and FE contain 16 bits of Humidity
##########################################################################
class BME280 :
##########################################################################
def __init__( self, i2c ):
self.i2c = i2c
self.readCalib()
self.initSensor()
# Initialize the sensor for single measurements between sleeps.
# The maximum amount of oversampling is requested to achieve maximal precision.
def initSensor( self ):
# Set the oversampling of the humidity measurement to 16.
# The data sheet states that this has to be done before writing
# to the CTRL_MEAS register.
self.i2c.writeto_mem( BME_ADR, BME_REG_CTRL_HUM, b'\x05' )
# Set the chip to sleep mode
# Set the pressure oversampling to 16
# Set the temerature oversampling to 16
self.i2c.writeto_mem( BME_ADR, BME_REG_CTRL_MEAS, b'\xB4' )
# Calibration data needs to be read out of the chip (see below the
# function "readCalib"). What follows here is a set of helper functions
# which read single calibration constants our of the memory of the
# sensor chip. The format of the the constants can be different
# (signed or unsigned short (2 byte values) or signed or unsigned char
# (one byte values)). There is a dedicated function for all of these four
# types which converts the read bytes into the appropriate python type.
#
# The readfrom_mem function reads a number of bytes via i2c into a
# python "bytes" object. Essentially this is an array of single bytes.
# To turn a bytes object into a python number the struct.unpack function
# is used. It is documented in the python documentation:
# https://docs.python.org/3.5/library/struct.html?highlight=unpack#struct.unpack
# If you do not understand how the unpack works, please ask !!!
#
def _readSignedShort( self, adr ):
tmp = self.i2c.readfrom_mem( BME_ADR, adr, 2 )
return unpack('<h', tmp)[0]
def _readUnsignedShort( self, adr ):
tmp = self.i2c.readfrom_mem( BME_ADR, adr, 2 )
return unpack('<H',tmp)[0]
def _readUnsignedChar( self, adr ):
tmp = self.i2c.readfrom_mem(BME_ADR, adr, 1)
return unpack('<B',tmp)[0]
def _readSignedChar( self, adr ):
tmp = self.i2c.readfrom_mem(BME_ADR, adr, 1)
return unpack('<b',tmp)[0]
# Read the calibration data which has been programmed into the chip.
# Due to production tolerances not every sensor gives exactly the same
# value at a given temperature/pressure/humidity. At the factory every
# sensor is calibrated and the calibration constants are programmed
# into the chip (they cannot be changed afterwards). We read out these
# constants here, since we need them to calculate calibrated
# (i.e. 'correct') sensor values.
#
def readCalib( self ):
calib={}
calib['T1'] = self._readSignedShort( 0x88 )
calib['T2'] = self._readSignedShort( 0x8A )
calib['T3'] = self._readSignedShort( 0x8C )
calib['P1'] = self._readUnsignedShort( 0x8E )
calib['P2'] = self._readSignedShort( 0x90 )
calib['P3'] = self._readSignedShort( 0x92 )
calib['P4'] = self._readSignedShort( 0x94 )
calib['P5'] = self._readSignedShort( 0x96 )
calib['P6'] = self._readSignedShort( 0x98 )
calib['P7'] = self._readSignedShort( 0x9A )
calib['P8'] = self._readSignedShort( 0x9C )
calib['P9'] = self._readSignedShort( 0x9E )
calib['H1'] = self._readUnsignedChar( 0xA1 )
calib['H2'] = self._readSignedShort( 0xE1 )
calib['H3'] = self._readUnsignedChar( 0xE3 )
# The following two constants need extra treatment.
# For some (not obvious) reason, the chip producer decided
# to pack the following 2 values into three bytes of which
# one of the bytes contains bits belonging to both fo the
# constants. Hence some fiddling around with the bits isint(
# needed in order to extract the two callibration values:
tmp = self.i2c.readfrom_mem( BME_ADR, 0xE4, 2 )
calib['H4'] = (int(tmp[0])<<4) + (int(tmp[1])&0x0f)
tmp = self.i2c.readfrom_mem( BME_ADR, 0xE5, 2 )
calib['H5'] = (int(tmp[0]) & 0xF0 ) >> 4 + (int(tmp[1]) << 4 )
calib['H6'] = self._readSignedChar( 0xE7 )
self.calib = calib
# The formulas of the following calculations come from the data sheet.
#
# Calculate the Temperature with help of the calibration data
def calcTemp( self, adc_T ):
var1 = ( ( ( (adc_T>>3) - (self.calib['T1']<<1) ) * self.calib['T2'] ) ) >> 11
var2 = (((((adc_T>>4) - (self.calib['T1'])) * ((adc_T>>4) - (self.calib['T1']))) >> 12) * (self.calib['T3'])) >> 14
t_fine = var1 + var2
T = (t_fine * 5 + 128) >> 8
self.t_fine = t_fine
return T
# Calculate the pressure with help of the calibration data
# This calculation includes a temperature correction.
def calcPress( self, adc_P ):
# uses the "alternate" formula of the datasheet since we do not have 64bit integers in
# micropython.
var1 = (self.t_fine >> 1) - 64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * self.calib['P6']
var2 = var2 + ((var1 * (self.calib['P5'])) << 1 )
var2 = (var2 >> 2) + ((self.calib['P4']) << 16 )
var1 = (((self.calib['P3'] * (((var1>>2) * (var1>>2) >> 13 )) >> 3) +
(( self.calib['P2']) * var1) >> 1)) >> 18
var1 = ((((32768+var1)) * (self.calib['P1'])) >> 15)
if var1 == 0 :
return 0
p = ((((1048576)-adc_P)-(var2>>12)))*3125
# convert to unsigned int:
if p < 0:
p = p + (1<<32)
if p < 0x80000000:
p = int((p<<1) / var1)
else:
p = ((p / var1) * 2)
if p < 0:
p = p + (1<<32)
var1 = ((self.calib['P9'] * (((p>>3) * (p>>3)) >> 13 ))) >> 12
var2 = (((p>>2)) * (self.calib['P8'])) >> 13
p = (p + ((var1 + var2 + self.calib['P7']) >> 4))
return p/100.0
def calcHum(self, adc_H):
h = self.t_fine - 76800
h = (((((adc_H << 14) - (self.calib['H4'] << 20) - (self.calib['H5'] * h)) + 16384) >> 15) *
(((((((h * self.calib['H6']) >> 10) * (((h * self.calib['H3']) >> 11) + 32768)) >> 10) + 2097152) *
self.calib['H2'] + 8192) >> 14))
h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.calib['H1']) >> 4)
h = 0 if h < 0 else h
h = 419430400 if h > 419430400 else h
return h >> 12
# Do the measurements of Temperature, Pressure and Humidity
def doMeasure( self ):
# Leave the oversampling values as defined in the init routine but wake up the chip into "forced mode".
# This means the chip is exactly performing one measurement and then returns to sleep mode.
self.i2c.writeto_mem( BME_ADR, BME_REG_CTRL_MEAS, b'\xB5' )
# Here we wait until the measurement is done.
# The bit 3 is '1' when a measurement is ongoing. It goes to '0' once the
# measurement is completed and results are ready for reading out.
# What we program here is called a "polling loop": we read a value over and
# over again and wait until it's value changes to the expected value. Then
# we leave the loop.
measuring = 8
while measuring == 8:
sleep(0.1)
measuring = int(self.i2c.readfrom_mem( BME_ADR, BME_REG_STATUS, 1 )[0]) & 8
# Now we read out the measurements. The values are raw values which need to
# be transformed via formulas into Temperature, Pressure and Humidity values.
# The formulas involve calibration constants. In addition the raw measurement
# value depend on each other (i.e. the raw values for humidity and pressure
# are temperature dependent. This dependency is known and worked into to the
# forumalas for the calculation.
# read temperature
temp = self.i2c.readfrom_mem( BME_ADR, BME_REG_TEMP, 3 )
T = int(temp[0]<<12)+int(temp[1]<<4)+int(temp[2]>>4)
self.lastT = self.calcTemp( T ) / 100.
# read pressure
press = self.i2c.readfrom_mem( BME_ADR, BME_REG_PRESS, 3 )
P = int(press[0]<<12)+int(press[1]<<4)+int(press[2]>>4)
self.lastP = self.calcPress( P )
# read humidity
hum = self.i2c.readfrom_mem( BME_ADR, BME_REG_HUM, 2 )
H = int(hum[0]<<8)+int(hum[1])
self.lastH = self.calcHum( H ) / 1024.
return( self.lastT, self.lastP, self.lastH )
def getAltitude( self ):
# 1013.24 is the reference pressure at sealevel
# This formula is an approximation, of course. But it is useful
# to calculate altitude differences (e.g. in a model airplane).
# You should be able to see altitude differences when holding the
# Sensor at different heights (1-2 meters of difference should be
# visible. To make it more obvious sample over multiple measurements
# and take the mean
return ( 44330 * ( 1.0 - (self.lastP / 1013.25 )**(1.0 / 5.255)) )
def dumpLastMeasurement( self ):
print( "Temperature : %7.2f C" % self.lastT )
print( "Pressure : %7.2f mb" % self.lastP )
print( "Humidity : %7.2f %%" % self.lastH )
print()
#############################################################################