Add support for Pt100 sensor with MAX31865 amplifier
This commit is contained in:
parent
95453be6a9
commit
05b98fc1f7
BIN
code/lib/adafruit_max31865.mpy
Normal file
BIN
code/lib/adafruit_max31865.mpy
Normal file
Binary file not shown.
280
code/lib/adafruit_max31865.py
Normal file
280
code/lib/adafruit_max31865.py
Normal file
@ -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
|
||||
<https://www.adafruit.com/product/3263>`_ (Product ID: 3263)
|
||||
|
||||
* Adafruit `PT100 RTD Temperature Sensor Amplifier - MAX31865
|
||||
<https://www.adafruit.com/product/3328>`_ (Product ID: 3328)
|
||||
|
||||
* Adafruit `PT1000 RTD Temperature Sensor Amplifier - MAX31865
|
||||
<https://www.adafruit.com/product/3648>`_ (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
|
12
code/main.py
12
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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user