/ adafruit_dps310.py
adafruit_dps310.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2020 Bryan Siepert for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `adafruit_dps310` 24 ================================================================================ 25 26 Library for the DPS310 Precision Barometric Pressure Sensor 27 28 * Author(s): Bryan Siepert 29 30 Implementation Notes 31 -------------------- 32 33 **Hardware:** 34 35 * Adafruit's DPS310 Breakout: https://www.adafruit.com/product/4494 36 37 **Software and Dependencies:** 38 39 * Adafruit CircuitPython firmware for the supported boards: 40 https://circuitpython.org/downloads 41 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice 42 * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register""" 43 44 __version__ = "0.0.0-auto.0" 45 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DPS310.git" 46 47 # Common imports; remove if unused or pylint will complain 48 from time import sleep 49 import adafruit_bus_device.i2c_device as i2c_device 50 from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct 51 from adafruit_register.i2c_bit import RWBit, ROBit 52 from adafruit_register.i2c_bits import RWBits, ROBits 53 54 _DPS310_DEFAULT_ADDRESS = 0x77 # DPS310 default i2c address 55 _DPS310_DEVICE_ID = 0x10 # DPS310 device identifier 56 57 _DPS310_PRSB2 = 0x00 # Highest byte of pressure data 58 _DPS310_TMPB2 = 0x03 # Highest byte of temperature data 59 _DPS310_PRSCFG = 0x06 # Pressure configuration 60 _DPS310_TMPCFG = 0x07 # Temperature configuration 61 _DPS310_MEASCFG = 0x08 # Sensor configuration 62 _DPS310_CFGREG = 0x09 # Interrupt/FIFO configuration 63 _DPS310_RESET = 0x0C # Soft reset 64 _DPS310_PRODREVID = 0x0D # Register that contains the part ID 65 _DPS310_TMPCOEFSRCE = 0x28 # Temperature calibration src 66 67 # pylint: enable=bad-whitespace 68 # pylint: disable=no-member,unnecessary-pass 69 70 71 class CV: 72 """struct helper""" 73 74 @classmethod 75 def add_values(cls, value_tuples): 76 """Add CV values to the class""" 77 cls.string = {} 78 cls.lsb = {} 79 80 for value_tuple in value_tuples: 81 name, value, string, lsb = value_tuple 82 setattr(cls, name, value) 83 cls.string[value] = string 84 cls.lsb[value] = lsb 85 86 @classmethod 87 def is_valid(cls, value): 88 """Validate that a given value is a member""" 89 return value in cls.string 90 91 92 class Mode(CV): 93 """Options for ``mode`` 94 95 +--------------------------+------------------------------------------------------------------+ 96 | Mode | Description | 97 +--------------------------+------------------------------------------------------------------+ 98 | ``Mode.IDLE`` | Puts the sensor into a shutdown state | 99 +--------------------------+------------------------------------------------------------------+ 100 | ``Mode.ONE_PRESSURE`` | Setting `mode` to ``Mode.ONE_PRESSURE`` takes a single pressure | 101 | | measurement then switches to ``Mode.IDLE`` | 102 +--------------------------+------------------------------------------------------------------+ 103 | ``Mode.ONE_TEMPERATURE`` | Setting `mode` to ``Mode.ONE_TEMPERATURE`` takes a single | 104 | | temperature measurement then switches to ``Mode.IDLE`` | 105 +--------------------------+------------------------------------------------------------------+ 106 | ``Mode.CONT_PRESSURE`` | Take pressure measurements at the current `pressure_rate`. | 107 | | `temperature` will not be updated | 108 +--------------------------+------------------------------------------------------------------+ 109 | ``Mode.CONT_TEMP`` | Take temperature measurements at the current `temperature_rate`. | 110 | | `pressure` will not be updated | 111 +--------------------------+------------------------------------------------------------------+ 112 | ``Mode.CONT_PRESTEMP`` | Take temperature and pressure measurements at the current | 113 | | `pressure_rate` and `temperature_rate` | 114 +--------------------------+------------------------------------------------------------------+ 115 116 """ 117 118 pass # pylint: disable=unnecessary-pass 119 120 121 Mode.add_values( 122 ( 123 ("IDLE", 0, "Idle", None), 124 ("ONE_PRESSURE", 1, "One-Shot Pressure", None), 125 ("ONE_TEMPERATURE", 2, "One-Shot Temperature", None), 126 ("CONT_PRESSURE", 5, "Continuous Pressure", None), 127 ("CONT_TEMP", 6, "Continuous Temperature", None), 128 ("CONT_PRESTEMP", 7, "Continuous Pressure & Temperature", None), 129 ) 130 ) 131 132 133 class Rate(CV): 134 """Options for `pressure_rate` and `temperature_rate`""" 135 136 pass 137 138 139 Rate.add_values( 140 ( 141 ("RATE_1_HZ", 0, 1, None), 142 ("RATE_2_HZ", 1, 2, None), 143 ("RATE_4_HZ", 2, 4, None), 144 ("RATE_8_HZ", 3, 8, None), 145 ("RATE_16_HZ", 4, 16, None), 146 ("RATE_32_HZ", 5, 32, None), 147 ("RATE_64_HZ", 6, 64, None), 148 ("RATE_128_HZ", 7, 128, None), 149 ) 150 ) 151 152 153 class SampleCount(CV): 154 """Options for `temperature_oversample_count` and `pressure_oversample_count`""" 155 156 pass 157 158 159 SampleCount.add_values( 160 ( 161 ("COUNT_1", 0, 1, None), 162 ("COUNT_2", 1, 2, None), 163 ("COUNT_4", 2, 4, None), 164 ("COUNT_8", 3, 8, None), 165 ("COUNT_16", 4, 16, None), 166 ("COUNT_32", 5, 32, None), 167 ("COUNT_64", 6, 64, None), 168 ("COUNT_128", 7, 128, None), 169 ) 170 ) 171 # pylint: enable=unnecessary-pass 172 class DPS310: 173 # pylint: disable=too-many-instance-attributes 174 """Library for the DPS310 Precision Barometric Pressure Sensor. 175 176 :param ~busio.I2C i2c_bus: The I2C bus the DPS310 is connected to. 177 :param address: The I2C slave address of the sensor 178 179 """ 180 # Register definitions 181 _device_id = ROUnaryStruct(_DPS310_PRODREVID, ">B") 182 _reset_register = UnaryStruct(_DPS310_RESET, ">B") 183 _mode_bits = RWBits(3, _DPS310_MEASCFG, 0) 184 185 _pressure_ratebits = RWBits(3, _DPS310_PRSCFG, 4) 186 _pressure_osbits = RWBits(4, _DPS310_PRSCFG, 0) 187 188 _temp_ratebits = RWBits(3, _DPS310_TMPCFG, 4) 189 _temp_osbits = RWBits(4, _DPS310_TMPCFG, 0) 190 191 _temp_measurement_src_bit = RWBit(_DPS310_TMPCFG, 7) 192 193 _pressure_shiftbit = RWBit(_DPS310_CFGREG, 2) 194 _temp_shiftbit = RWBit(_DPS310_CFGREG, 3) 195 196 _coefficients_ready = RWBit(_DPS310_MEASCFG, 7) 197 _sensor_ready = RWBit(_DPS310_MEASCFG, 6) 198 _temp_ready = RWBit(_DPS310_MEASCFG, 5) 199 _pressure_ready = RWBit(_DPS310_MEASCFG, 4) 200 201 _raw_pressure = ROBits(24, _DPS310_PRSB2, 0, 3, lsb_first=False) 202 _raw_temperature = ROBits(24, _DPS310_TMPB2, 0, 3, lsb_first=False) 203 204 _calib_coeff_temp_src_bit = ROBit(_DPS310_TMPCOEFSRCE, 7) 205 206 _reg0e = RWBits(8, 0x0E, 0) 207 _reg0f = RWBits(8, 0x0F, 0) 208 _reg62 = RWBits(8, 0x62, 0) 209 210 def __init__(self, i2c_bus, address=_DPS310_DEFAULT_ADDRESS): 211 self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) 212 213 if self._device_id != _DPS310_DEVICE_ID: 214 raise RuntimeError("Failed to find DPS310 - check your wiring!") 215 self._pressure_scale = None 216 self._temp_scale = None 217 self._c0 = None 218 self._c1 = None 219 self._c00 = None 220 self._c00 = None 221 self._c10 = None 222 self._c10 = None 223 self._c01 = None 224 self._c11 = None 225 self._c20 = None 226 self._c21 = None 227 self._c30 = None 228 self._oversample_scalefactor = ( 229 524288, 230 1572864, 231 3670016, 232 7864320, 233 253952, 234 516096, 235 1040384, 236 2088960, 237 ) 238 self.initialize() 239 240 def initialize(self): 241 """Initialize the sensor to continuous measurement""" 242 243 self.reset() 244 245 self.pressure_rate = Rate.RATE_64_HZ 246 self.pressure_oversample_count = SampleCount.COUNT_64 247 self.temperature_rate = Rate.RATE_64_HZ 248 self.temperature_oversample_count = SampleCount.COUNT_64 249 self.mode = Mode.CONT_PRESTEMP 250 251 # wait until we have at least one good measurement 252 self.wait_temperature_ready() 253 self.wait_pressure_ready() 254 255 # (https://github.com/Infineon/DPS310-Pressure-Sensor#temperature-measurement-issue) 256 # similar to DpsClass::correctTemp(void) from infineon's c++ library 257 def _correct_temp(self): 258 """Correct temperature readings on ICs with a fuse bit problem""" 259 self._reg0e = 0xA5 260 self._reg0f = 0x96 261 self._reg62 = 0x02 262 self._reg0e = 0 263 self._reg0f = 0 264 265 # perform a temperature measurement 266 # the most recent temperature will be saved internally 267 # and used for compensation when calculating pressure 268 _unused = self._raw_temperature 269 270 def reset(self): 271 """Reset the sensor""" 272 self._reset_register = 0x89 273 # wait for hardware reset to finish 274 sleep(0.010) 275 while not self._sensor_ready: 276 sleep(0.001) 277 self._correct_temp() 278 self._read_calibration() 279 # make sure we're using the temperature source used for calibration 280 self._temp_measurement_src_bit = self._calib_coeff_temp_src_bit 281 282 @property 283 def pressure(self): 284 """Returns the current pressure reading in kPA""" 285 286 temp_reading = self._raw_temperature 287 raw_temperature = self._twos_complement(temp_reading, 24) 288 pressure_reading = self._raw_pressure 289 raw_pressure = self._twos_complement(pressure_reading, 24) 290 _scaled_rawtemp = raw_temperature / self._temp_scale 291 292 _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 293 294 p_red = raw_pressure / self._pressure_scale 295 296 pres_calc = ( 297 self._c00 298 + p_red * (self._c10 + p_red * (self._c20 + p_red * self._c30)) 299 + _scaled_rawtemp * (self._c01 + p_red * (self._c11 + p_red * self._c21)) 300 ) 301 302 final_pressure = pres_calc / 100 303 return final_pressure 304 305 @property 306 def temperature(self): 307 """The current temperature reading in degrees C""" 308 _scaled_rawtemp = self._raw_temperature / self._temp_scale 309 _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 310 return _temperature 311 312 @property 313 def temperature_ready(self): 314 """Returns true if there is a temperature reading ready""" 315 return self._temp_ready 316 317 def wait_temperature_ready(self): 318 """Wait until a temperature measurement is available. 319 320 To avoid waiting indefinitely this function raises an 321 error if the sensor isn't configured for temperate measurements, 322 ie. ``Mode.ONE_TEMPERATURE``, ``Mode.CONT_TEMP`` or ``Mode.CONT_PRESTEMP``. 323 See the `Mode` documentation for details. 324 """ 325 if ( 326 self._mode_bits == Mode.IDLE 327 or self._mode_bits == Mode.ONE_PRESSURE 328 or self._mode_bits == Mode.CONT_PRESSURE 329 ): 330 raise RuntimeError( 331 "Sensor mode is set to idle or pressure measurement,\ 332 can't wait for a temperature measurement" 333 ) 334 while self._temp_ready is False: 335 sleep(0.001) 336 337 @property 338 def pressure_ready(self): 339 """Returns true if pressure readings are ready""" 340 return self._pressure_ready 341 342 def wait_pressure_ready(self): 343 """Wait until a pressure measurement is available 344 345 To avoid waiting indefinitely this function raises an 346 error if the sensor isn't configured for pressure measurements, 347 ie. ``Mode.ONE_PRESSURE``, ``Mode.CONT_PRESSURE`` or ``Mode.CONT_PRESTEMP`` 348 See the `Mode` documentation for details. 349 """ 350 if ( 351 self._mode_bits == Mode.IDLE 352 or self._mode_bits == Mode.ONE_TEMPERATURE 353 or self._mode_bits == Mode.CONT_TEMP 354 ): 355 raise RuntimeError( 356 "Sensor mode is set to idle or temperature measurement,\ 357 can't wait for a pressure measurement" 358 ) 359 while self._pressure_ready is False: 360 sleep(0.001) 361 362 @property 363 def mode(self): 364 """The measurement mode. Must be a `Mode`. See the `Mode` documentation for details""" 365 return self._mode_bits 366 367 @mode.setter 368 def mode(self, value): 369 if not Mode.is_valid(value): 370 raise AttributeError("mode must be an `Mode`") 371 372 self._mode_bits = value 373 374 @property 375 def pressure_rate(self): 376 """Configure the pressure measurement rate. Must be a `Rate`""" 377 return self._pressure_ratebits 378 379 @pressure_rate.setter 380 def pressure_rate(self, value): 381 if not Rate.is_valid(value): 382 raise AttributeError("pressure_rate must be a Rate") 383 self._pressure_ratebits = value 384 385 @property 386 def pressure_oversample_count(self): 387 """The number of samples taken per pressure measurement. Must be a `SampleCount`""" 388 return self._pressure_osbits 389 390 @pressure_oversample_count.setter 391 def pressure_oversample_count(self, value): 392 if not SampleCount.is_valid(value): 393 raise AttributeError("pressure_oversample_count must be a SampleCount") 394 395 self._pressure_osbits = value 396 self._pressure_shiftbit = value > SampleCount.COUNT_8 397 self._pressure_scale = self._oversample_scalefactor[value] 398 399 @property 400 def temperature_rate(self): 401 """Configure the temperature measurement rate. Must be a `Rate`""" 402 return self._temp_ratebits 403 404 @temperature_rate.setter 405 def temperature_rate(self, value): 406 if not Rate.is_valid(value): 407 raise AttributeError("temperature_rate must be a Rate") 408 self._temp_ratebits = value 409 410 @property 411 def temperature_oversample_count(self): 412 """The number of samples taken per temperature measurement. Must be a `SampleCount`""" 413 return self._temp_osbits 414 415 @temperature_oversample_count.setter 416 def temperature_oversample_count(self, value): 417 if not SampleCount.is_valid(value): 418 raise AttributeError("temperature_oversample_count must be a SampleCount") 419 420 self._temp_osbits = value 421 self._temp_scale = self._oversample_scalefactor[value] 422 self._temp_shiftbit = value > SampleCount.COUNT_8 423 424 @staticmethod 425 def _twos_complement(val, bits): 426 if val & (1 << (bits - 1)): 427 val -= 1 << bits 428 429 return val 430 431 def _read_calibration(self): 432 433 while not self._coefficients_ready: 434 sleep(0.001) 435 436 buffer = bytearray(19) 437 coeffs = [None] * 18 438 for offset in range(18): 439 buffer = bytearray(2) 440 buffer[0] = 0x10 + offset 441 442 with self.i2c_device as i2c: 443 444 i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1) 445 446 coeffs[offset] = buffer[1] 447 448 self._c0 = (coeffs[0] << 4) | ((coeffs[1] >> 4) & 0x0F) 449 self._c0 = self._twos_complement(self._c0, 12) 450 451 self._c1 = self._twos_complement(((coeffs[1] & 0x0F) << 8) | coeffs[2], 12) 452 453 self._c00 = (coeffs[3] << 12) | (coeffs[4] << 4) | ((coeffs[5] >> 4) & 0x0F) 454 self._c00 = self._twos_complement(self._c00, 20) 455 456 self._c10 = ((coeffs[5] & 0x0F) << 16) | (coeffs[6] << 8) | coeffs[7] 457 self._c10 = self._twos_complement(self._c10, 20) 458 459 self._c01 = self._twos_complement((coeffs[8] << 8) | coeffs[9], 16) 460 self._c11 = self._twos_complement((coeffs[10] << 8) | coeffs[11], 16) 461 self._c20 = self._twos_complement((coeffs[12] << 8) | coeffs[13], 16) 462 self._c21 = self._twos_complement((coeffs[14] << 8) | coeffs[15], 16) 463 self._c30 = self._twos_complement((coeffs[16] << 8) | coeffs[17], 16)