All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diozero.devices.BMx280 Maven / Gradle / Ivy

The newest version!
package com.diozero.devices;

/*
 * #%L
 * Organisation: diozero
 * Project:      diozero - Core
 * Filename:     BMx280.java
 * 
 * This file is part of the diozero project. More information about this project
 * can be found at https://www.diozero.com/.
 * %%
 * Copyright (C) 2016 - 2024 diozero
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.tinylog.Logger;

import com.diozero.api.I2CDevice;
import com.diozero.api.RuntimeIOException;
import com.diozero.api.SpiClockMode;
import com.diozero.api.SpiConstants;
import com.diozero.api.SpiDevice;
import com.diozero.util.SleepUtil;

/*-
 * Datasheet: https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
 * Reference implementation: https://github.com/BoschSensortec/BME280_driver
 *
 * Sample implementations:
 * Java: https://github.com/ControlEverythingCommunity/BME280/blob/master/Java/BME280.java
 * Adafruit / Python: https://github.com/adafruit/Adafruit_BME280_Library/blob/master/Adafruit_BME280.cpp
 *
 * SPI Wiring:
 * SDO  | CSB | SDA/SDI | SCK / SCL | GND | VCC
 * MISO | CS  |  MOSI   |   SCLK    |
 *
 * I2C Wiring:
 * SDO  | CSB | SDA/SDI | SCK / SCL | GND | VCC
 *      |     |   SDA   |    SCL    |
 */

/**
 * Provides access to the Bosch BMx280 pressure and temperature sensor. The
 * BME280 includes an additional humidity sensor. Different constructors support
 * access via I2C or SPI.
 *
 * All constructors configure the device as follows:
 * 
    *
  • Temperature oversampling: x1
  • *
  • Pressure oversampling: x1
  • *
  • Temperature oversampling: x1
  • *
  • Operating mode: Normal
  • *
  • Standby inactive duration: 1 second
  • *
  • IIR filter coefficient: Off
  • *
* * @author gregflurry * @author mattjlewis */ public class BMx280 implements BarometerInterface, ThermometerInterface, HygrometerInterface { public enum Model { BMP280(0x58, 24, 6), BME280(0x60, 26, 8); private int deviceId; private int calibrationSize; private int dataSize; Model(int deviceId, int calibrationSize, int dataSize) { this.deviceId = deviceId; this.calibrationSize = calibrationSize; this.dataSize = dataSize; } public int getDeviceId() { return deviceId; } int getCalibrationSize() { return calibrationSize; } int getDataSize() { return dataSize; } static Model of(int deviceId) { if (deviceId == BMP280.getDeviceId()) { return Model.BMP280; } if (deviceId == BME280.getDeviceId()) { return Model.BME280; } throw new RuntimeIOException("Unexpected device id: " + deviceId); } } public static final int DEFAULT_I2C_ADDRESS = 0x76; private static final int CALIB_00_REG = 0x88; private static final int ID_REG = 0xD0; private static final int RESET_REG = 0xE0; private static final int CALIB_26_REG = 0xE1; private static final int CTRL_HUM_REG = 0xF2; private static final int STATUS_REG = 0xF3; private static final int CTRL_MEAS_REG = 0xF4; private static final int CONFIG_REG = 0xF5; private static final int PRESS_MSB_REG = 0xF7; private static final byte RESET_COMMAND = (byte) 0xB6; // Flags for ctrl_hum and ctrl_meas registers private static final byte OVERSAMPLING_1_MASK = 0b001; private static final byte OVERSAMPLING_2_MASK = 0b010; private static final byte OVERSAMPLING_4_MASK = 0b011; private static final byte OVERSAMPLING_8_MASK = 0b100; private static final byte OVERSAMPLING_16_MASK = 0b101; /** * Temperature oversampling multiplier; value can be 1, 2, 4, 8, 16. */ public enum TemperatureOversampling { _1(OVERSAMPLING_1_MASK), _2(OVERSAMPLING_2_MASK), _4(OVERSAMPLING_4_MASK), _8(OVERSAMPLING_8_MASK), _16(OVERSAMPLING_16_MASK); private byte mask; TemperatureOversampling(byte mask) { this.mask = (byte) (mask << 5); } public byte getMask() { return mask; } } /** * Pressure oversampling multiplier; value can be 1, 2, 4, 8, 16. */ public enum PressureOversampling { _1(OVERSAMPLING_1_MASK), _2(OVERSAMPLING_2_MASK), _4(OVERSAMPLING_4_MASK), _8(OVERSAMPLING_8_MASK), _16(OVERSAMPLING_16_MASK); private byte mask; PressureOversampling(byte mask) { this.mask = (byte) (mask << 2); } byte getMask() { return mask; } } /** * Humidity oversampling multiplier; value can be 1, 2, 4, 8, 16. */ public enum HumidityOversampling { _1(OVERSAMPLING_1_MASK), _2(OVERSAMPLING_2_MASK), _4(OVERSAMPLING_4_MASK), _8(OVERSAMPLING_8_MASK), _16(OVERSAMPLING_16_MASK); private byte mask; HumidityOversampling(byte mask) { this.mask = mask; } public byte getMask() { return mask; } } /** * Operating mode; value can be SLEEP, FORCED, or NORMAL. */ public enum OperatingMode { SLEEP(0b00), FORCED(0b01), NORMAL(0b11); private byte mask; OperatingMode(int mask) { this.mask = (byte) mask; } byte getMask() { return mask; } } /** * Inactive duration in standby mode; can be: *
    *
  • _500_US (0.5 ms)
  • *
  • _62_5_MS (62.5 ms)
  • *
  • _125_MS (125 ms)
  • *
  • _250_MS (250 ms)
  • *
  • _500_MS (500 ms)
  • *
  • _1_S (1 second)
  • *
  • _10_MS (10 ms)
  • *
  • _20_MS (20 ms)
  • *
*/ public enum StandbyDuration { _500_US(0b000, 500), _62_5_MS(0b001, 62_500), _125_MS(0b010, 125_000), _250_MS(0b011, 250_000), _500_MS(0b100, 500_000), _1_S(0b101, 1_000_000), _10_MS(0b110, 10_000), _20_MS(0b111, 20_000); private byte mask; private int durationUs; StandbyDuration(int mask, int durationUs) { this.mask = (byte) (mask << 5); this.durationUs = durationUs; } byte getMask() { return mask; } int getDurationUs() { return durationUs; } } /** * IIR Filter coefficient; can be OFF, _2, _4, _8, _16. */ public enum FilterCoefficient { OFF(0b000), _2(0b001), _4(0b010), _8(0b011), _16(0b100); private byte mask; FilterCoefficient(int mask) { this.mask = (byte) (mask << 2); } public byte getMask() { return mask; } } private boolean useI2C; private I2CDevice deviceI; private SpiDevice deviceS; private Model model; // Calibration data private int digT1; private short digT2; private short digT3; private int digP1; private short digP2; private short digP3; private short digP4; private short digP5; private short digP6; private short digP7; private short digP8; private short digP9; private short digH1; private short digH2; private int digH3; private int digH4; private int digH5; private byte digH6; public static class I2CBuilder { public static I2CBuilder builder(int bus) { return new I2CBuilder(bus); } private int bus; private int address = DEFAULT_I2C_ADDRESS; private TemperatureOversampling temperatureOversampling = TemperatureOversampling._1; private PressureOversampling pressureOversampling = PressureOversampling._1; private HumidityOversampling humidityOversampling = HumidityOversampling._1; private OperatingMode operatingMode = OperatingMode.NORMAL; private StandbyDuration standbyDuration = StandbyDuration._1_S; private FilterCoefficient filterCoefficient = FilterCoefficient.OFF; public I2CBuilder(int bus) { this.bus = bus; } public I2CBuilder setAddress(int address) { this.address = address; return this; } public I2CBuilder setTemperatureOversampling(TemperatureOversampling temperatureOversampling) { this.temperatureOversampling = temperatureOversampling; return this; } public I2CBuilder setPressureOversampling(PressureOversampling pressureOversampling) { this.pressureOversampling = pressureOversampling; return this; } public I2CBuilder setHumidityOversampling(HumidityOversampling humidityOversampling) { this.humidityOversampling = humidityOversampling; return this; } public I2CBuilder setOperatingMode(OperatingMode operatingMode) { this.operatingMode = operatingMode; return this; } public I2CBuilder setStandbyDuration(StandbyDuration standbyDuration) { this.standbyDuration = standbyDuration; return this; } public I2CBuilder setFilterCoefficient(FilterCoefficient filterCoefficient) { this.filterCoefficient = filterCoefficient; return this; } public BMx280 build() { return new BMx280(bus, address, temperatureOversampling, pressureOversampling, humidityOversampling, operatingMode, standbyDuration, filterCoefficient); } } public static class SpiBuilder { public static SpiBuilder builder(int chipSelect) { return new SpiBuilder(chipSelect); } private int chipSelect; private int controller = SpiConstants.DEFAULT_SPI_CONTROLLER; private int frequency = SpiConstants.DEFAULT_SPI_CLOCK_FREQUENCY; private SpiClockMode mode = SpiConstants.DEFAULT_SPI_CLOCK_MODE; private TemperatureOversampling temperatureOversampling = TemperatureOversampling._1; private PressureOversampling pressureOversampling = PressureOversampling._1; private HumidityOversampling humidityOversampling = HumidityOversampling._1; private OperatingMode operatingMode = OperatingMode.NORMAL; private StandbyDuration standbyDuration = StandbyDuration._1_S; private FilterCoefficient filterCoefficient = FilterCoefficient.OFF; public SpiBuilder(int chipSelect) { this.chipSelect = chipSelect; } /** * Set the SPI controller * * @param controller the SPI controller used * @return this builder instance */ public SpiBuilder setController(int controller) { this.controller = controller; return this; } /** * Set the SPI chip select. * * @param chipSelect the chip select line used * @return this builder instance */ public SpiBuilder setChipSelect(int chipSelect) { this.chipSelect = chipSelect; return this; } /** * Set the SPI clock frequency. * * @param frequency the frequency used * @return this builder instance */ public SpiBuilder setFrequency(int frequency) { this.frequency = frequency; return this; } /** * Set the SPI clock mode. * * @param mode the clock mode to be used * @return this builder instance */ public SpiBuilder setClockMode(SpiClockMode mode) { this.mode = mode; return this; } public SpiBuilder setTemperatureOversampling(TemperatureOversampling temperatureOversampling) { this.temperatureOversampling = temperatureOversampling; return this; } public SpiBuilder setPressureOversampling(PressureOversampling pressureOversampling) { this.pressureOversampling = pressureOversampling; return this; } public SpiBuilder setHumidityOversampling(HumidityOversampling humidityOversampling) { this.humidityOversampling = humidityOversampling; return this; } public SpiBuilder setOperatingMode(OperatingMode operatingMode) { this.operatingMode = operatingMode; return this; } public SpiBuilder setStandbyDuration(StandbyDuration standbyDuration) { this.standbyDuration = standbyDuration; return this; } public SpiBuilder setFilterCoefficient(FilterCoefficient filterCoefficient) { this.filterCoefficient = filterCoefficient; return this; } public BMx280 build() { return new BMx280(controller, chipSelect, frequency, mode, temperatureOversampling, pressureOversampling, humidityOversampling, operatingMode, standbyDuration, filterCoefficient); } } private BMx280(int bus, int address, TemperatureOversampling temperatureOversampling, PressureOversampling pressureOversampling, HumidityOversampling humidityOversampling, OperatingMode operatingMode, StandbyDuration standbyDuration, FilterCoefficient filterCoefficient) throws RuntimeIOException { useI2C = true; deviceI = I2CDevice.builder(address).setController(bus).setByteOrder(ByteOrder.LITTLE_ENDIAN).build(); initialise(temperatureOversampling, pressureOversampling, humidityOversampling, operatingMode, standbyDuration, filterCoefficient); } private BMx280(int controller, int chipSelect, int frequency, SpiClockMode mode, TemperatureOversampling temperatureOversampling, PressureOversampling pressureOversampling, HumidityOversampling humidityOversampling, OperatingMode operatingMode, StandbyDuration standbyDuration, FilterCoefficient filterCoefficient) { useI2C = false; deviceS = SpiDevice.builder(chipSelect).setController(controller).setFrequency(frequency).setClockMode(mode) .build(); initialise(temperatureOversampling, pressureOversampling, humidityOversampling, operatingMode, standbyDuration, filterCoefficient); } private void initialise(TemperatureOversampling temperatureOversampling, PressureOversampling pressureOversampling, HumidityOversampling humidityOversampling, OperatingMode operatingMode, StandbyDuration standbyDuration, FilterCoefficient filterCoefficient) { readDeviceModel(); readCoefficients(); setOperatingModes(temperatureOversampling, pressureOversampling, humidityOversampling, operatingMode); setStandbyAndFilterModes(standbyDuration, filterCoefficient); } private void readDeviceModel() throws RuntimeIOException { // Detect the device model by reading the ID register model = Model.of(readByte(ID_REG)); } private void readCoefficients() { // Wait for NVM to be copied while ((readByte(STATUS_REG) & 0x01) != 0) { SleepUtil.sleepMillis(10); } // Read the calibration data from address 0x88(136) ByteBuffer buffer = readByteBlock(CALIB_00_REG, model.getCalibrationSize()); // Temperature coefficients digT1 = buffer.getShort() & 0xffff; // unsigned short digT2 = buffer.getShort(); // signed short digT3 = buffer.getShort(); // signed short // Pressure coefficients digP1 = buffer.getShort() & 0xffff; // unsigned short digP2 = buffer.getShort(); // signed short digP3 = buffer.getShort(); // signed short digP4 = buffer.getShort(); // signed short digP5 = buffer.getShort(); // signed short digP6 = buffer.getShort(); // signed short digP7 = buffer.getShort(); // signed short digP8 = buffer.getShort(); // signed short digP9 = buffer.getShort(); // signed short if (model == Model.BME280) { // Skip 1 byte (0xA0) buffer.get(); // Read 1 byte of data from address 0xA1(161) digH1 = (short) (buffer.get() & 0xff); // unsigned char // Read 7 bytes of data from address 0xE1(225) buffer = readByteBlock(CALIB_26_REG, 7); // Humidity coefficients digH2 = buffer.getShort(); // signed short digH3 = buffer.get() & 0xff; // unsigned char byte e4 = buffer.get(); byte e5 = buffer.get(); digH4 = (e4 << 4) | (e5 & 0x0F); // signed short (0xE4 / 0xE5[3:0]) digH5 = (buffer.get() << 4) | ((e5 & 0xF0) >> 4); // signed short (0xE5[7:4]/0xE6) digH6 = buffer.get(); // signed char } } public Model getModel() { return model; } /** * Sets the oversampling multipliers and operating mode. * * @param tempOversampling oversampling multiplier for temperature * @param pressOversampling oversampling multiplier for pressure * @param humOversampling oversampling multiplier for humidity * @param operatingMode operating mode */ public void setOperatingModes(TemperatureOversampling tempOversampling, PressureOversampling pressOversampling, HumidityOversampling humOversampling, OperatingMode operatingMode) { // You must set CTRL_MEAS_REG after setting the CTRL_HUM_REG register, otherwise // the values won't be applied (see DS 5.4.3) if (model == Model.BME280) { // Humidity over sampling rate = 1 writeByte(CTRL_HUM_REG, humOversampling.getMask()); } // Normal mode, temp and pressure oversampling rate = 1 writeByte(CTRL_MEAS_REG, (byte) (tempOversampling.getMask() | pressOversampling.getMask() | operatingMode.getMask())); } /** * Sets the standby duration for normal mode and the IIR filter coefficient. * * @param standbyDuration standby duration * @param filterCoefficient IIR filter coefficient */ public void setStandbyAndFilterModes(StandbyDuration standbyDuration, FilterCoefficient filterCoefficient) { // Stand_by time = 1000 ms, filter off writeByte(CONFIG_REG, (byte) (standbyDuration.getMask() | filterCoefficient.getMask())); } /** * Waits for data to become available. * * @param interval sleep interval * @param maxIntervals maximum number of intervals to wait * @return true if data available, false if not */ public boolean waitDataAvailable(int interval, int maxIntervals) { for (int i = 0; i < maxIntervals; i++) { // check data ready if (isDataAvailable()) { return true; } SleepUtil.sleepMillis(interval); } return false; } /** * Indicates if data is available. * * @return rue if data available, false if not */ public boolean isDataAvailable() { return (readByte(STATUS_REG) & 0x08) == 0; } /** * Reads the temperature, pressure, and humidity registers; compensates the raw * values to provide meaningful results. * * @return array in order of: temperature, pressure, humidity */ public float[] getValues() { // Read the pressure, temperature, and humidity registers ByteBuffer buffer = readByteBlock(PRESS_MSB_REG, model.getDataSize()); // Unpack the raw 20-bit unsigned pressure value int adc_p = ((buffer.get() & 0xff) << 12) | ((buffer.get() & 0xff) << 4) | ((buffer.get() & 0xf0) >> 4); // Unpack the raw 20-bit unsigned temperature value int adc_t = ((buffer.get() & 0xff) << 12) | ((buffer.get() & 0xff) << 4) | ((buffer.get() & 0xf0) >> 4); int adc_h = 0; if (model == Model.BME280) { // Unpack the raw 16-bit unsigned humidity value adc_h = ((buffer.get() & 0xff) << 8) | (buffer.get() & 0xff); } Logger.debug("adc_p={}, adc_t={}, adc_h={}", Integer.valueOf(adc_p), Integer.valueOf(adc_t), Integer.valueOf(adc_h)); int tvar1 = (((adc_t >> 3) - (digT1 << 1)) * digT2) >> 11; int tvar2 = (((((adc_t >> 4) - digT1) * ((adc_t >> 4) - digT1)) >> 12) * digT3) >> 14; int t_fine = tvar1 + tvar2; int temp = (t_fine * 5 + 128) >> 8; long pvar1 = t_fine - 128000; long pvar2 = pvar1 * pvar1 * digP6; pvar2 = pvar2 + ((pvar1 * digP5) << 17); pvar2 = pvar2 + (((long) digP4) << 35); pvar1 = ((pvar1 * pvar1 * digP3) >> 8) + ((pvar1 * digP2) << 12); pvar1 = (((1L << 47) + pvar1)) * digP1 >> 33; long pressure; if (pvar1 == 0) { pressure = 0; // Avoid exception caused by division by zero } else { pressure = 1048576 - adc_p; pressure = (((pressure << 31) - pvar2) * 3125) / pvar1; pvar1 = (digP9 * (pressure >> 13) * (pressure >> 13)) >> 25; pvar2 = (digP8 * pressure) >> 19; pressure = ((pressure + pvar1 + pvar2) >> 8) + (((long) digP7) << 4); } long humidity = 0; if (model == Model.BME280) { int v_x1_u32r = t_fine - 76800; v_x1_u32r = ((((adc_h << 14) - (digH4 << 20) - (digH5 * v_x1_u32r)) + 16384) >> 15) * (((((((v_x1_u32r * digH6) >> 10) * (((v_x1_u32r * digH3) >> 11) + 32768)) >> 10) + 2097152) * digH2 + 8192) >> 14); v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * digH1) >> 4); v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r; v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r; humidity = ((long) v_x1_u32r) >> 12; } return new float[] { temp / 100f, pressure / 25600f, humidity / 1024f }; } /** * Reads the temperature, pressure, and humidity registers; compensates the raw * values to provide meaningful results. * * @return temperature */ @Override public float getTemperature() { return getValues()[0]; } /** * Reads the temperature, pressure, and humidity registers; compensates the raw * values to provide meaningful results. * * @return pressure in hectoPascals (hPa) */ @Override public float getPressure() { return getValues()[1]; } /** * Reads the temperature, pressure, and humidity registers; compensates the raw * values to provide meaningful results. * * @return humidity */ @Override public float getRelativeHumidity() { return getValues()[2]; } /** * Closes the device * * @throws RuntimeIOException if close fails */ @Override public void close() throws RuntimeIOException { if (useI2C) { deviceI.close(); } else { deviceS.close(); } } /** * Resets the device. */ public void reset() { writeByte(RESET_REG, RESET_COMMAND); } private byte readByte(int register) { if (useI2C) { return deviceI.readByteData(register); } byte[] tx = { (byte) (register | 0x80), 0 }; byte[] ret = deviceS.writeAndRead(tx); return ret[1]; } private void writeByte(int register, byte value) { if (useI2C) { deviceI.writeByteData(register, value); } else { byte[] tx = new byte[2]; tx[0] = (byte) (register & 0x7f); // MSB must be 0 tx[1] = value; deviceS.write(tx); } } private ByteBuffer readByteBlock(int register, int length) { byte[] data = new byte[length]; if (useI2C) { deviceI.readI2CBlockData(register, data); } else { byte[] tx = new byte[length + 1]; tx[0] = (byte) (register | 0x80); /* NOTE: rest of array initialized to 0 */ byte[] ret = deviceS.writeAndRead(tx); System.arraycopy(ret, 1, data, 0, length); } ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(ByteOrder.LITTLE_ENDIAN); return buffer; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy