gobot.io/x/gobot/v2@v2.1.0/drivers/i2c/bme280_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"log"
     8  )
     9  
    10  const bme280Debug = true
    11  
    12  type BME280HumidityOversampling uint8
    13  
    14  const (
    15  	bme280RegCalibDigH1      = 0xA1
    16  	bme280RegCalibDigH2LSB   = 0xE1
    17  	bme280RegControlHumidity = 0xF2
    18  	bme280RegHumidityMSB     = 0xFD
    19  
    20  	// bits 0, 1, 3 of control humidity register
    21  	BME280CtrlHumidityNoMeasurement  BME280HumidityOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00)
    22  	BME280CtrlHumidityOversampling1  BME280HumidityOversampling = 0x01
    23  	BME280CtrlHumidityOversampling2  BME280HumidityOversampling = 0x02
    24  	BME280CtrlHumidityOversampling4  BME280HumidityOversampling = 0x03
    25  	BME280CtrlHumidityOversampling8  BME280HumidityOversampling = 0x04
    26  	BME280CtrlHumidityOversampling16 BME280HumidityOversampling = 0x05 // same as 0x06, 0x07
    27  )
    28  
    29  type bmeHumidityCalibrationCoefficients struct {
    30  	h1 uint8
    31  	h2 int16
    32  	h3 uint8
    33  	h4 int16
    34  	h5 int16
    35  	h6 int8
    36  }
    37  
    38  // BME280Driver is a driver for the BME280 temperature/humidity sensor.
    39  // It implements all of the same functions as the BMP280Driver, but also
    40  // adds the Humidity() function by reading the BME280's humidity sensor.
    41  // For details on the BMP280Driver please see:
    42  //
    43  //	https://godoc.org/gobot.io/x/gobot/v2/drivers/i2c#BMP280Driver
    44  type BME280Driver struct {
    45  	*BMP280Driver
    46  	humCalCoeffs    *bmeHumidityCalibrationCoefficients
    47  	ctrlHumOversamp BME280HumidityOversampling
    48  }
    49  
    50  // NewBME280Driver creates a new driver with specified i2c interface.
    51  // Params:
    52  //
    53  //	conn Connector - the Adaptor to use with this Driver
    54  //
    55  // Optional params:
    56  //
    57  //	i2c.WithBus(int):	bus to use with this driver
    58  //	i2c.WithAddress(int):	address to use with this driver
    59  func NewBME280Driver(c Connector, options ...func(Config)) *BME280Driver {
    60  	d := &BME280Driver{
    61  		BMP280Driver:    NewBMP280Driver(c),
    62  		humCalCoeffs:    &bmeHumidityCalibrationCoefficients{},
    63  		ctrlHumOversamp: BME280CtrlHumidityOversampling16,
    64  	}
    65  	d.afterStart = d.initializationBME280
    66  
    67  	// this loop is for options of this class, all options of base class BMP280Driver
    68  	// must be added in this class for usage
    69  	for _, option := range options {
    70  		option(d)
    71  	}
    72  
    73  	// TODO: expose commands to API
    74  	return d
    75  }
    76  
    77  // WithBME280PressureOversampling option sets the oversampling for pressure.
    78  // Valid settings are of type "BMP280PressureOversampling"
    79  func WithBME280PressureOversampling(val BMP280PressureOversampling) func(Config) {
    80  	return func(c Config) {
    81  		if d, ok := c.(*BME280Driver); ok {
    82  			d.ctrlPressOversamp = val
    83  		} else if bme280Debug {
    84  			log.Printf("Trying to set pressure oversampling for non-BME280Driver %v", c)
    85  		}
    86  	}
    87  }
    88  
    89  // WithBME280TemperatureOversampling option sets oversampling for temperature.
    90  // Valid settings are of type "BMP280TemperatureOversampling"
    91  func WithBME280TemperatureOversampling(val BMP280TemperatureOversampling) func(Config) {
    92  	return func(c Config) {
    93  		if d, ok := c.(*BME280Driver); ok {
    94  			d.ctrlTempOversamp = val
    95  		} else if bme280Debug {
    96  			log.Printf("Trying to set temperature oversampling for non-BME280Driver %v", c)
    97  		}
    98  	}
    99  }
   100  
   101  // WithBME280IIRFilter option sets the count of IIR filter coefficients.
   102  // Valid settings are of type "BMP280IIRFilter"
   103  func WithBME280IIRFilter(val BMP280IIRFilter) func(Config) {
   104  	return func(c Config) {
   105  		if d, ok := c.(*BME280Driver); ok {
   106  			d.confFilter = val
   107  		} else if bme280Debug {
   108  			log.Printf("Trying to set IIR filter for non-BME280Driver %v", c)
   109  		}
   110  	}
   111  }
   112  
   113  // WithBME280HumidityOversampling option sets the oversampling for humidity.
   114  // Valid settings are of type "BME280HumidityOversampling"
   115  func WithBME280HumidityOversampling(val BME280HumidityOversampling) func(Config) {
   116  	return func(c Config) {
   117  		if d, ok := c.(*BME280Driver); ok {
   118  			d.ctrlHumOversamp = val
   119  		} else if bme280Debug {
   120  			log.Printf("Trying to set humidity oversampling for non-BME280Driver %v", c)
   121  		}
   122  	}
   123  }
   124  
   125  // Humidity returns the current humidity in percentage of relative humidity
   126  func (d *BME280Driver) Humidity() (humidity float32, err error) {
   127  	d.mutex.Lock()
   128  	defer d.mutex.Unlock()
   129  
   130  	var rawH uint32
   131  	if rawH, err = d.rawHumidity(); err != nil {
   132  		return 0.0, err
   133  	}
   134  	humidity = d.calculateHumidity(rawH)
   135  	return
   136  }
   137  
   138  func (d *BME280Driver) initializationBME280() (err error) {
   139  	// call the initialization routine of base class BMP280Driver, which do:
   140  	// * initializes temperature and pressure calibration coefficients
   141  	// * set the control register
   142  	// * set the configuration register
   143  	if err := d.initialization(); err != nil {
   144  		return err
   145  	}
   146  
   147  	if err := d.initHumidity(); err != nil {
   148  		return err
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  // read the humidity calibration coefficients.
   155  func (d *BME280Driver) initHumidity() (err error) {
   156  	var hch1 byte
   157  	if hch1, err = d.connection.ReadByteData(bme280RegCalibDigH1); err != nil {
   158  		return err
   159  	}
   160  	buf := bytes.NewBuffer([]byte{hch1})
   161  	binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h1)
   162  
   163  	coefficients := make([]byte, 7)
   164  	if err = d.connection.ReadBlockData(bme280RegCalibDigH2LSB, coefficients); err != nil {
   165  		return err
   166  	}
   167  	buf = bytes.NewBuffer(coefficients)
   168  
   169  	// H4 and H5 laid out strangely on the bme280
   170  	var addrE4 byte
   171  	var addrE5 byte
   172  	var addrE6 byte
   173  
   174  	binary.Read(buf, binary.LittleEndian, &d.humCalCoeffs.h2) // E1 ...
   175  	binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h3)    // E3
   176  	binary.Read(buf, binary.BigEndian, &addrE4)               // E4
   177  	binary.Read(buf, binary.BigEndian, &addrE5)               // E5
   178  	binary.Read(buf, binary.BigEndian, &addrE6)               // E6
   179  	binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h6)    // ... E7
   180  
   181  	d.humCalCoeffs.h4 = 0 + (int16(addrE4) << 4) | (int16(addrE5 & 0x0F))
   182  	d.humCalCoeffs.h5 = 0 + (int16(addrE6) << 4) | (int16(addrE5) >> 4)
   183  
   184  	// The 'ctrl_hum' register (0xF2) sets the humidity data acquisition options of
   185  	// the device. Changes to this register only become effective after a write
   186  	// operation to 'ctrl_meas' (0xF4). So we read the current value in, then write it back
   187  	d.connection.WriteByteData(bme280RegControlHumidity, uint8(d.ctrlHumOversamp))
   188  
   189  	var cmr uint8
   190  	cmr, err = d.connection.ReadByteData(bmp280RegCtrl)
   191  	if err == nil {
   192  		err = d.connection.WriteByteData(bmp280RegCtrl, cmr)
   193  	}
   194  	return err
   195  }
   196  
   197  func (d *BME280Driver) rawHumidity() (uint32, error) {
   198  	ret := make([]byte, 2)
   199  	if err := d.connection.ReadBlockData(bme280RegHumidityMSB, ret); err != nil {
   200  		return 0, err
   201  	}
   202  	if ret[0] == 0x80 && ret[1] == 0x00 {
   203  		return 0, errors.New("Humidity disabled")
   204  	}
   205  	buf := bytes.NewBuffer(ret)
   206  	var rawH uint16
   207  	binary.Read(buf, binary.BigEndian, &rawH)
   208  	return uint32(rawH), nil
   209  }
   210  
   211  // Adapted from https://github.com/BoschSensortec/BME280_driver/blob/master/bme280.c
   212  // function bme280_compensate_humidity_double(s32 v_uncom_humidity_s32)
   213  func (d *BME280Driver) calculateHumidity(rawH uint32) float32 {
   214  	var rawT int32
   215  	var err error
   216  	var h float32
   217  
   218  	rawT, err = d.rawTemp()
   219  	if err != nil {
   220  		return 0
   221  	}
   222  
   223  	_, tFine := d.calculateTemp(rawT)
   224  	h = float32(tFine) - 76800
   225  
   226  	if h == 0 {
   227  		return 0 // TODO err is 'invalid data' from Bosch - include errors or not?
   228  	}
   229  
   230  	x := float32(rawH) - (float32(d.humCalCoeffs.h4)*64.0 +
   231  		(float32(d.humCalCoeffs.h5) / 16384.0 * h))
   232  
   233  	y := float32(d.humCalCoeffs.h2) / 65536.0 *
   234  		(1.0 + float32(d.humCalCoeffs.h6)/67108864.0*h*
   235  			(1.0+float32(d.humCalCoeffs.h3)/67108864.0*h))
   236  
   237  	h = x * y
   238  	h = h * (1 - float32(d.humCalCoeffs.h1)*h/524288)
   239  	return h
   240  }