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

     1  // Package bme280 provides a driver for the BME280 digital combined
     2  // humidity and pressure sensor by Bosch.
     3  //
     4  // Datasheet:
     5  // https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
     6  package bme280
     7  
     8  import (
     9  	"math"
    10  	"time"
    11  
    12  	"tinygo.org/x/drivers"
    13  	"tinygo.org/x/drivers/internal/legacy"
    14  )
    15  
    16  // calibrationCoefficients reads at startup and stores the calibration coefficients
    17  type calibrationCoefficients struct {
    18  	t1 uint16
    19  	t2 int16
    20  	t3 int16
    21  	p1 uint16
    22  	p2 int16
    23  	p3 int16
    24  	p4 int16
    25  	p5 int16
    26  	p6 int16
    27  	p7 int16
    28  	p8 int16
    29  	p9 int16
    30  	h1 uint8
    31  	h2 int16
    32  	h3 uint8
    33  	h4 int16
    34  	h5 int16
    35  	h6 int8
    36  }
    37  
    38  type Oversampling byte
    39  type Mode byte
    40  type FilterCoefficient byte
    41  type Period byte
    42  
    43  // Config contains settings for filtering, sampling, and modes of operation
    44  type Config struct {
    45  	Pressure    Oversampling
    46  	Temperature Oversampling
    47  	Humidity    Oversampling
    48  	Period      Period
    49  	Mode        Mode
    50  	IIR         FilterCoefficient
    51  }
    52  
    53  // Device wraps an I2C connection to a BME280 device.
    54  type Device struct {
    55  	bus                     drivers.I2C
    56  	Address                 uint16
    57  	calibrationCoefficients calibrationCoefficients
    58  	Config                  Config
    59  }
    60  
    61  // New creates a new BME280 connection. The I2C bus must already be
    62  // configured.
    63  //
    64  // This function only creates the Device object, it does not touch the device.
    65  func New(bus drivers.I2C) Device {
    66  	return Device{
    67  		bus:     bus,
    68  		Address: Address,
    69  	}
    70  }
    71  
    72  // ConfigureWithSettings sets up the device for communication and
    73  // read the calibration coefficients.
    74  //
    75  // The default configuration is the Indoor Navigation settings
    76  // from the BME280 datasheet.
    77  func (d *Device) Configure() {
    78  	d.ConfigureWithSettings(Config{})
    79  }
    80  
    81  // ConfigureWithSettings sets up the device for communication and
    82  // read the calibration coefficients.
    83  //
    84  // The default configuration if config is left at defaults is
    85  // the Indoor Navigation settings from the BME280 datasheet.
    86  func (d *Device) ConfigureWithSettings(config Config) {
    87  	d.Config = config
    88  
    89  	// If config is not initialized, use Indoor Navigation defaults.
    90  	if d.Config == (Config{}) {
    91  		d.Config = Config{
    92  			Mode:        ModeNormal,
    93  			Period:      Period0_5ms,
    94  			Temperature: Sampling2X,
    95  			Humidity:    Sampling1X,
    96  			Pressure:    Sampling16X,
    97  			IIR:         Coeff16,
    98  		}
    99  	}
   100  
   101  	var data [24]byte
   102  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION, data[:])
   103  	if err != nil {
   104  		return
   105  	}
   106  
   107  	var h1 [1]byte
   108  	err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H1, h1[:])
   109  	if err != nil {
   110  		return
   111  	}
   112  
   113  	var h2lsb [7]byte
   114  	err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
   115  	if err != nil {
   116  		return
   117  	}
   118  
   119  	d.calibrationCoefficients.t1 = readUintLE(data[0], data[1])
   120  	d.calibrationCoefficients.t2 = readIntLE(data[2], data[3])
   121  	d.calibrationCoefficients.t3 = readIntLE(data[4], data[5])
   122  	d.calibrationCoefficients.p1 = readUintLE(data[6], data[7])
   123  	d.calibrationCoefficients.p2 = readIntLE(data[8], data[9])
   124  	d.calibrationCoefficients.p3 = readIntLE(data[10], data[11])
   125  	d.calibrationCoefficients.p4 = readIntLE(data[12], data[13])
   126  	d.calibrationCoefficients.p5 = readIntLE(data[14], data[15])
   127  	d.calibrationCoefficients.p6 = readIntLE(data[16], data[17])
   128  	d.calibrationCoefficients.p7 = readIntLE(data[18], data[19])
   129  	d.calibrationCoefficients.p8 = readIntLE(data[20], data[21])
   130  	d.calibrationCoefficients.p9 = readIntLE(data[22], data[23])
   131  
   132  	d.calibrationCoefficients.h1 = h1[0]
   133  	d.calibrationCoefficients.h2 = readIntLE(h2lsb[0], h2lsb[1])
   134  	d.calibrationCoefficients.h3 = h2lsb[2]
   135  	d.calibrationCoefficients.h6 = int8(h2lsb[6])
   136  	d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
   137  	d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
   138  
   139  	d.Reset()
   140  
   141  	legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
   142  	legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
   143  
   144  	// Normal mode, start measuring now
   145  	if d.Config.Mode == ModeNormal {
   146  		legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
   147  			byte(d.Config.Temperature<<5) |
   148  				byte(d.Config.Pressure<<2) |
   149  				byte(d.Config.Mode)})
   150  	}
   151  }
   152  
   153  // Connected returns whether a BME280 has been found.
   154  // It does a "who am I" request and checks the response.
   155  func (d *Device) Connected() bool {
   156  	data := []byte{0}
   157  	legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
   158  	return data[0] == CHIP_ID
   159  }
   160  
   161  // Reset the device
   162  func (d *Device) Reset() {
   163  	legacy.WriteRegister(d.bus, uint8(d.Address), CMD_RESET, []byte{0xB6})
   164  }
   165  
   166  // SetMode can set the device to Sleep, Normal or Forced mode
   167  //
   168  // Calling this method is optional, Configure can be used to set the
   169  // initial mode if no mode change is desired.  This method is most
   170  // useful to switch between Sleep and Normal modes.
   171  func (d *Device) SetMode(mode Mode) {
   172  	d.Config.Mode = mode
   173  
   174  	legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
   175  		byte(d.Config.Temperature<<5) |
   176  			byte(d.Config.Pressure<<2) |
   177  			byte(d.Config.Mode)})
   178  }
   179  
   180  // ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
   181  func (d *Device) ReadTemperature() (int32, error) {
   182  	data, err := d.readData()
   183  	if err != nil {
   184  		return 0, err
   185  	}
   186  
   187  	temp, _ := d.calculateTemp(data)
   188  	return temp, nil
   189  }
   190  
   191  // ReadPressure returns the pressure in milli pascals mPa
   192  func (d *Device) ReadPressure() (int32, error) {
   193  	data, err := d.readData()
   194  	if err != nil {
   195  		return 0, err
   196  	}
   197  	_, tFine := d.calculateTemp(data)
   198  	pressure := d.calculatePressure(data, tFine)
   199  	return pressure, nil
   200  }
   201  
   202  // ReadHumidity returns the relative humidity in hundredths of a percent
   203  func (d *Device) ReadHumidity() (int32, error) {
   204  	data, err := d.readData()
   205  	if err != nil {
   206  		return 0, err
   207  	}
   208  	_, tFine := d.calculateTemp(data)
   209  	humidity := d.calculateHumidity(data, tFine)
   210  	return humidity, nil
   211  }
   212  
   213  // ReadAltitude returns the current altitude in meters based on the
   214  // current barometric pressure and estimated pressure at sea level.
   215  // Calculation is based on code from Adafruit BME280 library
   216  //
   217  //	https://github.com/adafruit/Adafruit_BME280_Library
   218  func (d *Device) ReadAltitude() (alt int32, err error) {
   219  	mPa, _ := d.ReadPressure()
   220  	atmP := float32(mPa) / 100000
   221  	alt = int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903)))
   222  	return
   223  }
   224  
   225  // convert2Bytes converts two bytes to int32
   226  func convert2Bytes(msb byte, lsb byte) int32 {
   227  	return int32(readUint(msb, lsb))
   228  }
   229  
   230  // convert3Bytes converts three bytes to int32
   231  func convert3Bytes(msb byte, b1 byte, lsb byte) int32 {
   232  	return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4)
   233  }
   234  
   235  // readUint converts two bytes to uint16
   236  func readUint(msb byte, lsb byte) uint16 {
   237  	return (uint16(msb) << 8) | uint16(lsb)
   238  }
   239  
   240  // readUintLE converts two little endian bytes to uint16
   241  func readUintLE(msb byte, lsb byte) uint16 {
   242  	temp := readUint(msb, lsb)
   243  	return (temp >> 8) | (temp << 8)
   244  }
   245  
   246  // readIntLE converts two little endian bytes to int16
   247  func readIntLE(msb byte, lsb byte) int16 {
   248  	return int16(readUintLE(msb, lsb))
   249  }
   250  
   251  // readData does a burst read from 0xF7 to 0xF0 according to the datasheet
   252  // resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
   253  func (d *Device) readData() (data [8]byte, err error) {
   254  	if d.Config.Mode == ModeForced {
   255  		// Write the CTRL_MEAS register to trigger a measurement
   256  		legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
   257  			byte(d.Config.Temperature<<5) |
   258  				byte(d.Config.Pressure<<2) |
   259  				byte(d.Config.Mode)})
   260  
   261  		time.Sleep(d.measurementDelay())
   262  	}
   263  
   264  	err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE, data[:])
   265  	if err != nil {
   266  		println(err)
   267  		return
   268  	}
   269  	return
   270  }
   271  
   272  // calculateTemp uses the data slice and applies calibrations values on it to convert the value to milli degrees
   273  // it also calculates the variable tFine which is used by the pressure and humidity calculation
   274  func (d *Device) calculateTemp(data [8]byte) (int32, int32) {
   275  
   276  	rawTemp := convert3Bytes(data[3], data[4], data[5])
   277  
   278  	var1 := (((rawTemp >> 3) - (int32(d.calibrationCoefficients.t1) << 1)) * int32(d.calibrationCoefficients.t2)) >> 11
   279  	var2 := (((((rawTemp >> 4) - int32(d.calibrationCoefficients.t1)) * ((rawTemp >> 4) - int32(d.calibrationCoefficients.t1))) >> 12) * int32(d.calibrationCoefficients.t3)) >> 14
   280  
   281  	tFine := var1 + var2
   282  	T := (tFine*5 + 128) >> 8
   283  	return (10 * T), tFine
   284  }
   285  
   286  // calculatePressure uses the data slice and applies calibrations values on it to convert the value to milli pascals mPa
   287  func (d *Device) calculatePressure(data [8]byte, tFine int32) int32 {
   288  
   289  	rawPressure := convert3Bytes(data[0], data[1], data[2])
   290  
   291  	var1 := int64(tFine) - 128000
   292  	var2 := var1 * var1 * int64(d.calibrationCoefficients.p6)
   293  	var2 = var2 + ((var1 * int64(d.calibrationCoefficients.p5)) << 17)
   294  	var2 = var2 + (int64(d.calibrationCoefficients.p4) << 35)
   295  	var1 = ((var1 * var1 * int64(d.calibrationCoefficients.p3)) >> 8) + ((var1 * int64(d.calibrationCoefficients.p2)) << 12)
   296  	var1 = ((int64(1) << 47) + var1) * int64(d.calibrationCoefficients.p1) >> 33
   297  
   298  	if var1 == 0 {
   299  		return 0 // avoid exception caused by division by zero
   300  	}
   301  	p := int64(1048576 - rawPressure)
   302  	p = (((p << 31) - var2) * 3125) / var1
   303  	var1 = (int64(d.calibrationCoefficients.p9) * (p >> 13) * (p >> 13)) >> 25
   304  	var2 = (int64(d.calibrationCoefficients.p8) * p) >> 19
   305  
   306  	p = ((p + var1 + var2) >> 8) + (int64(d.calibrationCoefficients.p7) << 4)
   307  	p = (p / 256)
   308  	return int32(1000 * p)
   309  }
   310  
   311  // calculateHumidity uses the data slice and applies calibrations values on it to convert the value to relative humidity in hundredths of a percent
   312  func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
   313  
   314  	rawHumidity := convert2Bytes(data[6], data[7])
   315  
   316  	h := float32(tFine) - 76800
   317  
   318  	if h == 0 {
   319  		println("invalid value")
   320  	}
   321  
   322  	var1 := float32(rawHumidity) - (float32(d.calibrationCoefficients.h4)*64.0 +
   323  		(float32(d.calibrationCoefficients.h5) / 16384.0 * h))
   324  
   325  	var2 := float32(d.calibrationCoefficients.h2) / 65536.0 *
   326  		(1.0 + float32(d.calibrationCoefficients.h6)/67108864.0*h*
   327  			(1.0+float32(d.calibrationCoefficients.h3)/67108864.0*h))
   328  
   329  	h = var1 * var2
   330  	h = h * (1 - float32(d.calibrationCoefficients.h1)*h/524288)
   331  	return int32(100 * h)
   332  
   333  }
   334  
   335  // measurementDelay returns how much time each measurement will take
   336  // on the device.
   337  //
   338  // This is used in forced mode to wait until a measurement is complete.
   339  func (d *Device) measurementDelay() time.Duration {
   340  	const MeasOffset = 1250
   341  	const MeasDur = 2300
   342  	const HumMeasOffset = 575
   343  	const MeasScalingFactor = 1000
   344  
   345  	// delay is based on over-sampling rate - this table converts from
   346  	// setting to number samples
   347  	sampleRateConv := []int{0, 1, 2, 4, 8, 16}
   348  
   349  	tempOsr := 16
   350  	if d.Config.Temperature <= Sampling16X {
   351  		tempOsr = sampleRateConv[d.Config.Temperature]
   352  	}
   353  
   354  	presOsr := 16
   355  	if d.Config.Temperature <= Sampling16X {
   356  		presOsr = sampleRateConv[d.Config.Pressure]
   357  	}
   358  
   359  	humOsr := 16
   360  	if d.Config.Temperature <= Sampling16X {
   361  		humOsr = sampleRateConv[d.Config.Humidity]
   362  	}
   363  
   364  	max_delay := ((MeasOffset + (MeasDur * tempOsr) +
   365  		((MeasDur * presOsr) + HumMeasOffset) +
   366  		((MeasDur * humOsr) + HumMeasOffset)) / MeasScalingFactor)
   367  
   368  	return time.Duration(max_delay) * time.Millisecond
   369  }