/ adafruit_max31855.py
adafruit_max31855.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Radomir Dopieralski 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  """
 24  ``adafruit_max31855``
 25  ===========================
 26  
 27  This is a CircuitPython driver for the Maxim Integrated MAX31855 thermocouple
 28  amplifier module.
 29  
 30  * Author(s): Radomir Dopieralski
 31  
 32  Implementation Notes
 33  --------------------
 34  
 35  **Hardware:**
 36  
 37  * Adafruit `MAX31855 Thermocouple Amplifier Breakout
 38    <https://www.adafruit.com/product/269>`_ (Product ID: 269)
 39  
 40  **Software and Dependencies:**
 41  
 42  * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
 43    https://github.com/adafruit/circuitpython/releases
 44  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 45  """
 46  import math
 47  
 48  try:
 49      import struct
 50  except ImportError:
 51      import ustruct as struct
 52  
 53  from adafruit_bus_device.spi_device import SPIDevice
 54  
 55  __version__ = "0.0.0-auto.0"
 56  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MAX31855.git"
 57  
 58  
 59  class MAX31855:
 60      """
 61      Driver for the MAX31855 thermocouple amplifier.
 62      """
 63  
 64      def __init__(self, spi, cs):
 65          self.spi_device = SPIDevice(spi, cs)
 66          self.data = bytearray(4)
 67  
 68      def _read(self, internal=False):
 69          with self.spi_device as spi:
 70              spi.readinto(self.data)  # pylint: disable=no-member
 71          if self.data[3] & 0x01:
 72              raise RuntimeError("thermocouple not connected")
 73          if self.data[3] & 0x02:
 74              raise RuntimeError("short circuit to ground")
 75          if self.data[3] & 0x04:
 76              raise RuntimeError("short circuit to power")
 77          if self.data[1] & 0x01:
 78              raise RuntimeError("faulty reading")
 79          temp, refer = struct.unpack(">hh", self.data)
 80          refer >>= 4
 81          temp >>= 2
 82          if internal:
 83              return refer
 84          return temp
 85  
 86      @property
 87      def temperature(self):
 88          """Thermocouple temperature in degrees Celsius."""
 89          return self._read() / 4
 90  
 91      @property
 92      def reference_temperature(self):
 93          """Internal reference temperature in degrees Celsius."""
 94          return self._read(True) * 0.0625
 95  
 96      @property
 97      def temperature_NIST(self):
 98          """
 99          Thermocouple temperature in degrees Celsius, computed using
100          raw voltages and NIST approximation for Type K, see:
101          https://srdata.nist.gov/its90/download/type_k.tab
102          """
103          # pylint: disable=bad-whitespace, bad-continuation, invalid-name
104          # temperature of remote thermocouple junction
105          TR = self.temperature
106          # temperature of device (cold junction)
107          TAMB = self.reference_temperature
108          # thermocouple voltage based on MAX31855's uV/degC for type K (table 1)
109          VOUT = 0.041276 * (TR - TAMB)
110          # cold junction equivalent thermocouple voltage
111          if TAMB >= 0:
112              VREF = (
113                  -0.176004136860e-01
114                  + 0.389212049750e-01 * TAMB
115                  + 0.185587700320e-04 * math.pow(TAMB, 2)
116                  + -0.994575928740e-07 * math.pow(TAMB, 3)
117                  + 0.318409457190e-09 * math.pow(TAMB, 4)
118                  + -0.560728448890e-12 * math.pow(TAMB, 5)
119                  + 0.560750590590e-15 * math.pow(TAMB, 6)
120                  + -0.320207200030e-18 * math.pow(TAMB, 7)
121                  + 0.971511471520e-22 * math.pow(TAMB, 8)
122                  + -0.121047212750e-25 * math.pow(TAMB, 9)
123                  + 0.1185976
124                  * math.exp(-0.1183432e-03 * math.pow(TAMB - 0.1269686e03, 2))
125              )
126          else:
127              VREF = (
128                  0.394501280250e-01 * TAMB
129                  + 0.236223735980e-04 * math.pow(TAMB, 2)
130                  + -0.328589067840e-06 * math.pow(TAMB, 3)
131                  + -0.499048287770e-08 * math.pow(TAMB, 4)
132                  + -0.675090591730e-10 * math.pow(TAMB, 5)
133                  + -0.574103274280e-12 * math.pow(TAMB, 6)
134                  + -0.310888728940e-14 * math.pow(TAMB, 7)
135                  + -0.104516093650e-16 * math.pow(TAMB, 8)
136                  + -0.198892668780e-19 * math.pow(TAMB, 9)
137                  + -0.163226974860e-22 * math.pow(TAMB, 10)
138              )
139          # total thermoelectric voltage
140          VTOTAL = VOUT + VREF
141          # determine coefficients
142          # https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html
143          if -5.891 <= VTOTAL <= 0:
144              DCOEF = (
145                  0.0000000e00,
146                  2.5173462e01,
147                  -1.1662878e00,
148                  -1.0833638e00,
149                  -8.9773540e-01,
150                  -3.7342377e-01,
151                  -8.6632643e-02,
152                  -1.0450598e-02,
153                  -5.1920577e-04,
154              )
155          elif 0 < VTOTAL <= 20.644:
156              DCOEF = (
157                  0.000000e00,
158                  2.508355e01,
159                  7.860106e-02,
160                  -2.503131e-01,
161                  8.315270e-02,
162                  -1.228034e-02,
163                  9.804036e-04,
164                  -4.413030e-05,
165                  1.057734e-06,
166                  -1.052755e-08,
167              )
168          elif 20.644 < VTOTAL <= 54.886:
169              DCOEF = (
170                  -1.318058e02,
171                  4.830222e01,
172                  -1.646031e00,
173                  5.464731e-02,
174                  -9.650715e-04,
175                  8.802193e-06,
176                  -3.110810e-08,
177              )
178          else:
179              raise RuntimeError(
180                  "Total thermoelectric voltage out of range:{}".format(VTOTAL)
181              )
182          # compute temperature
183          TEMPERATURE = 0
184          for n, c in enumerate(DCOEF):
185              TEMPERATURE += c * math.pow(VTOTAL, n)
186          return TEMPERATURE