/ 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