From 512896532e5d33797c4db17e53858fe91eeaca68 Mon Sep 17 00:00:00 2001 From: Blaise Thompson Date: Mon, 5 Apr 2021 17:59:25 -0500 Subject: bme280 --- firmware/bme280.py | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++ firmware/bme680.py | 229 ------------------------------------------ firmware/flash.sh | 6 +- firmware/main.py | 28 ++---- 4 files changed, 298 insertions(+), 251 deletions(-) create mode 100644 firmware/bme280.py delete mode 100644 firmware/bme680.py diff --git a/firmware/bme280.py b/firmware/bme280.py new file mode 100644 index 0000000..7f9c522 --- /dev/null +++ b/firmware/bme280.py @@ -0,0 +1,286 @@ +from machine import I2C +import time + +# BME280 default address. +BME280_I2CADDR = 0x77 + +# Operating Modes +BME280_OSAMPLE_1 = 1 +BME280_OSAMPLE_2 = 2 +BME280_OSAMPLE_4 = 3 +BME280_OSAMPLE_8 = 4 +BME280_OSAMPLE_16 = 5 + +# BME280 Registers + +BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers +BME280_REGISTER_DIG_T2 = 0x8A +BME280_REGISTER_DIG_T3 = 0x8C + +BME280_REGISTER_DIG_P1 = 0x8E +BME280_REGISTER_DIG_P2 = 0x90 +BME280_REGISTER_DIG_P3 = 0x92 +BME280_REGISTER_DIG_P4 = 0x94 +BME280_REGISTER_DIG_P5 = 0x96 +BME280_REGISTER_DIG_P6 = 0x98 +BME280_REGISTER_DIG_P7 = 0x9A +BME280_REGISTER_DIG_P8 = 0x9C +BME280_REGISTER_DIG_P9 = 0x9E + +BME280_REGISTER_DIG_H1 = 0xA1 +BME280_REGISTER_DIG_H2 = 0xE1 +BME280_REGISTER_DIG_H3 = 0xE3 +BME280_REGISTER_DIG_H4 = 0xE4 +BME280_REGISTER_DIG_H5 = 0xE5 +BME280_REGISTER_DIG_H6 = 0xE6 +BME280_REGISTER_DIG_H7 = 0xE7 + +BME280_REGISTER_CHIPID = 0xD0 +BME280_REGISTER_VERSION = 0xD1 +BME280_REGISTER_SOFTRESET = 0xE0 + +BME280_REGISTER_CONTROL_HUM = 0xF2 +BME280_REGISTER_CONTROL = 0xF4 +BME280_REGISTER_CONFIG = 0xF5 +BME280_REGISTER_PRESSURE_DATA = 0xF7 +BME280_REGISTER_TEMP_DATA = 0xFA +BME280_REGISTER_HUMIDITY_DATA = 0xFD + + +class Device: + """Class for communicating with an I2C device. + + Allows reading and writing 8-bit, 16-bit, and byte array values to + registers on the device.""" + + def __init__(self, address, i2c): + """Create an instance of the I2C device at the specified address using + the specified I2C interface object.""" + self._address = address + self._i2c = i2c + + def writeRaw8(self, value): + """Write an 8-bit value on the bus (without register).""" + value = value & 0xFF + self._i2c.writeto(self._address, value) + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + b=bytearray(1) + b[0]=value & 0xFF + self._i2c.writeto_mem(self._address, register, b) + + def write16(self, register, value): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + b=bytearray(2) + b[0]= value & 0xFF + b[1]= (value>>8) & 0xFF + self.i2c.writeto_mem(self._address, register, value) + + def readRaw8(self): + """Read an 8-bit value on the bus (without register).""" + return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + return int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF + + def readS8(self, register): + """Read a signed byte from the specified register.""" + result = self.readU8(register) + if result > 127: + result -= 256 + return result + + def readU16(self, register, little_endian=True): + """Read an unsigned 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF + if not little_endian: + result = ((result << 8) & 0xFF00) + (result >> 8) + return result + + def readS16(self, register, little_endian=True): + """Read a signed 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self.readU16(register, little_endian) + if result > 32767: + result -= 65536 + return result + + def readU16LE(self, register): + """Read an unsigned 16-bit value from the specified register, in little + endian byte order.""" + return self.readU16(register, little_endian=True) + + def readU16BE(self, register): + """Read an unsigned 16-bit value from the specified register, in big + endian byte order.""" + return self.readU16(register, little_endian=False) + + def readS16LE(self, register): + """Read a signed 16-bit value from the specified register, in little + endian byte order.""" + return self.readS16(register, little_endian=True) + + def readS16BE(self, register): + """Read a signed 16-bit value from the specified register, in big + endian byte order.""" + return self.readS16(register, little_endian=False) + + +class BME280: + def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None, + **kwargs): + # Check that mode is valid. + if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4, + BME280_OSAMPLE_8, BME280_OSAMPLE_16]: + raise ValueError( + 'Unexpected mode value {0}. Set mode to one of ' + 'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or ' + 'BME280_ULTRAHIGHRES'.format(mode)) + self._mode = mode + # Create I2C device. + if i2c is None: + raise ValueError('An I2C object is required.') + self._device = Device(address, i2c) + # Load calibration values. + self._load_calibration() + self._device.write8(BME280_REGISTER_CONTROL, 0x3F) + self.t_fine = 0 + + def _load_calibration(self): + + self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1) + self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2) + self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3) + + self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1) + self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2) + self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3) + self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4) + self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5) + self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6) + self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7) + self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8) + self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9) + + self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1) + self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2) + self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3) + self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7) + + h4 = self._device.readS8(BME280_REGISTER_DIG_H4) + h4 = (h4 << 24) >> 20 + self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F) + + h5 = self._device.readS8(BME280_REGISTER_DIG_H6) + h5 = (h5 << 24) >> 20 + self.dig_H5 = h5 | ( + self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F) + + def read_raw_temp(self): + """Reads the raw (uncompensated) temperature from the sensor.""" + meas = self._mode + self._device.write8(BME280_REGISTER_CONTROL_HUM, meas) + meas = self._mode << 5 | self._mode << 2 | 1 + self._device.write8(BME280_REGISTER_CONTROL, meas) + sleep_time = 1250 + 2300 * (1 << self._mode) + + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + time.sleep_us(sleep_time) # Wait the required time + msb = self._device.readU8(BME280_REGISTER_TEMP_DATA) + lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_pressure(self): + """Reads the raw (uncompensated) pressure level from the sensor.""" + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA) + lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_humidity(self): + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA) + lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1) + raw = (msb << 8) | lsb + return raw + + def read_temperature(self): + """Get the compensated temperature in 0.01 of a degree celsius.""" + adc = self.read_raw_temp() + var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11) + var2 = (( + (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) * + self.dig_T3) >> 14 + self.t_fine = var1 + var2 + return (self.t_fine * 5 + 128) >> 8 + + def read_pressure(self): + """Gets the compensated pressure in Pascals.""" + adc = self.read_raw_pressure() + var1 = self.t_fine - 128000 + var2 = var1 * var1 * self.dig_P6 + var2 = var2 + ((var1 * self.dig_P5) << 17) + var2 = var2 + (self.dig_P4 << 35) + var1 = (((var1 * var1 * self.dig_P3) >> 8) + + ((var1 * self.dig_P2) >> 12)) + var1 = (((1 << 47) + var1) * self.dig_P1) >> 33 + if var1 == 0: + return 0 + p = 1048576 - adc + p = (((p << 31) - var2) * 3125) // var1 + var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 + var2 = (self.dig_P8 * p) >> 19 + return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) + + def read_humidity(self): + adc = self.read_raw_humidity() + # print 'Raw humidity = {0:d}'.format (adc) + h = self.t_fine - 76800 + h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) + + 16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h * + self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) * + self.dig_H2 + 8192) >> 14)) + h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4) + h = 0 if h < 0 else h + h = 419430400 if h > 419430400 else h + return h >> 12 + + @property + def temperature(self): + "Return the temperature in degrees." + t = self.read_temperature() + ti = t // 100 + td = t - ti * 100 + return "{}.{:02d}".format(ti, td) + + @property + def pressure(self): + "Return the temperature in Pa." + p = self.read_pressure() // 256 + p *= 1000 # uPa to Pa + pi = p // 100 + pd = p - pi * 100 + return "{}.{:02d}".format(pi, pd) + + @property + def humidity(self): + "Return the humidity in percent." + h = self.read_humidity() + hi = h // 1024 + hd = h * 100 // 1024 - hi * 100 + return "{}.{:02d}".format(hi, hd) diff --git a/firmware/bme680.py b/firmware/bme680.py deleted file mode 100644 index 3dfe753..0000000 --- a/firmware/bme680.py +++ /dev/null @@ -1,229 +0,0 @@ -# Spaces, comments and some functions have been removed from the original file to save memory -# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py -import time -import math -from micropython import const -from ubinascii import hexlify as hex -try: - import struct -except ImportError: - import ustruct as struct -_BME680_CHIPID = const(0x61) -_BME680_REG_CHIPID = const(0xD0) -_BME680_BME680_COEFF_ADDR1 = const(0x89) -_BME680_BME680_COEFF_ADDR2 = const(0xE1) -_BME680_BME680_RES_HEAT_0 = const(0x5A) -_BME680_BME680_GAS_WAIT_0 = const(0x64) -_BME680_REG_SOFTRESET = const(0xE0) -_BME680_REG_CTRL_GAS = const(0x71) -_BME680_REG_CTRL_HUM = const(0x72) -_BME280_REG_STATUS = const(0xF3) -_BME680_REG_CTRL_MEAS = const(0x74) -_BME680_REG_CONFIG = const(0x75) -_BME680_REG_PAGE_SELECT = const(0x73) -_BME680_REG_MEAS_STATUS = const(0x1D) -_BME680_REG_PDATA = const(0x1F) -_BME680_REG_TDATA = const(0x22) -_BME680_REG_HDATA = const(0x25) -_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16) -_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127) -_BME680_RUNGAS = const(0x10) -_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, - 2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0, - 2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0, - 2147483647.0) -_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0, - 64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0, - 500000.0, 250000.0, 125000.0) -def _read24(arr): - ret = 0.0 - for b in arr: - ret *= 256.0 - ret += float(b & 0xFF) - return ret -class Adafruit_BME680: - def __init__(self, *, refresh_rate=10): - self._write(_BME680_REG_SOFTRESET, [0xB6]) - time.sleep(0.005) - chip_id = self._read_byte(_BME680_REG_CHIPID) - if chip_id != _BME680_CHIPID: - raise RuntimeError('Failed 0x%x' % chip_id) - self._read_calibration() - self._write(_BME680_BME680_RES_HEAT_0, [0x73]) - self._write(_BME680_BME680_GAS_WAIT_0, [0x65]) - self.sea_level_pressure = 1013.25 - self._pressure_oversample = 0b011 - self._temp_oversample = 0b100 - self._humidity_oversample = 0b010 - self._filter = 0b010 - self._adc_pres = None - self._adc_temp = None - self._adc_hum = None - self._adc_gas = None - self._gas_range = None - self._t_fine = None - self._last_reading = 0 - self._min_refresh_time = 1000 / refresh_rate - @property - def pressure_oversample(self): - return _BME680_SAMPLERATES[self._pressure_oversample] - @pressure_oversample.setter - def pressure_oversample(self, sample_rate): - if sample_rate in _BME680_SAMPLERATES: - self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate) - else: - raise RuntimeError("Invalid") - @property - def humidity_oversample(self): - return _BME680_SAMPLERATES[self._humidity_oversample] - @humidity_oversample.setter - def humidity_oversample(self, sample_rate): - if sample_rate in _BME680_SAMPLERATES: - self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate) - else: - raise RuntimeError("Invalid") - @property - def temperature_oversample(self): - return _BME680_SAMPLERATES[self._temp_oversample] - @temperature_oversample.setter - def temperature_oversample(self, sample_rate): - if sample_rate in _BME680_SAMPLERATES: - self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate) - else: - raise RuntimeError("Invalid") - @property - def filter_size(self): - return _BME680_FILTERSIZES[self._filter] - @filter_size.setter - def filter_size(self, size): - if size in _BME680_FILTERSIZES: - self._filter = _BME680_FILTERSIZES[size] - else: - raise RuntimeError("Invalid") - @property - def temperature(self): - self._perform_reading() - calc_temp = (((self._t_fine * 5) + 128) / 256) - return calc_temp / 100 - @property - def pressure(self): - self._perform_reading() - var1 = (self._t_fine / 2) - 64000 - var2 = ((var1 / 4) * (var1 / 4)) / 2048 - var2 = (var2 * self._pressure_calibration[5]) / 4 - var2 = var2 + (var1 * self._pressure_calibration[4] * 2) - var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536) - var1 = (((((var1 / 4) * (var1 / 4)) / 8192) * - (self._pressure_calibration[2] * 32) / 8) + - ((self._pressure_calibration[1] * var1) / 2)) - var1 = var1 / 262144 - var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768 - calc_pres = 1048576 - self._adc_pres - calc_pres = (calc_pres - (var2 / 4096)) * 3125 - calc_pres = (calc_pres / var1) * 2 - var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096 - var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192 - var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072 - calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16) - return calc_pres # Pa - @property - def humidity(self): - self._perform_reading() - temp_scaled = ((self._t_fine * 5) + 128) / 256 - var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) - - ((temp_scaled * self._humidity_calibration[2]) / 200)) - var2 = (self._humidity_calibration[1] * - (((temp_scaled * self._humidity_calibration[3]) / 100) + - (((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) / - 64) / 100) + 16384)) / 1024 - var3 = var1 * var2 - var4 = self._humidity_calibration[5] * 128 - var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16 - var5 = ((var3 / 16384) * (var3 / 16384)) / 1024 - var6 = (var4 * var5) / 2 - calc_hum = (((var3 + var6) / 1024) * 1000) / 4096 - calc_hum /= 1000 - if calc_hum > 100: - calc_hum = 100 - if calc_hum < 0: - calc_hum = 0 - return calc_hum - @property - def altitude(self): - pressure = self.pressure - return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903)) - @property - def gas(self): - self._perform_reading() - var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536 - var2 = ((self._adc_gas * 32768) - 16777216) + var1 - var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512 - calc_gas_res = (var3 + (var2 / 2)) / var2 - return int(calc_gas_res) - def _perform_reading(self): - if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1) - < self._min_refresh_time): - return - self._write(_BME680_REG_CONFIG, [self._filter << 2]) - self._write(_BME680_REG_CTRL_MEAS, - [(self._temp_oversample << 5)|(self._pressure_oversample << 2)]) - self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample]) - self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS]) - ctrl = self._read_byte(_BME680_REG_CTRL_MEAS) - ctrl = (ctrl & 0xFC) | 0x01 - self._write(_BME680_REG_CTRL_MEAS, [ctrl]) - new_data = False - while not new_data: - data = self._read(_BME680_REG_MEAS_STATUS, 15) - new_data = data[0] & 0x80 != 0 - time.sleep(0.005) - self._last_reading = time.ticks_ms() - self._adc_pres = _read24(data[2:5]) / 16 - self._adc_temp = _read24(data[5:8]) / 16 - self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0] - self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64) - self._gas_range = data[14] & 0x0F - var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2) - var2 = (var1 * self._temp_calibration[1]) / 2048 - var3 = ((var1 / 2) * (var1 / 2)) / 4096 - var3 = (var3 * self._temp_calibration[2] * 16) / 16384 - self._t_fine = int(var2 + var3) - def _read_calibration(self): - coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25) - coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16) - coeff = list(struct.unpack('