/ adafruit_mcp9600.py
adafruit_mcp9600.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2019 Dan Cogliano for Adafruit Industries
  4  # Copyright (c) 2019 Kattni Rembor for Adafruit Industries
  5  #
  6  # Permission is hereby granted, free of charge, to any person obtaining a copy
  7  # of this software and associated documentation files (the "Software"), to deal
  8  # in the Software without restriction, including without limitation the rights
  9  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  # copies of the Software, and to permit persons to whom the Software is
 11  # furnished to do so, subject to the following conditions:
 12  #
 13  # The above copyright notice and this permission notice shall be included in
 14  # all copies or substantial portions of the Software.
 15  #
 16  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  # THE SOFTWARE.
 23  """
 24  `adafruit_mcp9600`
 25  ================================================================================
 26  
 27  CircuitPython driver for the MCP9600 thermocouple I2C amplifier
 28  
 29  
 30  * Author(s): Dan Cogliano, Kattni Rembor
 31  
 32  Implementation Notes
 33  --------------------
 34  
 35  **Hardware:**
 36  
 37  * Adafruit MCP9600 I2C Thermocouple Amplifier:
 38    https://www.adafruit.com/product/4101
 39  
 40  **Software and Dependencies:**
 41  
 42  * Adafruit CircuitPython firmware for the supported boards:
 43    https://github.com/adafruit/circuitpython/releases
 44  
 45  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 46  """
 47  
 48  from struct import unpack
 49  from micropython import const
 50  from adafruit_bus_device.i2c_device import I2CDevice
 51  from adafruit_register.i2c_struct import UnaryStruct
 52  from adafruit_register.i2c_bits import RWBits, ROBits
 53  from adafruit_register.i2c_bit import RWBit, ROBit
 54  
 55  __version__ = "0.0.0-auto.0"
 56  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP9600.git"
 57  
 58  _DEFAULT_ADDRESS = const(0x67)
 59  
 60  _REGISTER_HOT_JUNCTION = const(0x00)
 61  _REGISTER_DELTA_TEMP = const(0x01)
 62  _REGISTER_COLD_JUNCTION = const(0x02)
 63  _REGISTER_THERM_CFG = const(0x05)
 64  _REGISTER_VERSION = const(0x20)
 65  
 66  
 67  class MCP9600:
 68      """Interface to the MCP9600 thermocouple amplifier breakout"""
 69  
 70      # Shutdown mode options
 71      NORMAL = 0b00
 72      SHUTDOWN = 0b01
 73      BURST = 0b10
 74  
 75      # Burst mode sample options
 76      BURST_SAMPLES_1 = 0b000
 77      BURST_SAMPLES_2 = 0b001
 78      BURST_SAMPLES_4 = 0b010
 79      BURST_SAMPLES_8 = 0b011
 80      BURST_SAMPLES_16 = 0b100
 81      BURST_SAMPLES_32 = 0b101
 82      BURST_SAMPLES_64 = 0b110
 83      BURST_SAMPLES_128 = 0b111
 84  
 85      # Alert temperature monitor options
 86      AMBIENT = 1
 87      THERMOCOUPLE = 0
 88  
 89      # Temperature change type to trigger alert. Rising is heating up. Falling is cooling down.
 90      RISING = 1
 91      FALLING = 0
 92  
 93      # Alert output options
 94      ACTIVE_HIGH = 1
 95      ACTIVE_LOW = 0
 96  
 97      # Alert mode options
 98      INTERRUPT = 1  # Interrupt clear option must be set when using this mode!
 99      COMPARATOR = 0
100  
101      # Ambient (cold-junction) temperature sensor resolution options
102      AMBIENT_RESOLUTION_0_0625 = 0  # 0.0625 degrees Celsius
103      AMBIENT_RESOLUTION_0_25 = 1  # 0.25 degrees Celsius
104  
105      # STATUS - 0x4
106      burst_complete = RWBit(0x4, 7)
107      """Burst complete."""
108      temperature_update = RWBit(0x4, 6)
109      """Temperature update."""
110      input_range = ROBit(0x4, 4)
111      """Input range."""
112      alert_1 = ROBit(0x4, 0)
113      """Alert 1 status."""
114      alert_2 = ROBit(0x4, 1)
115      """Alert 2 status."""
116      alert_3 = ROBit(0x4, 2)
117      """Alert 3 status."""
118      alert_4 = ROBit(0x4, 3)
119      """Alert 4 status."""
120      # Device Configuration - 0x6
121      ambient_resolution = RWBit(0x6, 7)
122      """Ambient (cold-junction) temperature resolution. Options are ``AMBIENT_RESOLUTION_0_0625``
123      (0.0625 degrees Celsius) or ``AMBIENT_RESOLUTION_0_25`` (0.25 degrees Celsius)."""
124      burst_mode_samples = RWBits(3, 0x6, 2)
125      """The number of samples taken during a burst in burst mode. Options are ``BURST_SAMPLES_1``,
126      ``BURST_SAMPLES_2``, ``BURST_SAMPLES_4``, ``BURST_SAMPLES_8``, ``BURST_SAMPLES_16``,
127      ``BURST_SAMPLES_32``, ``BURST_SAMPLES_64``, ``BURST_SAMPLES_128``."""
128      shutdown_mode = RWBits(2, 0x6, 0)
129      """Shutdown modes. Options are ``NORMAL``, ``SHUTDOWN``, and ``BURST``."""
130      # Alert 1 Configuration - 0x8
131      _alert_1_interrupt_clear = RWBit(0x8, 7)
132      _alert_1_monitor = RWBit(0x8, 4)
133      _alert_1_temp_direction = RWBit(0x8, 3)
134      _alert_1_state = RWBit(0x8, 2)
135      _alert_1_mode = RWBit(0x8, 1)
136      _alert_1_enable = RWBit(0x8, 0)
137      # Alert 2 Configuration - 0x9
138      _alert_2_interrupt_clear = RWBit(0x9, 7)
139      _alert_2_monitor = RWBit(0x9, 4)
140      _alert_2_temp_direction = RWBit(0x9, 3)
141      _alert_2_state = RWBit(0x9, 2)
142      _alert_2_mode = RWBit(0x9, 1)
143      _alert_2_enable = RWBit(0x9, 0)
144      # Alert 3 Configuration - 0xa
145      _alert_3_interrupt_clear = RWBit(0xA, 7)
146      _alert_3_monitor = RWBit(0xA, 4)
147      _alert_3_temp_direction = RWBit(0xA, 3)
148      _alert_3_state = RWBit(0xA, 2)
149      _alert_3_mode = RWBit(0xA, 1)
150      _alert_3_enable = RWBit(0xA, 0)
151      # Alert 4 Configuration - 0xb
152      _alert_4_interrupt_clear = RWBit(0xB, 7)
153      _alert_4_monitor = RWBit(0xB, 4)
154      _alert_4_temp_direction = RWBit(0xB, 3)
155      _alert_4_state = RWBit(0xB, 2)
156      _alert_4_mode = RWBit(0xB, 1)
157      _alert_4_enable = RWBit(0xB, 0)
158      # Alert 1 Hysteresis - 0xc
159      _alert_1_hysteresis = UnaryStruct(0xC, ">H")
160      # Alert 2 Hysteresis - 0xd
161      _alert_2_hysteresis = UnaryStruct(0xD, ">H")
162      # Alert 3 Hysteresis - 0xe
163      _alert_3_hysteresis = UnaryStruct(0xE, ">H")
164      # Alert 4 Hysteresis - 0xf
165      _alert_4_hysteresis = UnaryStruct(0xF, ">H")
166      # Alert 1 Limit - 0x10
167      _alert_1_temperature_limit = UnaryStruct(0x10, ">H")
168      # Alert 2 Limit - 0x11
169      _alert_2_limit = UnaryStruct(0x11, ">H")
170      # Alert 3 Limit - 0x12
171      _alert_3_limit = UnaryStruct(0x12, ">H")
172      # Alert 4 Limit - 0x13
173      _alert_4_limit = UnaryStruct(0x13, ">H")
174      # Device ID/Revision - 0x20
175      _device_id = ROBits(8, 0x20, 8, register_width=2, lsb_first=False)
176      _revision_id = ROBits(8, 0x20, 0, register_width=2)
177  
178      types = ("K", "J", "T", "N", "S", "E", "B", "R")
179  
180      def __init__(self, i2c, address=_DEFAULT_ADDRESS, tctype="K", tcfilter=0):
181          self.buf = bytearray(3)
182          self.i2c_device = I2CDevice(i2c, address)
183          self.type = tctype
184          # is this a valid thermocouple type?
185          if tctype not in MCP9600.types:
186              raise Exception("invalid thermocouple type ({})".format(tctype))
187          # filter is from 0 (none) to 7 (max), can limit spikes in
188          # temperature readings
189          tcfilter = min(7, max(0, tcfilter))
190          ttype = MCP9600.types.index(tctype)
191  
192          self.buf[0] = _REGISTER_THERM_CFG
193          self.buf[1] = tcfilter | (ttype << 4)
194          with self.i2c_device as tci2c:
195              tci2c.write(self.buf, end=2)
196          if self._device_id != 0x40:
197              raise RuntimeError("Failed to find MCP9600 - check wiring!")
198  
199      def alert_config(
200          self,
201          *,
202          alert_number,
203          alert_temp_source,
204          alert_temp_limit,
205          alert_hysteresis,
206          alert_temp_direction,
207          alert_mode,
208          alert_state
209      ):
210          """Configure a specified alert pin. Alert is enabled by default when alert is configured.
211          To disable an alert pin, use ``alert_disable``.
212  
213          :param int alert_number: The alert pin number. Must be 1-4.
214          :param alert_temp_source: The temperature source to monitor for the alert. Options are:
215                                    ``THERMOCOUPLE`` (hot-junction) or ``AMBIENT`` (cold-junction).
216                                    Temperatures are in Celsius.
217          :param float alert_temp_limit: The temperature in degrees Celsius at which the alert should
218                                         trigger. For rising temperatures, the alert will trigger when
219                                         the temperature rises above this limit. For falling
220                                         temperatures, the alert will trigger when the temperature
221                                         falls below this limit.
222          :param float alert_hysteresis: The alert hysteresis range. Must be 0-255 degrees Celsius.
223                                         For rising temperatures, the hysteresis is below alert limit.
224                                         For falling temperatures, the hysteresis is above alert
225                                         limit. See data-sheet for further information.
226          :param alert_temp_direction: The direction the temperature must change to trigger the alert.
227                                       Options are ``RISING`` (heating up) or ``FALLING`` (cooling
228                                       down).
229          :param alert_mode: The alert mode. Options are ``COMPARATOR`` or ``INTERRUPT``. In
230                             comparator mode, the pin will follow the alert, so if the temperature
231                             drops, for example, the alert pin will go back low. In interrupt mode,
232                             by comparison, once the alert goes off, you must manually clear it. If
233                             setting mode to ``INTERRUPT``, use ``alert_interrupt_clear`` to clear the
234                             interrupt flag.
235          :param alert_state: Alert pin output state. Options are ``ACTIVE_HIGH`` or ``ACTIVE_LOW``.
236  
237  
238          For example, to configure alert 1:
239  
240          .. code-block:: python
241  
242              import board
243              import busio
244              import digitalio
245              import adafruit_mcp9600
246  
247              i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
248              mcp = adafruit_mcp9600.MCP9600(i2c)
249              alert_1 = digitalio.DigitalInOut(board.D5)
250              alert_1.switch_to_input()
251  
252              mcp.alert_config(alert_number=1, alert_temp_source=mcp.THERMOCOUPLE,
253                               alert_temp_limit=25, alert_hysteresis=0,
254                               alert_temp_direction=mcp.RISING, alert_mode=mcp.COMPARATOR,
255                               alert_state=mcp.ACTIVE_LOW)
256  
257          """
258          if alert_number not in (1, 2, 3, 4):
259              raise ValueError("Alert pin number must be 1-4.")
260          if not 0 <= alert_hysteresis < 256:
261              raise ValueError("Hysteresis value must be 0-255.")
262          setattr(self, "_alert_%d_monitor" % alert_number, alert_temp_source)
263          setattr(
264              self,
265              "_alert_%d_temperature_limit" % alert_number,
266              int(alert_temp_limit / 0.0625),
267          )
268          setattr(self, "_alert_%d_hysteresis" % alert_number, alert_hysteresis)
269          setattr(self, "_alert_%d_temp_direction" % alert_number, alert_temp_direction)
270          setattr(self, "_alert_%d_mode" % alert_number, alert_mode)
271          setattr(self, "_alert_%d_state" % alert_number, alert_state)
272          setattr(self, "_alert_%d_enable" % alert_number, True)
273  
274      def alert_disable(self, alert_number):
275          """Configuring an alert using ``alert_config()`` enables the specified alert by default.
276          Use ``alert_disable`` to disable an alert pin.
277  
278          :param int alert_number: The alert pin number. Must be 1-4.
279  
280          """
281          if alert_number not in (1, 2, 3, 4):
282              raise ValueError("Alert pin number must be 1-4.")
283          setattr(self, "_alert_%d_enable" % alert_number, False)
284  
285      def alert_interrupt_clear(self, alert_number, interrupt_clear=True):
286          """Turns off the alert flag in the MCP9600, and clears the pin state (not used if the alert
287          is in comparator mode). Required when ``alert_mode`` is ``INTERRUPT``.
288  
289          :param int alert_number: The alert pin number. Must be 1-4.
290          :param bool interrupt_clear: The bit to write the interrupt state flag
291  
292          """
293          if alert_number not in (1, 2, 3, 4):
294              raise ValueError("Alert pin number must be 1-4.")
295          setattr(self, "_alert_%d_interrupt_clear" % alert_number, interrupt_clear)
296  
297      @property
298      def version(self):
299          """ MCP9600 chip version """
300          data = self._read_register(_REGISTER_VERSION, 2)
301          return unpack(">xH", data)[0]
302  
303      @property
304      def ambient_temperature(self):
305          """ Cold junction/ambient/room temperature in Celsius """
306          data = self._read_register(_REGISTER_COLD_JUNCTION, 2)
307          value = unpack(">xH", data)[0] * 0.0625
308          if data[1] & 0x80:
309              value -= 4096
310          return value
311  
312      @property
313      def temperature(self):
314          """ Hot junction temperature in Celsius """
315          data = self._read_register(_REGISTER_HOT_JUNCTION, 2)
316          value = unpack(">xH", data)[0] * 0.0625
317          if data[1] & 0x80:
318              value -= 4096
319          return value
320  
321      @property
322      def delta_temperature(self):
323          """ Delta temperature in Celsius """
324          data = self._read_register(_REGISTER_DELTA_TEMP, 2)
325          value = unpack(">xH", data)[0] * 0.0625
326          if data[1] & 0x80:
327              value -= 4096
328          return value
329  
330      def _read_register(self, reg, count=1):
331          self.buf[0] = reg
332          with self.i2c_device as i2c:
333              i2c.write_then_readinto(self.buf, self.buf, out_end=count, in_start=1)
334          return self.buf