/ 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)