From 3835654116fd77f539bf643bf22c14fbf43d953b Mon Sep 17 00:00:00 2001 From: combolek <4743344+combolek@users.noreply.github.com> Date: Sun, 19 Jul 2020 17:18:54 -0700 Subject: [PATCH] lm75: Added support for LM75/LM75A I2C connected temperature sensors (#3101) Signed-off-by: Boleslaw Ciesielski --- config/example-extras.cfg | 24 +++++++++ klippy/extras/heaters.py | 2 +- klippy/extras/lm75.py | 107 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/lm75.py diff --git a/config/example-extras.cfg b/config/example-extras.cfg index f5f6bdb4..89279da9 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -1036,6 +1036,30 @@ #htu21d_report_time: # interval in seconds between readings. Default is 30 +# LM75/LM75A two wire (I2C) connected temperature sensors. +# These sensors have range up to 125 C, so are usable for e.g. chamber +# temperature monitoring. They can also function as simple +# fan/heater controllers but this mode is not used here. +#[temperature_sensor my_sensor] +# See the "temperature_sensor" section below for a description of its +# parameters. The parameters below describe LM75 family sensor parameters. +#sensor_type: +# Must be "LM75" +#i2c_address: +# Default is 72 (0x48). Normal range is 72-79 (0x48-0x4F) and the 3 low +# bits of the address are configured via pins on the chip (usually +# with jumpers or hard wired). +#i2c_mcu: +# MCU the sensor is connected to. Default is the primary mcu. +#i2c_bus: +# The I2C bus the sensor is connected to. On some MCU platforms the default +# is bus 0. On platforms without bus 0 this parameter is required. +#i2c_speed: +# The I2C speed (in Hz) to use when communicating with the sensor. Default +# is 100000. On some MCUs changing this value has no effect. +#lm75_report_time: +# interval in seconds between readings. Default is 0.8, with minimum 0.5 + # Generic heaters (one may define any number of sections with a # "heater_generic" prefix). These heaters behave similarly to standard # heaters (extruders, heated beds). Use the SET_HEATER_TEMPERATURE diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index e5cb4dbd..e66168c5 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -261,7 +261,7 @@ class PrinterHeaters: return self.heaters[heater_name] def setup_sensor(self, config): modules = ["thermistor", "adc_temperature", "spi_temperature", - "bme280", "htu21d"] + "bme280", "htu21d", "lm75"] for module_name in modules: self.printer.load_object(config, module_name) sensor_type = config.get('sensor_type') diff --git a/klippy/extras/lm75.py b/klippy/extras/lm75.py new file mode 100644 index 00000000..ca6f474d --- /dev/null +++ b/klippy/extras/lm75.py @@ -0,0 +1,107 @@ +# Support for I2C based LM75/LM75A temperature sensors +# +# Copyright (C) 2020 Boleslaw Ciesielski +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from . import bus + +LM75_CHIP_ADDR = 0x48 +LM75_I2C_SPEED = 100000 +LM75_REGS = { + 'TEMP' : 0x00, + 'CONF' : 0x01, + 'THYST' : 0x02, + 'TOS' : 0x03, + 'PRODID' : 0x07 # TI LM75A chips only? +} +LM75_REPORT_TIME = .8 +# Temperature can be sampled at any time but the read aborts +# the current conversion. Conversion time is 300ms so make +# sure not to read too often. +LM75_MIN_REPORT_TIME = .5 + +class LM75: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name().split()[-1] + self.reactor = self.printer.get_reactor() + self.i2c = bus.MCU_I2C_from_config( + config, + default_addr=LM75_CHIP_ADDR, + default_speed=LM75_I2C_SPEED + ) + self.mcu = self.i2c.get_mcu() + self.report_time = config.getint( + 'lm75_report_time', + LM75_REPORT_TIME, + minval=LM75_MIN_REPORT_TIME + ) + self.temp = 0.0 + self.sample_timer = self.reactor.register_timer(self._sample_lm75) + self.printer.add_object("lm75 " + self.name, self) + self.printer.register_event_handler("klippy:ready", self.handle_ready) + + def handle_ready(self): + self._init_lm75() + self.reactor.update_timer(self.sample_timer, self.reactor.NOW) + + def setup_minmax(self, min_temp, max_temp): + pass + + def setup_callback(self, cb): + self._callback = cb + + def get_report_time_delta(self): + return self.report_time + + def degrees_from_sample(self, x): + # The temp sample is encoded in the top 9 bits of a 16-bit + # value. Resolution is 0.5 degrees C. + return x[0] + (x[1] >> 7) * 0.5 + + def _init_lm75(self): + # Check and report the chip ID but ignore errors since many + # chips don't have it + try: + prodid = self.read_register('PRODID', 1)[0] + logging.info("lm75: Chip ID %#x" % prodid) + except: + pass + + def _sample_lm75(self, eventtime): + try: + sample = self.read_register('TEMP', 2) + self.temp = self.degrees_from_sample(sample) + except Exception: + logging.exception("lm75: Error reading data") + self.temp = 0.0 + return self.reactor.NEVER + + measured_time = self.reactor.monotonic() + self._callback(self.mcu.estimated_print_time(measured_time), self.temp) + return measured_time + self.report_time + + def read_register(self, reg_name, read_len): + # read a single register + regs = [LM75_REGS[reg_name]] + params = self.i2c.i2c_read(regs, read_len) + return bytearray(params['response']) + + def write_register(self, reg_name, data): + if type(data) is not list: + data = [data] + reg = LM75_REGS[reg_name] + data.insert(0, reg) + self.i2c.i2c_write(data) + + def get_status(self, eventtime): + return { + 'temperature': self.temp, + } + + +def load_config(config): + # Register sensor + pheaters = config.get_printer().load_object(config, "heaters") + pheaters.add_sensor_factory("LM75", LM75)