diff options
Diffstat (limited to 'klippy/extras/spi_temperature.py')
-rw-r--r-- | klippy/extras/spi_temperature.py | 274 |
1 files changed, 154 insertions, 120 deletions
diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 92d1b385..38d6b559 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -15,6 +15,7 @@ from . import bus REPORT_TIME = 0.300 MAX_INVALID_COUNT = 3 + class SensorBase: def __init__(self, config, chip_type, config_cmd=None, spi_mode=1): self.printer = config.get_printer() @@ -23,44 +24,58 @@ class SensorBase: self.min_sample_value = self.max_sample_value = 0 self._report_clock = 0 self.spi = bus.MCU_SPI_from_config( - config, spi_mode, pin_option="sensor_pin", default_speed=4000000) + config, spi_mode, pin_option="sensor_pin", default_speed=4000000 + ) if config_cmd is not None: self.spi.spi_send(config_cmd) self.mcu = mcu = self.spi.get_mcu() # Reader chip configuration self.oid = oid = mcu.create_oid() - mcu.register_response(self._handle_spi_response, - "thermocouple_result", oid) + mcu.register_response(self._handle_spi_response, "thermocouple_result", oid) mcu.register_config_callback(self._build_config) + def setup_minmax(self, min_temp, max_temp): adc_range = [self.calc_adc(min_temp), self.calc_adc(max_temp)] self.min_sample_value = min(adc_range) self.max_sample_value = max(adc_range) + def setup_callback(self, cb): self._callback = cb + def get_report_time_delta(self): return REPORT_TIME + def _build_config(self): self.mcu.add_config_cmd( - "config_thermocouple oid=%u spi_oid=%u thermocouple_type=%s" % ( - self.oid, self.spi.get_oid(), self.chip_type)) + "config_thermocouple oid=%u spi_oid=%u thermocouple_type=%s" + % (self.oid, self.spi.get_oid(), self.chip_type) + ) clock = self.mcu.get_query_slot(self.oid) self._report_clock = self.mcu.seconds_to_clock(REPORT_TIME) self.mcu.add_config_cmd( "query_thermocouple oid=%u clock=%u rest_ticks=%u" - " min_value=%u max_value=%u max_invalid_count=%u" % ( - self.oid, clock, self._report_clock, - self.min_sample_value, self.max_sample_value, - MAX_INVALID_COUNT), is_init=True) + " min_value=%u max_value=%u max_invalid_count=%u" + % ( + self.oid, + clock, + self._report_clock, + self.min_sample_value, + self.max_sample_value, + MAX_INVALID_COUNT, + ), + is_init=True, + ) + def _handle_spi_response(self, params): - if params['fault']: - self.handle_fault(params['value'], params['fault']) + if params["fault"]: + self.handle_fault(params["value"], params["fault"]) return - temp = self.calc_temp(params['value']) - next_clock = self.mcu.clock32_to_clock64(params['next_clock']) + temp = self.calc_temp(params["value"]) + next_clock = self.mcu.clock32_to_clock64(params["next_clock"]) last_read_clock = next_clock - self._report_clock - last_read_time = self.mcu.clock_to_print_time(last_read_clock) + last_read_time = self.mcu.clock_to_print_time(last_read_clock) self._callback(last_read_time, temp) + def report_fault(self, msg): logging.warning(msg) @@ -69,62 +84,63 @@ class SensorBase: # MAX31856 thermocouple ###################################################################### -MAX31856_CR0_REG = 0x00 -MAX31856_CR0_AUTOCONVERT = 0x80 -MAX31856_CR0_1SHOT = 0x40 -MAX31856_CR0_OCFAULT1 = 0x20 -MAX31856_CR0_OCFAULT0 = 0x10 -MAX31856_CR0_CJ = 0x08 -MAX31856_CR0_FAULT = 0x04 -MAX31856_CR0_FAULTCLR = 0x02 -MAX31856_CR0_FILT50HZ = 0x01 -MAX31856_CR0_FILT60HZ = 0x00 +MAX31856_CR0_REG = 0x00 +MAX31856_CR0_AUTOCONVERT = 0x80 +MAX31856_CR0_1SHOT = 0x40 +MAX31856_CR0_OCFAULT1 = 0x20 +MAX31856_CR0_OCFAULT0 = 0x10 +MAX31856_CR0_CJ = 0x08 +MAX31856_CR0_FAULT = 0x04 +MAX31856_CR0_FAULTCLR = 0x02 +MAX31856_CR0_FILT50HZ = 0x01 +MAX31856_CR0_FILT60HZ = 0x00 -MAX31856_CR1_REG = 0x01 -MAX31856_CR1_AVGSEL1 = 0x00 -MAX31856_CR1_AVGSEL2 = 0x10 -MAX31856_CR1_AVGSEL4 = 0x20 -MAX31856_CR1_AVGSEL8 = 0x30 -MAX31856_CR1_AVGSEL16 = 0x70 +MAX31856_CR1_REG = 0x01 +MAX31856_CR1_AVGSEL1 = 0x00 +MAX31856_CR1_AVGSEL2 = 0x10 +MAX31856_CR1_AVGSEL4 = 0x20 +MAX31856_CR1_AVGSEL8 = 0x30 +MAX31856_CR1_AVGSEL16 = 0x70 -MAX31856_MASK_REG = 0x02 -MAX31856_MASK_COLD_JUNCTION_HIGH_FAULT = 0x20 -MAX31856_MASK_COLD_JUNCTION_LOW_FAULT = 0x10 -MAX31856_MASK_THERMOCOUPLE_HIGH_FAULT = 0x08 -MAX31856_MASK_THERMOCOUPLE_LOW_FAULT = 0x04 -MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT = 0x02 -MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT = 0x01 +MAX31856_MASK_REG = 0x02 +MAX31856_MASK_COLD_JUNCTION_HIGH_FAULT = 0x20 +MAX31856_MASK_COLD_JUNCTION_LOW_FAULT = 0x10 +MAX31856_MASK_THERMOCOUPLE_HIGH_FAULT = 0x08 +MAX31856_MASK_THERMOCOUPLE_LOW_FAULT = 0x04 +MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT = 0x02 +MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT = 0x01 -MAX31856_CJHF_REG = 0x03 -MAX31856_CJLF_REG = 0x04 -MAX31856_LTHFTH_REG = 0x05 -MAX31856_LTHFTL_REG = 0x06 -MAX31856_LTLFTH_REG = 0x07 -MAX31856_LTLFTL_REG = 0x08 -MAX31856_CJTO_REG = 0x09 -MAX31856_CJTH_REG = 0x0A -MAX31856_CJTL_REG = 0x0B -MAX31856_LTCBH_REG = 0x0C -MAX31856_LTCBM_REG = 0x0D -MAX31856_LTCBL_REG = 0x0E +MAX31856_CJHF_REG = 0x03 +MAX31856_CJLF_REG = 0x04 +MAX31856_LTHFTH_REG = 0x05 +MAX31856_LTHFTL_REG = 0x06 +MAX31856_LTLFTH_REG = 0x07 +MAX31856_LTLFTL_REG = 0x08 +MAX31856_CJTO_REG = 0x09 +MAX31856_CJTH_REG = 0x0A +MAX31856_CJTL_REG = 0x0B +MAX31856_LTCBH_REG = 0x0C +MAX31856_LTCBM_REG = 0x0D +MAX31856_LTCBL_REG = 0x0E -MAX31856_SR_REG = 0x0F -MAX31856_FAULT_CJRANGE = 0x80 # Cold Junction out of range -MAX31856_FAULT_TCRANGE = 0x40 # Thermocouple out of range -MAX31856_FAULT_CJHIGH = 0x20 # Cold Junction High -MAX31856_FAULT_CJLOW = 0x10 # Cold Junction Low -MAX31856_FAULT_TCHIGH = 0x08 # Thermocouple Low -MAX31856_FAULT_TCLOW = 0x04 # Thermocouple Low -MAX31856_FAULT_OVUV = 0x02 # Under Over Voltage -MAX31856_FAULT_OPEN = 0x01 +MAX31856_SR_REG = 0x0F +MAX31856_FAULT_CJRANGE = 0x80 # Cold Junction out of range +MAX31856_FAULT_TCRANGE = 0x40 # Thermocouple out of range +MAX31856_FAULT_CJHIGH = 0x20 # Cold Junction High +MAX31856_FAULT_CJLOW = 0x10 # Cold Junction Low +MAX31856_FAULT_TCHIGH = 0x08 # Thermocouple Low +MAX31856_FAULT_TCLOW = 0x04 # Thermocouple Low +MAX31856_FAULT_OVUV = 0x02 # Under Over Voltage +MAX31856_FAULT_OPEN = 0x01 MAX31856_SCALE = 5 MAX31856_MULT = 0.0078125 + class MAX31856(SensorBase): def __init__(self, config): - SensorBase.__init__(self, config, "MAX31856", - self.build_spi_init(config)) + SensorBase.__init__(self, config, "MAX31856", self.build_spi_init(config)) + def handle_fault(self, adc, fault): if fault & MAX31856_FAULT_CJRANGE: self.report_fault("Max31856: Cold Junction Range Fault") @@ -142,6 +158,7 @@ class MAX31856(SensorBase): self.report_fault("Max31856: Over/Under Voltage Fault") if fault & MAX31856_FAULT_OPEN: self.report_fault("Max31856: Thermocouple Open Fault") + def calc_temp(self, adc): adc = adc >> MAX31856_SCALE # Fix sign bit: @@ -149,41 +166,45 @@ class MAX31856(SensorBase): adc = ((adc & 0x3FFFF) + 1) * -1 temp = MAX31856_MULT * adc return temp + def calc_adc(self, temp): - adc = int( ( temp / MAX31856_MULT ) + 0.5 ) # convert to ADC value + adc = int((temp / MAX31856_MULT) + 0.5) # convert to ADC value adc = max(0, min(0x3FFFF, adc)) << MAX31856_SCALE return adc + def build_spi_init(self, config): cmds = [] value = MAX31856_CR0_AUTOCONVERT - if config.getboolean('tc_use_50Hz_filter', False): + if config.getboolean("tc_use_50Hz_filter", False): value |= MAX31856_CR0_FILT50HZ cmds.append(0x80 + MAX31856_CR0_REG) cmds.append(value) types = { - "B" : 0b0000, - "E" : 0b0001, - "J" : 0b0010, - "K" : 0b0011, - "N" : 0b0100, - "R" : 0b0101, - "S" : 0b0110, - "T" : 0b0111, + "B": 0b0000, + "E": 0b0001, + "J": 0b0010, + "K": 0b0011, + "N": 0b0100, + "R": 0b0101, + "S": 0b0110, + "T": 0b0111, } - value = config.getchoice('tc_type', types, default="K") + value = config.getchoice("tc_type", types, default="K") averages = { - 1 : MAX31856_CR1_AVGSEL1, - 2 : MAX31856_CR1_AVGSEL2, - 4 : MAX31856_CR1_AVGSEL4, - 8 : MAX31856_CR1_AVGSEL8, - 16 : MAX31856_CR1_AVGSEL16 + 1: MAX31856_CR1_AVGSEL1, + 2: MAX31856_CR1_AVGSEL2, + 4: MAX31856_CR1_AVGSEL4, + 8: MAX31856_CR1_AVGSEL8, + 16: MAX31856_CR1_AVGSEL16, } - value |= config.getchoice('tc_averaging_count', averages, 1) + value |= config.getchoice("tc_averaging_count", averages, 1) cmds.append(value) - value = (MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT | - MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT) + value = ( + MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT + | MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT + ) cmds.append(value) return cmds @@ -195,9 +216,11 @@ class MAX31856(SensorBase): MAX31855_SCALE = 18 MAX31855_MULT = 0.25 + class MAX31855(SensorBase): def __init__(self, config): SensorBase.__init__(self, config, "MAX31855", spi_mode=0) + def handle_fault(self, adc, fault): if fault & 0x1: self.report_fault("MAX31855 : Open Circuit") @@ -205,6 +228,7 @@ class MAX31855(SensorBase): self.report_fault("MAX31855 : Short to GND") if fault & 0x4: self.report_fault("MAX31855 : Short to Vcc") + def calc_temp(self, adc): adc = adc >> MAX31855_SCALE # Fix sign bit: @@ -212,8 +236,9 @@ class MAX31855(SensorBase): adc = ((adc & 0x1FFF) + 1) * -1 temp = MAX31855_MULT * adc return temp + def calc_adc(self, temp): - adc = int( ( temp / MAX31855_MULT ) + 0.5 ) # convert to ADC value + adc = int((temp / MAX31855_MULT) + 0.5) # convert to ADC value adc = max(0, min(0x1FFF, adc)) << MAX31855_SCALE return adc @@ -225,14 +250,17 @@ class MAX31855(SensorBase): MAX6675_SCALE = 3 MAX6675_MULT = 0.25 + class MAX6675(SensorBase): def __init__(self, config): SensorBase.__init__(self, config, "MAX6675", spi_mode=0) + def handle_fault(self, adc, fault): if fault & 0x02: self.report_fault("Max6675 : Device ID error") if fault & 0x04: self.report_fault("Max6675 : Thermocouple Open Fault") + def calc_temp(self, adc): adc = adc >> MAX6675_SCALE # Fix sign bit: @@ -240,8 +268,9 @@ class MAX6675(SensorBase): adc = ((adc & 0x1FFF) + 1) * -1 temp = MAX6675_MULT * adc return temp + def calc_adc(self, temp): - adc = int( ( temp / MAX6675_MULT ) + 0.5 ) # convert to ADC value + adc = int((temp / MAX6675_MULT) + 0.5) # convert to ADC value adc = max(0, min(0x1FFF, adc)) << MAX6675_SCALE return adc @@ -250,43 +279,45 @@ class MAX6675(SensorBase): # MAX31865 (RTD sensor) ###################################################################### -MAX31865_CONFIG_REG = 0x00 -MAX31865_RTDMSB_REG = 0x01 -MAX31865_RTDLSB_REG = 0x02 -MAX31865_HFAULTMSB_REG = 0x03 -MAX31865_HFAULTLSB_REG = 0x04 -MAX31865_LFAULTMSB_REG = 0x05 -MAX31865_LFAULTLSB_REG = 0x06 -MAX31865_FAULTSTAT_REG = 0x07 +MAX31865_CONFIG_REG = 0x00 +MAX31865_RTDMSB_REG = 0x01 +MAX31865_RTDLSB_REG = 0x02 +MAX31865_HFAULTMSB_REG = 0x03 +MAX31865_HFAULTLSB_REG = 0x04 +MAX31865_LFAULTMSB_REG = 0x05 +MAX31865_LFAULTLSB_REG = 0x06 +MAX31865_FAULTSTAT_REG = 0x07 -MAX31865_CONFIG_BIAS = 0x80 -MAX31865_CONFIG_MODEAUTO = 0x40 -MAX31865_CONFIG_1SHOT = 0x20 -MAX31865_CONFIG_3WIRE = 0x10 -MAX31865_CONFIG_FAULTCLEAR = 0x02 -MAX31865_CONFIG_FILT50HZ = 0x01 +MAX31865_CONFIG_BIAS = 0x80 +MAX31865_CONFIG_MODEAUTO = 0x40 +MAX31865_CONFIG_1SHOT = 0x20 +MAX31865_CONFIG_3WIRE = 0x10 +MAX31865_CONFIG_FAULTCLEAR = 0x02 +MAX31865_CONFIG_FILT50HZ = 0x01 -MAX31865_FAULT_HIGHTHRESH = 0x80 -MAX31865_FAULT_LOWTHRESH = 0x40 -MAX31865_FAULT_REFINLOW = 0x20 -MAX31865_FAULT_REFINHIGH = 0x10 -MAX31865_FAULT_RTDINLOW = 0x08 -MAX31865_FAULT_OVUV = 0x04 +MAX31865_FAULT_HIGHTHRESH = 0x80 +MAX31865_FAULT_LOWTHRESH = 0x40 +MAX31865_FAULT_REFINLOW = 0x20 +MAX31865_FAULT_REFINHIGH = 0x10 +MAX31865_FAULT_RTDINLOW = 0x08 +MAX31865_FAULT_OVUV = 0x04 -MAX31865_ADC_MAX = 1<<15 +MAX31865_ADC_MAX = 1 << 15 # Callendar-Van Dusen constants for platinum resistance thermometers (RTD) CVD_A = 3.9083e-3 CVD_B = -5.775e-7 + class MAX31865(SensorBase): def __init__(self, config): - rtd_nominal_r = config.getfloat('rtd_nominal_r', 100., above=0.) - rtd_reference_r = config.getfloat('rtd_reference_r', 430., above=0.) + rtd_nominal_r = config.getfloat("rtd_nominal_r", 100.0, above=0.0) + rtd_reference_r = config.getfloat("rtd_reference_r", 430.0, above=0.0) adc_to_resist = rtd_reference_r / float(MAX31865_ADC_MAX) self.adc_to_resist_div_nominal = adc_to_resist / rtd_nominal_r self.config_reg = self.build_spi_init(config) SensorBase.__init__(self, config, "MAX31865", self.config_reg) + def handle_fault(self, adc, fault): if fault & 0x80: self.report_fault("Max31865 RTD input is disconnected") @@ -294,45 +325,47 @@ class MAX31865(SensorBase): self.report_fault("Max31865 RTD input is shorted") if fault & 0x20: self.report_fault( - "Max31865 VREF- is greater than 0.85 * VBIAS, FORCE- open") + "Max31865 VREF- is greater than 0.85 * VBIAS, FORCE- open" + ) if fault & 0x10: - self.report_fault( - "Max31865 VREF- is less than 0.85 * VBIAS, FORCE- open") + self.report_fault("Max31865 VREF- is less than 0.85 * VBIAS, FORCE- open") if fault & 0x08: - self.report_fault( - "Max31865 VRTD- is less than 0.85 * VBIAS, FORCE- open") + self.report_fault("Max31865 VRTD- is less than 0.85 * VBIAS, FORCE- open") if fault & 0x04: self.report_fault("Max31865 Overvoltage or undervoltage fault") - if not fault & 0xfc: + if not fault & 0xFC: self.report_fault("Max31865 Unspecified error") # Attempt to clear the fault self.spi.spi_send(self.config_reg) + def calc_temp(self, adc): - adc = adc >> 1 # remove fault bit + adc = adc >> 1 # remove fault bit R_div_nominal = adc * self.adc_to_resist_div_nominal # Resistance (relative to rtd_nominal_r) is calculated using: # R_div_nominal = 1. + CVD_A * temp + CVD_B * temp**2 # Solve for temp using quadratic equation: # temp = (-b +- sqrt(b**2 - 4ac)) / 2a - discriminant = math.sqrt(CVD_A**2 - 4. * CVD_B * (1. - R_div_nominal)) - temp = (-CVD_A + discriminant) / (2. * CVD_B) + discriminant = math.sqrt(CVD_A**2 - 4.0 * CVD_B * (1.0 - R_div_nominal)) + temp = (-CVD_A + discriminant) / (2.0 * CVD_B) return temp + def calc_adc(self, temp): # Calculate relative resistance via Callendar-Van Dusen formula: # resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2) temp = min(temp, 1768.3) # Melting point of platinum - R_div_nominal = 1. + CVD_A * temp + CVD_B * temp * temp + R_div_nominal = 1.0 + CVD_A * temp + CVD_B * temp * temp adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5) adc = max(0, min(MAX31865_ADC_MAX - 1, adc)) - adc = adc << 1 # Add fault bit + adc = adc << 1 # Add fault bit return adc + def build_spi_init(self, config): - value = (MAX31865_CONFIG_BIAS | - MAX31865_CONFIG_MODEAUTO | - MAX31865_CONFIG_FAULTCLEAR) - if config.getboolean('rtd_use_50Hz_filter', False): + value = ( + MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO | MAX31865_CONFIG_FAULTCLEAR + ) + if config.getboolean("rtd_use_50Hz_filter", False): value |= MAX31865_CONFIG_FILT50HZ - if config.getint('rtd_num_of_wires', 2) == 3: + if config.getint("rtd_num_of_wires", 2) == 3: value |= MAX31865_CONFIG_3WIRE cmd = 0x80 + MAX31865_CONFIG_REG return [cmd, value] @@ -349,6 +382,7 @@ Sensors = { "MAX31865": MAX31865, } + def load_config(config): # Register sensors pheaters = config.get_printer().load_object(config, "heaters") |