diff --git a/code/lib/adafruit_max31865.mpy b/code/lib/adafruit_max31865.mpy new file mode 100644 index 0000000..1e09ddd Binary files /dev/null and b/code/lib/adafruit_max31865.mpy differ diff --git a/code/lib/adafruit_max31865.py b/code/lib/adafruit_max31865.py new file mode 100644 index 0000000..fccfee4 --- /dev/null +++ b/code/lib/adafruit_max31865.py @@ -0,0 +1,280 @@ +# 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 adaptation : 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:** + +* 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)