/ adafruit_ds18x20.py
adafruit_ds18x20.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Carter Nelson 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_ds18x20`
 24  ====================================================
 25  
 26  Driver for Dallas 1-Wire temperature sensor.
 27  
 28  * Author(s): Carter Nelson
 29  """
 30  
 31  __version__ = "0.0.0-auto.0"
 32  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DS18x20.git"
 33  
 34  import time
 35  from micropython import const
 36  from adafruit_onewire.device import OneWireDevice
 37  
 38  _CONVERT = b"\x44"
 39  _RD_SCRATCH = b"\xBE"
 40  _WR_SCRATCH = b"\x4E"
 41  _CONVERSION_TIMEOUT = const(1)
 42  RESOLUTION = (9, 10, 11, 12)
 43  # Maximum conversion delay in seconds, from DS18B20 datasheet.
 44  _CONVERSION_DELAY = {9: 0.09375, 10: 0.1875, 11: 0.375, 12: 0.750}
 45  
 46  
 47  class DS18X20:
 48      """Class which provides interface to DS18X20 temperature sensor."""
 49  
 50      def __init__(self, bus, address):
 51          if address.family_code == 0x10 or address.family_code == 0x28:
 52              self._address = address
 53              self._device = OneWireDevice(bus, address)
 54              self._buf = bytearray(9)
 55              self._conv_delay = _CONVERSION_DELAY[12]  # pessimistic default
 56          else:
 57              raise ValueError("Incorrect family code in device address.")
 58  
 59      @property
 60      def temperature(self):
 61          """The temperature in degrees Celsius."""
 62          self._convert_temp()
 63          return self._read_temp()
 64  
 65      @property
 66      def resolution(self):
 67          """The programmable resolution. 9, 10, 11, or 12 bits."""
 68          return RESOLUTION[self._read_scratch()[4] >> 5 & 0x03]
 69  
 70      @resolution.setter
 71      def resolution(self, bits):
 72          if bits not in RESOLUTION:
 73              raise ValueError("Incorrect resolution. Must be 9, 10, 11, or 12.")
 74          self._buf[0] = 0  # TH register
 75          self._buf[1] = 0  # TL register
 76          self._buf[2] = RESOLUTION.index(bits) << 5 | 0x1F  # configuration register
 77          self._write_scratch(self._buf)
 78  
 79      def _convert_temp(self, timeout=_CONVERSION_TIMEOUT):
 80          with self._device as dev:
 81              dev.write(_CONVERT)
 82              start_time = time.monotonic()
 83              if timeout > 0:
 84                  dev.readinto(self._buf, end=1)
 85                  # 0 = conversion in progress, 1 = conversion done
 86                  while self._buf[0] == 0x00:
 87                      if time.monotonic() - start_time > timeout:
 88                          raise RuntimeError(
 89                              "Timeout waiting for conversion to complete."
 90                          )
 91                      dev.readinto(self._buf, end=1)
 92          return time.monotonic() - start_time
 93  
 94      def _read_temp(self):
 95          # pylint: disable=invalid-name
 96          buf = self._read_scratch()
 97          if self._address.family_code == 0x10:
 98              if buf[1]:
 99                  t = buf[0] >> 1 | 0x80
100                  t = -((~t + 1) & 0xFF)
101              else:
102                  t = buf[0] >> 1
103              return t - 0.25 + (buf[7] - buf[6]) / buf[7]
104          t = buf[1] << 8 | buf[0]
105          if t & 0x8000:  # sign bit set
106              t = -((t ^ 0xFFFF) + 1)
107          return t / 16
108  
109      def _read_scratch(self):
110          with self._device as dev:
111              dev.write(_RD_SCRATCH)
112              dev.readinto(self._buf)
113          return self._buf
114  
115      def _write_scratch(self, buf):
116          with self._device as dev:
117              dev.write(_WR_SCRATCH)
118              dev.write(buf, end=3)
119  
120      def start_temperature_read(self):
121          """Start asynchronous conversion, returns immediately.
122          Returns maximum conversion delay [seconds] based on resolution."""
123          with self._device as dev:
124              dev.write(_CONVERT)
125          return _CONVERSION_DELAY[self.resolution]
126  
127      def read_temperature(self):
128          """Read the temperature. No polling of the conversion busy bit
129          (assumes that the conversion has completed)."""
130          return self._read_temp()