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 }