Max31865 Board with a ESP8266

All ESP8266 boards running MicroPython.
Official boards are the Adafruit Huzzah and Feather boards.
Target audience: MicroPython users with an ESP8266 board.
Post Reply
slogan87
Posts: 1
Joined: Wed Mar 10, 2021 1:58 am

Max31865 Board with a ESP8266

Post by slogan87 » Sun Mar 14, 2021 7:40 am

Hey there everyone,

I am new to Python coding and am having a few issues trying to get a ESP8266 working with 2x Max31865 boards. I have done a bit with Java for HMI's as I am more involved with PLC programming but nothing like this. I can work my way through some of the code and how it works but I am getting errors when I try running it. Currently I only have one board connected. I have found some code online that should work and have copied it but no luck. Basically it is now throwing the following error that I can not get past. All of the pins are as per below and wired up. Is anyone able to help. Eventually I will be reading from two boards and doing some calculations as it will be a solar controller for my pool.

>> %Run -c $EDITOR_CONTENT
Warning: SPI(-1, ...) is deprecated, use SoftSPI(...) instead
Traceback (most recent call last):
File "<stdin>", line 31, in <module>
File "adafruit_max31865.py", line 66, in __init__
TypeError: can't convert SoftSPI to int

Attached is my current code. The Adafruit_Max31865 library I copied from their website and as it is written in circuit python, I have changed some of the parameters from requiring SPI_device to SPI. However as per the fault above, I changed it to SoftSPI

Main.py

Code: Select all

## MicroPython Imports
import time
import machine
## Local Imports
import adafruit_max31865 as max31865

RTD_NOMINAL   = 100.0  ## Resistance of RTD at 0C
RTD_REFERENCE = 430.0  ## Value of reference resistor on PCB
RTD_WIRES = 3          ## RTD 3 wires
## Create Software SPI controller.  MAX31865 requires polarity of 0 and phase of 1.
## Currently, the micropython on the ESP32 does not support hardware SPI
sck  = machine.Pin(14, machine.Pin.OUT)
mosi = machine.Pin(13, machine.Pin.IN)
miso = machine.Pin(12, machine.Pin.OUT)
spi  = machine.SPI(baudrate=50000, sck=sck, mosi=mosi, miso=miso, polarity=0, phase=1)
## Create array SPI Chip Select pins 
#cs1  = machine.Pin(33, machine.Pin.OUT, value=1)
#cs2  = machine.Pin(15, machine.Pin.OUT, value=1)
#cs3  = machine.Pin(32, machine.Pin.OUT, value=1)
#cs4  = machine.Pin(14, machine.Pin.OUT, value=1)
#css  = [cs1, cs2, cs3, cs4]
cs1  = machine.Pin(15, machine.Pin.OUT, value=1)
css  = [cs1]
sensors     = []
idx         = 0
## Create array of active RTD sensors and information about them
for cs in css:
    idx += 1
sensors.append(
        max31865.MAX31865(
            spi, css[idx-1],
            wires        = RTD_WIRES,
            rtd_nominal  = RTD_NOMINAL,
            ref_resistor = RTD_REFERENCE)
    )
def timestamp():
    return float(time.ticks_ms()) / 1000.0
boot_time = timestamp()
while True:
    data = [timestamp() - boot_time] + [sensor.temperature for sensor in sensors]
    print(','.join(map(str,data)))

Code: Select all

"""
`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, original CircuitPython version
             Chris Osterwood, port _read and _write methods to MicroPython
"""
import math
import time
from micropython import const
import machine
#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):
        self.rtd_nominal = rtd_nominal
        self.ref_resistor = ref_resistor
        self._device = machine.SoftSPI(spi, cs)
# 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!')
            config = self._read_u8(_MAX31865_CONFIG_REG)
        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.set_bias(False)
        self.set_auto_convert(False)
    def _read_u8(self, address):
        # Read an 8-bit unsigned value from the specified 8-bit address.
        buf = []
        with self._device as device:
            device.write(bytes([address & 0x7F]))
            buf = device.read(1)
        return buf[0]
    def _read_u16(self, address):
        # Read a 16-bit BE unsigned value from the specified 8-bit address.
        buf = []
        with self._device as device:
            device.write(bytes([address & 0x7F]))
            buf = device.read(2)
        return (buf[0] << 8) | buf[1]
    def _write_u8(self, address, val):
        # Write an 8-bit unsigned value to the specified 8-bit address.
        with self._device as device:
            buf = bytearray(2)
            buf[0] = (address | 0x80) & 0xFF
            buf[1] = val & 0xFF
            device.write(buf)
    @property
    def bias(self):
        """Get and set the boolean state of the sensor's bias (True/False)."""
        return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_BIAS)
    def set_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):
        """Get and set the boolean state of the sensor's automatic conversion
        mode (True/False).
        """
        return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_MODEAUTO)
    def set_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):
        """Get 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.set_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
# Have to normalize to 100 ohms if temperure is less than 0C for the following math to work
        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
Thanks in advance.

Kind Regards,
Sam Logan

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Max31865 Board with a ESP8266

Post by pythoncoder » Mon Mar 15, 2021 10:03 am

In the line

Code: Select all

spi  = machine.SPI(baudrate=50000, sck=sck, mosi=mosi, miso=miso, polarity=0, phase=1)
you create an SPI instance, which you then pass to your constructor. This then tries to make a SoftSPI instance from it, which won't work. You need to assign the SPI instance to the class:

Code: Select all

self._device = spi
If you want to use soft SPI, the first line that instantiates the SPI is the one that needs to be adapted.

I'll leave you to figure out what to do with CS. Each Max31865 will need its own CS line which is activated before each SPI transfer and deactivated after it.
Peter Hinch
Index to my micropython libraries.

Post Reply