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 }