# The MIT License (MIT) # # Copyright (c) 2017 Tony DiCola for Adafruit Industries # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ `adafruit_max31865` ==================================================== MicroPython module for the MAX31865 platinum RTD temperature sensor. See examples/simpletest.py for an example of the usage. * Author(s): Tony DiCola * micropython port: arofarn Implementation Notes -------------------- **Hardware:** * Adafruit `Universal Thermocouple Amplifier MAX31856 Breakout `_ (Product ID: 3263) * Adafruit `PT100 RTD Temperature Sensor Amplifier - MAX31865 `_ (Product ID: 3328) * Adafruit `PT1000 RTD Temperature Sensor Amplifier - MAX31865 `_ (Product ID: 3648) **Software and Dependencies:** * Micropython firmware : https://micropython.org/download """ import math import time from machine import Pin from micropython import const __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MAX31865.git" # pylint: disable=bad-whitespace # Register and other constant values: _MAX31865_CONFIG_REG = const(0x00) _MAX31865_CONFIG_BIAS = const(0x80) _MAX31865_CONFIG_MODEAUTO = const(0x40) _MAX31865_CONFIG_MODEOFF = const(0x00) _MAX31865_CONFIG_1SHOT = const(0x20) _MAX31865_CONFIG_3WIRE = const(0x10) _MAX31865_CONFIG_24WIRE = const(0x00) _MAX31865_CONFIG_FAULTSTAT = const(0x02) _MAX31865_CONFIG_FILT50HZ = const(0x01) _MAX31865_CONFIG_FILT60HZ = const(0x00) _MAX31865_RTDMSB_REG = const(0x01) _MAX31865_RTDLSB_REG = const(0x02) _MAX31865_HFAULTMSB_REG = const(0x03) _MAX31865_HFAULTLSB_REG = const(0x04) _MAX31865_LFAULTMSB_REG = const(0x05) _MAX31865_LFAULTLSB_REG = const(0x06) _MAX31865_FAULTSTAT_REG = const(0x07) _MAX31865_FAULT_HIGHTHRESH = const(0x80) _MAX31865_FAULT_LOWTHRESH = const(0x40) _MAX31865_FAULT_REFINLOW = const(0x20) _MAX31865_FAULT_REFINHIGH = const(0x10) _MAX31865_FAULT_RTDINLOW = const(0x08) _MAX31865_FAULT_OVUV = const(0x04) _RTD_A = 3.9083e-3 _RTD_B = -5.775e-7 # pylint: enable=bad-whitespace class MAX31865: """Driver for the MAX31865 thermocouple amplifier.""" def __init__( self, spi, cs, *, rtd_nominal=100, ref_resistor=430.0, wires=2, filter_frequency=60 ): self.rtd_nominal = rtd_nominal self.ref_resistor = ref_resistor self._spi = spi self._cs = cs self._cs.init(mode=Pin.OUT, pull=Pin.PULL_UP) self._cs.value(1) # Set 50Hz or 60Hz filter. if filter_frequency not in (50, 60): raise ValueError("Filter_frequency must be a value of 50 or 60!") config = self._read_u8(_MAX31865_CONFIG_REG) if filter_frequency == 50: config |= _MAX31865_CONFIG_FILT50HZ else: config &= ~_MAX31865_CONFIG_FILT50HZ # Set wire config register based on the number of wires specified. if wires not in (2, 3, 4): raise ValueError("Wires must be a value of 2, 3, or 4!") if wires == 3: config |= _MAX31865_CONFIG_3WIRE else: # 2 or 4 wire config &= ~_MAX31865_CONFIG_3WIRE self._write_u8(_MAX31865_CONFIG_REG, config) # Default to no bias and no auto conversion. self.bias = False self.auto_convert = False # pylint: disable=no-member def _read_u8(self, address): # Read an 8-bit unsigned value from the specified 8-bit address. buf=bytearray(1) self._cs.value(0) self._spi.write(bytes([address & 0x7F])) self._spi.readinto(buf, 1) self._cs.value(1) return buf[0] def _read_u16(self, address): # Read a 16-bit BE unsigned value from the specified 8-bit address. buf=bytearray(2) self._cs.value(0) self._spi.write(bytes([address & 0x7F])) self._spi.readinto(buf, 1) self._cs.value(1) return (buf[0] << 8) | buf[1] def _write_u8(self, address, val): # Write an 8-bit unsigned value to the specified 8-bit address. buf=bytearray(2) buf[0] = (address | 0x80) & 0xFF buf[1] = val & 0xFF self._cs.value(0) self._spi.write(buf) self._cs.value(1) # pylint: enable=no-member @property def bias(self): """The state of the sensor's bias (True/False).""" return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_BIAS) @bias.setter def bias(self, val): config = self._read_u8(_MAX31865_CONFIG_REG) if val: config |= _MAX31865_CONFIG_BIAS # Enable bias. else: config &= ~_MAX31865_CONFIG_BIAS # Disable bias. self._write_u8(_MAX31865_CONFIG_REG, config) @property def auto_convert(self): """The state of the sensor's automatic conversion mode (True/False). """ return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_MODEAUTO) @auto_convert.setter def auto_convert(self, val): config = self._read_u8(_MAX31865_CONFIG_REG) if val: config |= _MAX31865_CONFIG_MODEAUTO # Enable auto convert. else: config &= ~_MAX31865_CONFIG_MODEAUTO # Disable auto convert. self._write_u8(_MAX31865_CONFIG_REG, config) @property def fault(self): """The fault state of the sensor. Use ``clear_faults()`` to clear the fault state. Returns a 6-tuple of boolean values which indicate if any faults are present: - HIGHTHRESH - LOWTHRESH - REFINLOW - REFINHIGH - RTDINLOW - OVUV """ faults = self._read_u8(_MAX31865_FAULTSTAT_REG) # pylint: disable=bad-whitespace highthresh = bool(faults & _MAX31865_FAULT_HIGHTHRESH) lowthresh = bool(faults & _MAX31865_FAULT_LOWTHRESH) refinlow = bool(faults & _MAX31865_FAULT_REFINLOW) refinhigh = bool(faults & _MAX31865_FAULT_REFINHIGH) rtdinlow = bool(faults & _MAX31865_FAULT_RTDINLOW) ovuv = bool(faults & _MAX31865_FAULT_OVUV) # pylint: enable=bad-whitespace return (highthresh, lowthresh, refinlow, refinhigh, rtdinlow, ovuv) def clear_faults(self): """Clear any fault state previously detected by the sensor.""" config = self._read_u8(_MAX31865_CONFIG_REG) config &= ~0x2C config |= _MAX31865_CONFIG_FAULTSTAT self._write_u8(_MAX31865_CONFIG_REG, config) def read_rtd(self): """Perform a raw reading of the thermocouple and return its 15-bit value. You'll need to manually convert this to temperature using the nominal value of the resistance-to-digital conversion and some math. If you just want temperature use the temperature property instead. """ self.clear_faults() self.bias = True time.sleep(0.01) config = self._read_u8(_MAX31865_CONFIG_REG) config |= _MAX31865_CONFIG_1SHOT self._write_u8(_MAX31865_CONFIG_REG, config) time.sleep(0.065) rtd = self._read_u16(_MAX31865_RTDMSB_REG) # Remove fault bit. rtd >>= 1 return rtd @property def resistance(self): """Read the resistance of the RTD and return its value in Ohms.""" resistance = self.read_rtd() resistance /= 32768 resistance *= self.ref_resistor return resistance @property def temperature(self): """Read the temperature of the sensor and return its value in degrees Celsius. """ # This math originates from: # http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf # To match the naming from the app note we tell lint to ignore the Z1-4 # naming. # pylint: disable=invalid-name raw_reading = self.resistance Z1 = -_RTD_A Z2 = _RTD_A * _RTD_A - (4 * _RTD_B) Z3 = (4 * _RTD_B) / self.rtd_nominal Z4 = 2 * _RTD_B temp = Z2 + (Z3 * raw_reading) temp = (math.sqrt(temp) + Z1) / Z4 if temp >= 0: return temp # For the following math to work, nominal RTD resistance must be normalized to 100 ohms raw_reading /= self.rtd_nominal raw_reading *= 100 rpoly = raw_reading temp = -242.02 temp += 2.2228 * rpoly rpoly *= raw_reading # square temp += 2.5859e-3 * rpoly rpoly *= raw_reading # ^3 temp -= 4.8260e-6 * rpoly rpoly *= raw_reading # ^4 temp -= 2.8183e-8 * rpoly rpoly *= raw_reading # ^5 temp += 1.5243e-10 * rpoly return temp