tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/bmp388/bmp388.go (about)

     1  package bmp388
     2  
     3  import (
     4  	"errors"
     5  
     6  	"tinygo.org/x/drivers"
     7  	"tinygo.org/x/drivers/internal/legacy"
     8  )
     9  
    10  var (
    11  	errConfigWrite  = errors.New("bmp388: failed to configure sensor, check connection")
    12  	errConfig       = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
    13  	errCaliRead     = errors.New("bmp388: failed to read calibration coefficient register")
    14  	errSoftReset    = errors.New("bmp388: failed to perform a soft reset")
    15  	errNotConnected = errors.New("bmp388: not connected")
    16  )
    17  
    18  type Oversampling byte
    19  type Mode byte
    20  type OutputDataRate byte
    21  type FilterCoefficient byte
    22  
    23  // Config contains settings for filtering, sampling, and modes of operation
    24  type Config struct {
    25  	Pressure    Oversampling
    26  	Temperature Oversampling
    27  	Mode        Mode
    28  	ODR         OutputDataRate
    29  	IIR         FilterCoefficient
    30  }
    31  
    32  // Device wraps the I2C connection and configuration values for the BMP388
    33  type Device struct {
    34  	bus     drivers.I2C
    35  	Address uint8
    36  	cali    calibrationCoefficients
    37  	Config  Config
    38  }
    39  
    40  type calibrationCoefficients struct {
    41  	// Temperature compensation
    42  	t1 uint16
    43  	t2 uint16
    44  	t3 int8
    45  
    46  	// Pressure compensation
    47  	p1  int16
    48  	p2  int16
    49  	p3  int8
    50  	p4  int8
    51  	p5  uint16
    52  	p6  uint16
    53  	p7  int8
    54  	p8  int8
    55  	p9  int16
    56  	p10 int8
    57  	p11 int8
    58  }
    59  
    60  // New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
    61  func New(bus drivers.I2C) Device {
    62  	return Device{
    63  		bus:     bus,
    64  		Address: Address,
    65  	}
    66  }
    67  
    68  // Configure can enable settings on the BMP388 and reads the calibration coefficients
    69  func (d *Device) Configure(config Config) (err error) {
    70  	d.Config = config
    71  
    72  	if d.Config == (Config{}) {
    73  		d.Config.Mode = Normal
    74  	}
    75  
    76  	// Turning on the pressure and temperature sensors and setting the measurement mode
    77  	err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
    78  
    79  	// Configure the oversampling, output data rate, and iir filter coefficient settings
    80  	err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
    81  	err = d.writeRegister(RegODR, byte(d.Config.ODR))
    82  	err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
    83  
    84  	if err != nil {
    85  		return errConfigWrite
    86  	}
    87  
    88  	// Check if there is a problem with the given configuration
    89  	if d.configurationError() {
    90  		return errConfig
    91  	}
    92  
    93  	// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
    94  	// in the datasheet is implemented in floating point
    95  	buffer, err := d.readRegister(RegCali, 21)
    96  	if err != nil {
    97  		return errCaliRead
    98  	}
    99  
   100  	d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
   101  	d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
   102  	d.cali.t3 = int8(buffer[4])
   103  
   104  	d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
   105  	d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
   106  	d.cali.p3 = int8(buffer[9])
   107  	d.cali.p4 = int8(buffer[10])
   108  	d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
   109  	d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
   110  	d.cali.p7 = int8(buffer[15])
   111  	d.cali.p8 = int8(buffer[16])
   112  	d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
   113  	d.cali.p10 = int8(buffer[19])
   114  	d.cali.p11 = int8(buffer[20])
   115  
   116  	return nil
   117  }
   118  
   119  // Read the temperature registers and compute a compensation value for the temperature and pressure compensation
   120  // calculations. This is not the temperature itself.
   121  func (d *Device) tlinCompensate() (int64, error) {
   122  	rawTemp, err := d.readSensorData(RegTemp)
   123  	if err != nil {
   124  		return 0, err
   125  	}
   126  
   127  	// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
   128  	partialData1 := rawTemp - (256 * int64(d.cali.t1))
   129  	partialData2 := int64(d.cali.t2) * partialData1
   130  	partialData3 := (partialData1 * partialData1)
   131  	partialData4 := partialData3 * int64(d.cali.t3)
   132  	partialData5 := (partialData2 * 262144) + partialData4
   133  	return partialData5 / 4294967296, nil
   134  
   135  }
   136  
   137  // ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
   138  func (d *Device) ReadTemperature() (int32, error) {
   139  
   140  	tlin, err := d.tlinCompensate()
   141  	if err != nil {
   142  		return 0, err
   143  	}
   144  
   145  	temp := (tlin * 25) / 16384
   146  	return int32(temp), nil
   147  }
   148  
   149  // ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
   150  func (d *Device) ReadPressure() (int32, error) {
   151  
   152  	tlin, err := d.tlinCompensate()
   153  	if err != nil {
   154  		return 0, err
   155  	}
   156  	rawPress, err := d.readSensorData(RegPress)
   157  	if err != nil {
   158  		return 0, err
   159  	}
   160  
   161  	// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
   162  	partialData1 := tlin * tlin
   163  	partialData2 := partialData1 / 64
   164  	partialData3 := (partialData2 * tlin) / 256
   165  	partialData4 := (int64(d.cali.p8) * partialData3) / 32
   166  	partialData5 := (int64(d.cali.p7) * partialData1) * 16
   167  	partialData6 := (int64(d.cali.p6) * tlin) * 4194304
   168  	offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
   169  	partialData2 = (int64(d.cali.p4) * partialData3) / 32
   170  	partialData4 = (int64(d.cali.p3) * partialData1) * 4
   171  	partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
   172  	sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
   173  	partialData1 = (sensitivity / 16777216) * rawPress
   174  	partialData2 = int64(d.cali.p10) * tlin
   175  	partialData3 = partialData2 + (65536 * int64(d.cali.p9))
   176  	partialData4 = (partialData3 * rawPress) / 8192
   177  
   178  	// dividing by 10 followed by multiplying by 10
   179  	// To avoid overflow caused by (pressure * partial_data4)
   180  	partialData5 = (rawPress * (partialData4 / 10)) / 512
   181  	partialData5 = partialData5 * 10
   182  	partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
   183  	partialData2 = (int64(d.cali.p11) * partialData6) / 65536
   184  	partialData3 = (partialData2 * rawPress) / 128
   185  	partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
   186  	compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
   187  	return int32(compPress), nil
   188  }
   189  
   190  // SoftReset commands the BMP388 to reset of all user configuration settings
   191  func (d *Device) SoftReset() error {
   192  	err := d.writeRegister(RegCmd, SoftReset)
   193  	if err != nil {
   194  		return errSoftReset
   195  	}
   196  	return nil
   197  }
   198  
   199  // Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
   200  // communicate over i2c and returns the correct value
   201  func (d *Device) Connected() bool {
   202  	data, err := d.readRegister(RegChipId, 1)
   203  	return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
   204  }
   205  
   206  // SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
   207  // measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
   208  func (d *Device) SetMode(mode Mode) error {
   209  	d.Config.Mode = mode
   210  	return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
   211  }
   212  
   213  func (d *Device) readSensorData(register byte) (data int64, err error) {
   214  
   215  	if !d.Connected() {
   216  		return 0, errNotConnected
   217  	}
   218  
   219  	// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
   220  	// forced mode
   221  	if d.Config.Mode != Normal {
   222  		err = d.SetMode(Forced)
   223  		if err != nil {
   224  			return
   225  		}
   226  	}
   227  
   228  	bytes, err := d.readRegister(register, 3)
   229  	if err != nil {
   230  		return
   231  	}
   232  	data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
   233  	return
   234  }
   235  
   236  // configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
   237  func (d *Device) configurationError() bool {
   238  	data, err := d.readRegister(RegErr, 1)
   239  	return err == nil && (data[0]&0x04) != 0
   240  }
   241  
   242  func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
   243  	data = make([]byte, len)
   244  	err = legacy.ReadRegister(d.bus, d.Address, register, data)
   245  	return
   246  }
   247  
   248  func (d *Device) writeRegister(register byte, data byte) error {
   249  	return legacy.WriteRegister(d.bus, d.Address, register, []byte{data})
   250  }