Skip to content
81 changes: 67 additions & 14 deletions adafruit_max31856.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
_MAX31856_CR0_CJ = const(0x08)
_MAX31856_CR0_FAULT = const(0x04)
_MAX31856_CR0_FAULTCLR = const(0x02)
_MAX31856_CR0_50HZ = const(0x01)

_MAX31856_CR1_REG = const(0x01)
_MAX31856_MASK_REG = const(0x02)
Expand Down Expand Up @@ -76,6 +77,8 @@
_MAX31856_FAULT_OVUV = const(0x02)
_MAX31856_FAULT_OPEN = const(0x01)

_AVGSEL_CONSTS = {1: 0x00, 2: 0x10, 4: 0x20, 8: 0x30, 16: 0x40}


class ThermocoupleType: # pylint: disable=too-few-public-methods
"""An enum-like class representing the different types of thermocouples that the MAX31856 can
Expand Down Expand Up @@ -113,6 +116,8 @@ class MAX31856:
:param ~microcontroller.Pin cs: The pin used for the CS signal.
:param ~adafruit_max31856.ThermocoupleType thermocouple_type: The type of thermocouple.\
Default is Type K.
:param ~int sampling: Number of samples to be averaged [1,2,4,8,16]
:param ~bool filter_50hz: Filter 50Hz mains frequency instead of 60Hz

**Quickstart: Importing and using the MAX31856**

Expand Down Expand Up @@ -155,13 +160,64 @@ def __init__(self, spi, cs, thermocouple_type=ThermocoupleType.K):
self._write_u8(_MAX31856_CR0_REG, _MAX31856_CR0_OCFAULT0)

# set thermocouple type
self._set_thermocouple_type(thermocouple_type)

def _set_thermocouple_type(self, thermocouple_type: ThermocoupleType):
# get current value of CR1 Reg
conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0]
conf_reg_1 &= 0xF0 # mask off bottom 4 bits
# add the new value for the TC type
conf_reg_1 |= int(thermocouple_type) & 0x0F
self._write_u8(_MAX31856_CR1_REG, conf_reg_1)

@property
def averaging(self) -> int:
"""Number of samples averaged together in each result.
Must be 1, 2, 4, 8, or 16. Default is 1 (no averaging).
"""
conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0]
avgsel = conf_reg_1 & ~0b10001111 # clear bits other than 4-6
# check which byte this corresponds to
for key, value in _AVGSEL_CONSTS.items():
if value == avgsel:
return key
raise KeyError(f"AVGSEL bit pattern was not recognised ({avgsel:>08b})")

@averaging.setter
def averaging(self, num_samples: int):
# This option is set in bits 4-6 of register CR1.
if num_samples not in _AVGSEL_CONSTS:
raise ValueError("Num_samples must be one of 1,2,4,8,16")
avgsel = _AVGSEL_CONSTS[num_samples]
# get current value of CR1 Reg
conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0]
conf_reg_1 &= 0b10001111 # clear bits 4-6
# OR the AVGSEL bits (4-6)
conf_reg_1 |= avgsel
self._write_u8(_MAX31856_CR1_REG, conf_reg_1)

@property
def noise_rejection(self) -> int:
"""
The frequency (Hz) to be used by the noise rejection filter.
Must be 50 or 60. Default is 60."""
# this value is stored in bit 0 of register CR0.
conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0]
if conf_reg_0 & _MAX31856_CR0_50HZ:
return 50
return 60

@noise_rejection.setter
def noise_rejection(self, frequency: int):
conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0]
if frequency == 50:
conf_reg_0 |= _MAX31856_CR0_50HZ # set the 50hz bit
elif frequency == 60:
conf_reg_0 &= ~_MAX31856_CR0_50HZ # clear the 50hz bit
else:
raise ValueError("Frequency must be 50 or 60")
self._write_u8(_MAX31856_CR0_REG, conf_reg_0)

@property
def temperature(self):
"""Measure the temperature of the sensor and wait for the result.
Expand All @@ -170,7 +226,7 @@ def temperature(self):
return self.unpack_temperature()

def unpack_temperature(self) -> float:
'''Reads the probe temperature from the register'''
"""Reads the probe temperature from the register"""
# unpack the 3-byte temperature as 4 bytes
raw_temp = unpack(
">i", self._read_register(_MAX31856_LTCBH_REG, 3) + bytes([0])
Expand All @@ -184,16 +240,14 @@ def unpack_temperature(self) -> float:

return temp_float



@property
def reference_temperature(self):
"""Wait to retreive temperature of the cold junction in degrees Celsius. (read-only)"""
"""Wait to retrieve temperature of the cold junction in degrees Celsius. (read-only)"""
self._perform_one_shot_measurement()
return self.unpack_reference_temperature()

def unpack_reference_temperature(self) -> float:
'''Reads the reference temperature from the register'''
"""Reads the reference temperature from the register"""
raw_read = unpack(">h", self._read_register(_MAX31856_CJTH_REG, 2))[0]

# effectively shift raw_read >> 8 to convert pseudo-float
Expand Down Expand Up @@ -277,13 +331,12 @@ def _perform_one_shot_measurement(self):
# wait for the measurement to complete
self._wait_for_oneshot()


def initiate_one_shot_measurement(self):
'''Starts a one-shot measurement and returns immediately.
"""Starts a one-shot measurement and returns immediately.
A measurement takes approximately 160ms.
Check the status of the measurement with `oneshot_pending`; when it is false,
the measurement is complete and the value can be read with `unpack_temperature`.
'''
"""
# read the current value of the first config register
conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0]

Expand All @@ -295,20 +348,20 @@ def initiate_one_shot_measurement(self):
# write it back with the new values, prompting the sensor to perform a measurement
self._write_u8(_MAX31856_CR0_REG, conf_reg_0)


@property
def oneshot_pending(self) -> bool:
'''A boolean indicating the status of the one-shot flag.
A True value means the measurement is still ongoing.
A False value means measurement is complete.'''
oneshot_flag = self._read_register(_MAX31856_CR0_REG, 1)[0] & _MAX31856_CR0_1SHOT
"""A boolean indicating the status of the one-shot flag.
A True value means the measurement is still ongoing.
A False value means measurement is complete."""
oneshot_flag = (
self._read_register(_MAX31856_CR0_REG, 1)[0] & _MAX31856_CR0_1SHOT
)
return bool(oneshot_flag)

def _wait_for_oneshot(self):
while self.oneshot_pending:
sleep(0.01)


def _read_register(self, address, length):
# pylint: disable=no-member
# Read a 16-bit BE unsigned value from the specified 8-bit address.
Expand Down