From 05b98fc1f75a9048d7dbab5ca93551f3f227bad8 Mon Sep 17 00:00:00 2001 From: Pierrick C Date: Thu, 26 Mar 2020 22:14:11 +0100 Subject: [PATCH] Add support for Pt100 sensor with MAX31865 amplifier --- code/lib/adafruit_max31865.mpy | Bin 0 -> 1854 bytes code/lib/adafruit_max31865.py | 280 +++++++++++++++++++++++++++++++++ code/main.py | 12 ++ 3 files changed, 292 insertions(+) create mode 100644 code/lib/adafruit_max31865.mpy create mode 100644 code/lib/adafruit_max31865.py diff --git a/code/lib/adafruit_max31865.mpy b/code/lib/adafruit_max31865.mpy new file mode 100644 index 0000000000000000000000000000000000000000..1e09ddd3458d4621d7c74cf9d7e2ad491a0cbd69 GIT binary patch literal 1854 zcma)7TWr%-7(Py#i`xRuWhNW2x=GWdO`E39rGYUhUBrq6S__n|5|VKen|jL)+ky23 zPC~n}o5aK3hbCSpIcb|b?V$nMXSBUcXcDTnA#GwztQv3Ie!FSW*dCT6=X~d!-{1dz z|FMrxSxi05b%qKeTaeH4qFk0#F3-Oo9Ze?U;nF;Q2_Fasr%H@}ai3pg5T4#=&-3aW zP3TCK@&X{rIwchbiG92Cd}dB62>Z=6F^efQ33EuXm>=18F>-9%&Kr`P0b5h^>KtU=rVPV9A_-)X=)p)bDw6T=*jQW` zj*Kmy_oN8dr{~89FXLAiNoZZ~j(KvDOW8~1T+V56?Vg%Bd6Ju% zN-UsR&9%#ozN{pO*@l<(9v(D(|4qt{ooLJlh6T+%j1$BFDojO78stn%;(AiQxutagel5QBGRk$y{+%~f5osvOms*Oi}hAhhdeGnt_DJh zp%8MS@k)$nX|BCs6TE<7zZ>0lqKTRpUwaZFVuUeCxY5;m1;lDDbGsmQJd;5ncsHNT zLRzJI{*Y&k#GmXCR}Es@4l()viEsQ?ugp9)=*em%>N!B-&jUljkRcd&@6@~%^w?Xu z_GAg4s~dggoaXvxt|deHw%qA0HY?OE*Gu(u;V&3)e?)UTz&5Zwi4jIb;22Ow)^1}qN3aWMcz}_;Tk?4*NZ^7JM9oz(PPX{*u ztm$9@z>CJ8>UWR{G<#a}IGo0@?Cw-@f>81q&kGa>gw}59M0*S(r_F8ine-iTAAr}X zRj>Tesv8fy*a5HdsAz%zM7%=1Ouy#9zcPV908+RT2sBkO`gyemPv)c5njw8LIep}y z4gZ1det$UB-yiNz2abFD0^1E~E^^h`KH~4UoP?9uTxgFcHh;gf0AtNH{OoSkY2LiI zv|QVKy5=-KGj6+bNmd&PqW*N{*;)fXNSSaW1G#OW08U*{uD4M$k$!z{}tx4sB$JQ+VIlCf}-O~4l+A$GFi T`_ (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:** + +* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: + https://github.com/adafruit/circuitpython/releases +""" +import math +import time +from machine import Pin + +from micropython import const + +#import adafruit_bus_spi.spi_spi as spi_spi + +__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 diff --git a/code/main.py b/code/main.py index 0f9ed41..9cc1c32 100644 --- a/code/main.py +++ b/code/main.py @@ -6,10 +6,12 @@ import machine import network import esp import bme280_i2c +import adafruit_max31865 from ntptime import settime from umqtt.robust import MQTTClient import config + # connect to WLAN wlan = network.WLAN(network.STA_IF) wlan.active(True) @@ -44,6 +46,12 @@ bme.set_measurement_settings({ # Start the sensor automatically sensing bme.set_power_mode(bme280_i2c.BME280_NORMAL_MODE) +# PT100 (via max81865 module) sensor setup +spi=machine.SPI(-1, sck=machine.Pin(14, machine.Pin.OUT), mosi=machine.Pin(13, machine.Pin.OUT), miso=machine.Pin(12, machine.Pin.OUT)) +spi.init(baudrate=115200, polarity=0, phase=1) #, firstbit=machine.SPI.MSB) +max31865_cs=machine.Pin(2) +max31865=adafruit_max31865.MAX31865(spi, max31865_cs) + # RTC setup rtc=machine.RTC() settime() @@ -78,6 +86,8 @@ MqttPublish(client, "pressure/unit", "hPa", retain=True, qos=0) MqttPublish(client, "pressure/desc", "Capteur Bosch BME280", retain=True, qos=0) MqttPublish(client, "temperature/unit", "degC", retain=True, qos=0) MqttPublish(client, "temperature/desc", "Capteur Bosch BME280", retain=True, qos=0) +MqttPublish(client, "temperature2/unit", "degC", retain=True, qos=0) +MqttPublish(client, "temperature2/desc", "Sonde PT100/MAX31865", retain=True, qos=0) MqttPublish(client, "wifi/ssid", config.WIFI_SSID, retain=True, qos=0) MqttPublish(client, "wifi/ip", wlan.ifconfig()[0], retain=True, qos=0) MqttPublish(client, "wifi/channel", "{:d}".format(net[2]), retain=True, qos=0) @@ -98,6 +108,7 @@ while 1: bme_data = bme.get_measurement() meas_time = now() print(meas_time, ":", bme_data) + print("Pt100:", max31865.temperature, "degC") for nature, param in topics.items(): MqttPublish(client, @@ -106,6 +117,7 @@ while 1: qos=param[3], sleep=50) + MqttPublish(client, "temperature2/value", "{:.2f}".format(max31865.temperature)) MqttPublish(client, "wifi/rssi", "{:.0f}".format(wlan.status('rssi'))) MqttPublish(client, "time/last_values", meas_time, retain=True)