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

     1  package i2c
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"log"
     8  	"math"
     9  )
    10  
    11  const bmp388Debug = false
    12  
    13  // the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76
    14  const bmp388DefaultAddress = 0x77
    15  
    16  // BMP388Accuracy accuracy type
    17  type BMP388Accuracy uint8
    18  type BMP388IIRFilter uint8
    19  
    20  const (
    21  	bmp388ChipID = 0x50
    22  
    23  	bmp388RegChipID       = 0x00
    24  	bmp388RegStatus       = 0x03
    25  	bmp388RegPressureData = 0x04 // XLSB, 0x05 LSByte, 0x06 MSByte
    26  	bmp388RegTempData     = 0x07 // XLSB, 0x08 LSByte, 0x09 MSByte
    27  	bmp388RegPWRCTRL      = 0x1B // enable/disable pressure and temperature measurement, mode
    28  	bmp388RegOSR          = 0x1C // Oversampling Rates
    29  	bmp388RegODR          = 0x1D // Output Data Rates
    30  	bmp388RegConf         = 0x1F // config filter for IIR coefficients
    31  	bmp388RegCalib00      = 0x31
    32  	bmp388RegCMD          = 0x7E
    33  
    34  	// bits 0, 1 of control register
    35  	bmp388PWRCTRLPressEnableBit = 0x01
    36  	bmp388PWRCTRLTempEnableBit  = 0x02
    37  
    38  	// bits 4, 5 of control register (will be shifted on write)
    39  	bmp388PWRCTRLSleep  = 0x00
    40  	bmp388PWRCTRLForced = 0x01 // same as 0x02
    41  	bmp388PWRCTRLNormal = 0x03
    42  
    43  	// bits 1, 2 ,3 of config filter IIR filter coefficients (will be shifted on write)
    44  	bmp388ConfFilterCoef0   BMP388IIRFilter = 0 // bypass-mode
    45  	bmp388ConfFilterCoef1   BMP388IIRFilter = 1
    46  	bmp388ConfFilterCoef3   BMP388IIRFilter = 2
    47  	bmp388ConfFilterCoef7   BMP388IIRFilter = 3
    48  	bmp388ConfFilterCoef15  BMP388IIRFilter = 4
    49  	bmp388ConfFilterCoef31  BMP388IIRFilter = 5
    50  	bmp388ConfFilterCoef63  BMP388IIRFilter = 6
    51  	bmp388ConfFilterCoef127 BMP388IIRFilter = 7
    52  
    53  	// oversampling rate, a single value is used (could be different for pressure and temperature)
    54  	BMP388AccuracyUltraLow  BMP388Accuracy = 0 // x1 sample
    55  	BMP388AccuracyLow       BMP388Accuracy = 1 // x2 samples
    56  	BMP388AccuracyStandard  BMP388Accuracy = 2 // x4 samples
    57  	BMP388AccuracyHigh      BMP388Accuracy = 3 // x8 samples
    58  	BMP388AccuracyUltraHigh BMP388Accuracy = 4 // x16 samples
    59  	BMP388AccuracyHighest   BMP388Accuracy = 5 // x32 samples
    60  
    61  	bmp388CMDReserved        = 0x00 // reserved, no command
    62  	bmp388CMDExtModeEnMiddle = 0x34
    63  	bmp388CMDFifoFlush       = 0xB0 // clears all data in the FIFO, does not change FIFO_CONFIG registers
    64  	bmp388CMDSoftReset       = 0xB6 // triggers a reset, all user configuration settings are overwritten with their default state
    65  
    66  	bmp388SeaLevelPressure = 1013.25
    67  )
    68  
    69  type bmp388CalibrationCoefficients struct {
    70  	t1  float32
    71  	t2  float32
    72  	t3  float32
    73  	p1  float32
    74  	p2  float32
    75  	p3  float32
    76  	p4  float32
    77  	p5  float32
    78  	p6  float32
    79  	p7  float32
    80  	p8  float32
    81  	p9  float32
    82  	p10 float32
    83  	p11 float32
    84  }
    85  
    86  // BMP388Driver is a driver for the BMP388 temperature/pressure sensor
    87  type BMP388Driver struct {
    88  	*Driver
    89  	calCoeffs   *bmp388CalibrationCoefficients
    90  	ctrlPwrMode uint8
    91  	confFilter  BMP388IIRFilter
    92  }
    93  
    94  // NewBMP388Driver creates a new driver with specified i2c interface.
    95  // Params:
    96  //		c Connector - the Adaptor to use with this Driver
    97  //
    98  // Optional params:
    99  //		i2c.WithBus(int):	bus to use with this driver
   100  //		i2c.WithAddress(int):	address to use with this driver
   101  //
   102  func NewBMP388Driver(c Connector, options ...func(Config)) *BMP388Driver {
   103  	d := &BMP388Driver{
   104  		Driver:      NewDriver(c, "BMP388", bmp388DefaultAddress),
   105  		calCoeffs:   &bmp388CalibrationCoefficients{},
   106  		ctrlPwrMode: bmp388PWRCTRLForced,
   107  		confFilter:  bmp388ConfFilterCoef0,
   108  	}
   109  	d.afterStart = d.initialization
   110  
   111  	for _, option := range options {
   112  		option(d)
   113  	}
   114  
   115  	// TODO: expose commands to API
   116  	return d
   117  }
   118  
   119  // WithBMP388IIRFilter option sets count of IIR filter coefficients.
   120  // Valid settings are of type "BMP388IIRFilter"
   121  func WithBMP388IIRFilter(val BMP388IIRFilter) func(Config) {
   122  	return func(c Config) {
   123  		if d, ok := c.(*BMP388Driver); ok {
   124  			d.confFilter = val
   125  		} else if bmp388Debug {
   126  			log.Printf("Trying to set IIR filter for non-BMP388Driver %v", c)
   127  		}
   128  	}
   129  }
   130  
   131  // Temperature returns the current temperature, in celsius degrees.
   132  func (d *BMP388Driver) Temperature(accuracy BMP388Accuracy) (temp float32, err error) {
   133  	d.mutex.Lock()
   134  	defer d.mutex.Unlock()
   135  
   136  	var rawT int32
   137  
   138  	mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit
   139  	if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil {
   140  		return 0, err
   141  	}
   142  
   143  	// Set Accuracy for temperature
   144  	if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy<<3)); err != nil {
   145  		return 0, err
   146  	}
   147  
   148  	if rawT, err = d.rawTemp(); err != nil {
   149  		return 0.0, err
   150  	}
   151  	temp = d.calculateTemp(rawT)
   152  	return
   153  }
   154  
   155  // Pressure returns the current barometric pressure, in Pa
   156  func (d *BMP388Driver) Pressure(accuracy BMP388Accuracy) (press float32, err error) {
   157  	d.mutex.Lock()
   158  	defer d.mutex.Unlock()
   159  
   160  	var rawT, rawP int32
   161  
   162  	mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit
   163  	if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil {
   164  		return 0, err
   165  	}
   166  
   167  	// Set Standard Accuracy for pressure
   168  	if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy)); err != nil {
   169  		return 0, err
   170  	}
   171  
   172  	if rawT, err = d.rawTemp(); err != nil {
   173  		return 0.0, err
   174  	}
   175  
   176  	if rawP, err = d.rawPressure(); err != nil {
   177  		return 0.0, err
   178  	}
   179  	tLin := d.calculateTemp(rawT)
   180  	return d.calculatePress(rawP, float64(tLin)), nil
   181  }
   182  
   183  // Altitude returns the current altitude in meters based on the
   184  // current barometric pressure and estimated pressure at sea level.
   185  // https://www.weather.gov/media/epz/wxcalc/pressureAltitude.pdf
   186  func (d *BMP388Driver) Altitude(accuracy BMP388Accuracy) (alt float32, err error) {
   187  	atmP, _ := d.Pressure(accuracy)
   188  	atmP /= 100.0
   189  	alt = float32(44307.0 * (1.0 - math.Pow(float64(atmP/bmp388SeaLevelPressure), 0.190284)))
   190  
   191  	return
   192  }
   193  
   194  // initialization reads the calibration coefficients.
   195  func (d *BMP388Driver) initialization() (err error) {
   196  	var chipID uint8
   197  	if chipID, err = d.connection.ReadByteData(bmp388RegChipID); err != nil {
   198  		return err
   199  	}
   200  
   201  	if bmp388ChipID != chipID {
   202  		return fmt.Errorf("Incorrect BMP388 chip ID '0%x' Expected 0x%x", chipID, bmp388ChipID)
   203  	}
   204  
   205  	var (
   206  		t1  uint16
   207  		t2  uint16
   208  		t3  int8
   209  		p1  int16
   210  		p2  int16
   211  		p3  int8
   212  		p4  int8
   213  		p5  uint16
   214  		p6  uint16
   215  		p7  int8
   216  		p8  int8
   217  		p9  int16
   218  		p10 int8
   219  		p11 int8
   220  	)
   221  
   222  	coefficients := make([]byte, 24)
   223  	if err = d.connection.ReadBlockData(bmp388RegCalib00, coefficients); err != nil {
   224  		return err
   225  	}
   226  	buf := bytes.NewBuffer(coefficients)
   227  
   228  	binary.Read(buf, binary.LittleEndian, &t1)
   229  	binary.Read(buf, binary.LittleEndian, &t2)
   230  	binary.Read(buf, binary.LittleEndian, &t3)
   231  	binary.Read(buf, binary.LittleEndian, &p1)
   232  	binary.Read(buf, binary.LittleEndian, &p2)
   233  	binary.Read(buf, binary.LittleEndian, &p3)
   234  	binary.Read(buf, binary.LittleEndian, &p4)
   235  	binary.Read(buf, binary.LittleEndian, &p5)
   236  	binary.Read(buf, binary.LittleEndian, &p6)
   237  	binary.Read(buf, binary.LittleEndian, &p7)
   238  	binary.Read(buf, binary.LittleEndian, &p8)
   239  	binary.Read(buf, binary.LittleEndian, &p9)
   240  	binary.Read(buf, binary.LittleEndian, &p10)
   241  	binary.Read(buf, binary.LittleEndian, &p11)
   242  
   243  	d.calCoeffs.t1 = float32(float64(t1) / math.Pow(2, -8))
   244  	d.calCoeffs.t2 = float32(float64(t2) / math.Pow(2, 30))
   245  	d.calCoeffs.t3 = float32(float64(t3) / math.Pow(2, 48))
   246  	d.calCoeffs.p1 = float32((float64(p1) - math.Pow(2, 14)) / math.Pow(2, 20))
   247  	d.calCoeffs.p2 = float32((float64(p2) - math.Pow(2, 14)) / math.Pow(2, 29))
   248  	d.calCoeffs.p3 = float32(float64(p3) / math.Pow(2, 32))
   249  	d.calCoeffs.p4 = float32(float64(p4) / math.Pow(2, 37))
   250  	d.calCoeffs.p5 = float32(float64(p5) / math.Pow(2, -3))
   251  	d.calCoeffs.p6 = float32(float64(p6) / math.Pow(2, 6))
   252  	d.calCoeffs.p7 = float32(float64(p7) / math.Pow(2, 8))
   253  	d.calCoeffs.p8 = float32(float64(p8) / math.Pow(2, 15))
   254  	d.calCoeffs.p9 = float32(float64(p9) / math.Pow(2, 48))
   255  	d.calCoeffs.p10 = float32(float64(p10) / math.Pow(2, 48))
   256  	d.calCoeffs.p11 = float32(float64(p11) / math.Pow(2, 65))
   257  
   258  	if err = d.connection.WriteByteData(bmp388RegCMD, bmp388CMDSoftReset); err != nil {
   259  		return err
   260  	}
   261  
   262  	if err = d.connection.WriteByteData(bmp388RegConf, uint8(d.confFilter)<<1); err != nil {
   263  		return err
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  func (d *BMP388Driver) rawTemp() (temp int32, err error) {
   270  	var tp0, tp1, tp2 byte
   271  
   272  	data := make([]byte, 3)
   273  	if err = d.connection.ReadBlockData(bmp388RegTempData, data); err != nil {
   274  		return 0, err
   275  	}
   276  	buf := bytes.NewBuffer(data)
   277  
   278  	binary.Read(buf, binary.LittleEndian, &tp0) // XLSB
   279  	binary.Read(buf, binary.LittleEndian, &tp1) // LSB
   280  	binary.Read(buf, binary.LittleEndian, &tp2) // MSB
   281  
   282  	temp = ((int32(tp2) << 16) | (int32(tp1) << 8) | int32(tp0))
   283  	return
   284  }
   285  
   286  func (d *BMP388Driver) rawPressure() (press int32, err error) {
   287  	var tp0, tp1, tp2 byte
   288  
   289  	data := make([]byte, 3)
   290  	if err = d.connection.ReadBlockData(bmp388RegPressureData, data); err != nil {
   291  		return 0, err
   292  	}
   293  	buf := bytes.NewBuffer(data)
   294  
   295  	binary.Read(buf, binary.LittleEndian, &tp0) // XLSB
   296  	binary.Read(buf, binary.LittleEndian, &tp1) // LSB
   297  	binary.Read(buf, binary.LittleEndian, &tp2) // MSB
   298  
   299  	press = ((int32(tp2) << 16) | (int32(tp1) << 8) | int32(tp0))
   300  
   301  	return
   302  }
   303  
   304  func (d *BMP388Driver) calculateTemp(rawTemp int32) float32 {
   305  	// datasheet, sec 9.2 Temperature compensation
   306  	pd1 := float32(rawTemp) - d.calCoeffs.t1
   307  	pd2 := pd1 * d.calCoeffs.t2
   308  
   309  	temperatureComp := pd2 + (pd1*pd1)*d.calCoeffs.t3
   310  
   311  	return temperatureComp
   312  }
   313  
   314  func (d *BMP388Driver) calculatePress(rawPress int32, tLin float64) float32 {
   315  	pd1 := float64(d.calCoeffs.p6) * tLin
   316  	pd2 := float64(d.calCoeffs.p7) * math.Pow(tLin, 2)
   317  	pd3 := float64(d.calCoeffs.p8) * math.Pow(tLin, 3)
   318  	po1 := float64(d.calCoeffs.p5) + pd1 + pd2 + pd3
   319  
   320  	pd1 = float64(d.calCoeffs.p2) * tLin
   321  	pd2 = float64(d.calCoeffs.p3) * math.Pow(tLin, 2)
   322  	pd3 = float64(d.calCoeffs.p4) * math.Pow(tLin, 3)
   323  	po2 := float64(rawPress) * (float64(d.calCoeffs.p1) + pd1 + pd2 + pd3)
   324  
   325  	pd1 = math.Pow(float64(rawPress), 2)
   326  	pd2 = float64(d.calCoeffs.p9) + float64(d.calCoeffs.p10)*tLin
   327  	pd3 = pd1 * pd2
   328  	pd4 := pd3 + math.Pow(float64(rawPress), 3)*float64(d.calCoeffs.p11)
   329  
   330  	pressure := po1 + po2 + pd4
   331  
   332  	return float32(pressure)
   333  }